Skip to content

Commit

Permalink
Merge pull request #7 from liuxuan30/H264
Browse files Browse the repository at this point in the history
Add a H264 video decoder
  • Loading branch information
liuxuan30 authored Apr 22, 2020
2 parents 520e18c + 808eece commit 7f30dd6
Show file tree
Hide file tree
Showing 24 changed files with 2,164 additions and 3 deletions.
1 change: 1 addition & 0 deletions .codebeatignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TelloVideoDecoder/**
1 change: 1 addition & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ignore:
- "TelloSwiftTests"
- "Package.swift"
- "TelloVideoDecoder"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ TelloSwift is built upon SwiftNIO purely in Swift, providing flexible protocols
* Event-driven duplex command handlers based on SwiftNIO, powering sync and async command execution;
* Many useful built-in control commands to design your own flight flow;
* Full coverage for both Tello SDK and Tello 2.0 SDK (except for swarm)
* Provide a default H264 video decoder, more details [here](https://github.com/liuxuan30/TelloSwift/tree/master/TelloVideoDecoder)

## Requirement
* Xcode 11 / Swift 5
Expand Down Expand Up @@ -95,4 +96,3 @@ It will directly forward the raw stream bytes to the delegate. To turn on the ca
## TODO list
1. Swarm support
2. Better video streaming solution
11 changes: 10 additions & 1 deletion TelloSwift/Tello.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,21 @@ open class Tello {
RunLoop.main.add(kaTimer!, forMode: .common)
}
}


/// Invalidate timer and set it to nil
///
/// Make sure you call this method on the same thread as keepAlive()
public func invalidate() {
kaTimer?.invalidate()
kaTimer = nil
}

/// lazy name for invalidate() to clear timer
///
/// Make sure you call this method on the same thread as keepAlive()
public func clearTimer() {
invalidate()
}

/// Only used in unit tests for now.
func cleanup() {
Expand Down
2 changes: 1 addition & 1 deletion TelloSwiftTests/MockTelloFlightTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class MockTelloFlightTests: XCTestCase {
e.fulfill()
}
wait(for: [e], timeout: 5)
tello.invalidate()
tello.clearTimer()
XCTAssertNil(tello.kaTimer)
}

Expand Down
68 changes: 68 additions & 0 deletions TelloVideoDecoder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
Packages/
Package.pins
Package.resolved
.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
61 changes: 61 additions & 0 deletions TelloVideoDecoder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# TelloVideoH264Decoder
This contains a demo macOS app to illustrate how to use the `TelloVideoH264Decoder`

The `TelloVideoH264Decoder` serves like a demo decoder that can meet common requirement

I'm no expert on video decoding and I merely assemble code from different places and make it work. It could be a little buggy, and not world-class flexible for some configurations (e.g. `decodeFlags`)

Therefore I will be grateful that if you can send me pull requests to make it better.

## Get a valid NAL Unit
You need to first generate valid NALU either by yourself or by `getNalUnits()`/ `readNalUnits()`.

>getNalUnits(streamBuffer: Array<UInt8>) -> NALUnit?
will get all valid NALU and abandon incomplete NALU (data before next start code)

>readNalUnits(streamBuffer:inout Array<UInt8>) -> NALUnit?
will consume `streamBuffer`. To stop reading, call `stopProcessing()`

## Get CMSampleBuffer from NALU
Get a CMSampleBuffer by `getCMSampleBuffer(from nalu: NALUnit)`

## Get CVImageBuffer from CMSampleBuffer
Get a `CVImageBuffer` by calling `decompress()`

>decompress(sampleBuffer: CMSampleBuffer, outputHandler: @escaping VTDecompressionOutputHandler) -> OSStatus
This is a wrapper of `VTDecompressionSessionDecodeFrame(_:sampleBuffer:flags:infoFlagsOut:outputHandler:)`

note that
>outputHandler cannot be called with a session created with a VTDecompressionOutputCallbackRecord.
### what if I want to use VTDecompressionOutputCallbackRecord?
Grab the source code and modify `createVTDecompressionSession()` by commenting back
```swift
var outputCallback = VTDecompressionOutputCallbackRecord()
outputCallback.decompressionOutputCallback = decodeFrameCallback
outputCallback.decompressionOutputRefCon = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
```
and pass to `outputCallback: ` in `VTDecompressionSessionCreate()`

## About CMSampleTimingInfo
I don't quite understand what each parameter means while creating the CMSampleTimingInfo. Even if I don't set it, the video streaming seems working well. Welcome any improvement and knowledge sharing.

## About renderVideoStream()
>renderVideoStream(streamBuffer: inout Array<UInt8>, to videoLayer: AVSampleBufferDisplayLayer)
This is a lazy method for people just want to display the video stream from Tello with `AVSampleBufferDisplayLayer`

It simply keeps consuming `streamBuffer`, so you would want to put it into a `DispatchQueue` as it will block current thread, and be careful not causing write/read corruption. This also applies to `readNalUnits()`.

## TelloVideoDecoderMac
This mac app contains similar code used in `TelloVideoH264Decoder`, just to demonstrate how to use it in different ways.

`startHandling()` behaves like `renderVideoStream()`. You only need one of them in `viewDidAppear()`.

Remember "get a valid NALU" is the first step, so `getNALUnit()` is similar to `getNalUnits()`/ `readNalUnits()`.

## One more thing
For more information, refer WWDC 2014 Session 513 "Direct Access to Video Encoding and Decoding"
Loading

0 comments on commit 7f30dd6

Please sign in to comment.