Merge H264Decoder -> VideoCodec.
This commit is contained in:
parent
65b8aca8ee
commit
04d23d9953
|
@ -138,7 +138,6 @@
|
|||
29AF3FCF1D7C744C00E41212 /* NetStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF3FCE1D7C744C00E41212 /* NetStream.swift */; };
|
||||
29AF3FD01D7C745200E41212 /* NetStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF3FCE1D7C744C00E41212 /* NetStream.swift */; };
|
||||
29B8765B1CD70A7900FC07DA /* AudioCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876571CD70A7900FC07DA /* AudioCodec.swift */; };
|
||||
29B8765C1CD70A7900FC07DA /* H264Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876581CD70A7900FC07DA /* H264Decoder.swift */; };
|
||||
29B8765D1CD70A7900FC07DA /* VideoCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876591CD70A7900FC07DA /* VideoCodec.swift */; };
|
||||
29B876691CD70AB300FC07DA /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876631CD70AB300FC07DA /* Constants.swift */; };
|
||||
29B8766D1CD70AB300FC07DA /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876671CD70AB300FC07DA /* DataConvertible.swift */; };
|
||||
|
@ -175,7 +174,6 @@
|
|||
29B876BD1CD70B3900FC07DA /* CRC32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876B91CD70B3900FC07DA /* CRC32.swift */; };
|
||||
29B876BE1CD70B3900FC07DA /* EventDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876BA1CD70B3900FC07DA /* EventDispatcher.swift */; };
|
||||
29B876EC1CD70D5900FC07DA /* AudioCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876571CD70A7900FC07DA /* AudioCodec.swift */; };
|
||||
29B876ED1CD70D5900FC07DA /* H264Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876581CD70A7900FC07DA /* H264Decoder.swift */; };
|
||||
29B876EE1CD70D5900FC07DA /* VideoCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876591CD70A7900FC07DA /* VideoCodec.swift */; };
|
||||
29B876F01CD70D5900FC07DA /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876631CD70AB300FC07DA /* Constants.swift */; };
|
||||
29B876F41CD70D5900FC07DA /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876671CD70AB300FC07DA /* DataConvertible.swift */; };
|
||||
|
@ -253,7 +251,6 @@
|
|||
29EB3DEB1ED055B0001CAE8B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D3D5051ED053C000DD4AA6 /* ViewController.swift */; };
|
||||
29EB3DED1ED055B4001CAE8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29D3D5001ED053C000DD4AA6 /* Assets.xcassets */; };
|
||||
29EB3DEE1ED05763001CAE8B /* AudioCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876571CD70A7900FC07DA /* AudioCodec.swift */; };
|
||||
29EB3DEF1ED05766001CAE8B /* H264Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876581CD70A7900FC07DA /* H264Decoder.swift */; };
|
||||
29EB3DF01ED05768001CAE8B /* VideoCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876591CD70A7900FC07DA /* VideoCodec.swift */; };
|
||||
29EB3DF11ED0576C001CAE8B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876631CD70AB300FC07DA /* Constants.swift */; };
|
||||
29EB3DF21ED05770001CAE8B /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876671CD70AB300FC07DA /* DataConvertible.swift */; };
|
||||
|
@ -344,15 +341,15 @@
|
|||
BC4914A628DDD367009E2DF6 /* VTSessionOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */; };
|
||||
BC4914A728DDD367009E2DF6 /* VTSessionOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */; };
|
||||
BC4914A828DDD367009E2DF6 /* VTSessionOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */; };
|
||||
BC4914AA28DDD966009E2DF6 /* VTSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A928DDD966009E2DF6 /* VTSessionHolder.swift */; };
|
||||
BC4914AB28DDD966009E2DF6 /* VTSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A928DDD966009E2DF6 /* VTSessionHolder.swift */; };
|
||||
BC4914AC28DDD966009E2DF6 /* VTSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A928DDD966009E2DF6 /* VTSessionHolder.swift */; };
|
||||
BC4914AE28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914AD28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift */; };
|
||||
BC4914AF28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914AD28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift */; };
|
||||
BC4914B028DDF445009E2DF6 /* VTDecompressionSession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914AD28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift */; };
|
||||
BC4914B228DDFE31009E2DF6 /* VTSessionOptionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914B128DDFE31009E2DF6 /* VTSessionOptionKey.swift */; };
|
||||
BC4914B328DDFE31009E2DF6 /* VTSessionOptionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914B128DDFE31009E2DF6 /* VTSessionOptionKey.swift */; };
|
||||
BC4914B428DDFE31009E2DF6 /* VTSessionOptionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914B128DDFE31009E2DF6 /* VTSessionOptionKey.swift */; };
|
||||
BC4914B628DEC2FE009E2DF6 /* VTSessionMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */; };
|
||||
BC4914B728DEC2FE009E2DF6 /* VTSessionMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */; };
|
||||
BC4914B828DEC2FE009E2DF6 /* VTSessionMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */; };
|
||||
BC4C9EAC23F00F3A004A14F2 /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291468161E581C7D00E619BA /* Preference.swift */; };
|
||||
BC4C9EAF23F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4C9EAE23F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift */; };
|
||||
BC4C9EB023F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4C9EAE23F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift */; };
|
||||
|
@ -763,7 +760,6 @@
|
|||
29AF3FCE1D7C744C00E41212 /* NetStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetStream.swift; sourceTree = "<group>"; };
|
||||
29B8761B1CD701F900FC07DA /* HaishinKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HaishinKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
29B876571CD70A7900FC07DA /* AudioCodec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioCodec.swift; sourceTree = "<group>"; };
|
||||
29B876581CD70A7900FC07DA /* H264Decoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = H264Decoder.swift; sourceTree = "<group>"; };
|
||||
29B876591CD70A7900FC07DA /* VideoCodec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCodec.swift; sourceTree = "<group>"; };
|
||||
29B876631CD70AB300FC07DA /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
29B876671CD70AB300FC07DA /* DataConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataConvertible.swift; sourceTree = "<group>"; };
|
||||
|
@ -844,9 +840,9 @@
|
|||
BC44A1A823D31E92002D4297 /* AudioCodecBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCodecBuffer.swift; sourceTree = "<group>"; wrapsLines = 1; };
|
||||
BC4914A128DDD33D009E2DF6 /* VTSessionConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionConvertible.swift; sourceTree = "<group>"; };
|
||||
BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionOption.swift; sourceTree = "<group>"; };
|
||||
BC4914A928DDD966009E2DF6 /* VTSessionHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionHolder.swift; sourceTree = "<group>"; };
|
||||
BC4914AD28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VTDecompressionSession+Extension.swift"; sourceTree = "<group>"; };
|
||||
BC4914B128DDFE31009E2DF6 /* VTSessionOptionKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionOptionKey.swift; sourceTree = "<group>"; };
|
||||
BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionMode.swift; sourceTree = "<group>"; };
|
||||
BC4C9EAE23F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AudioStreamBasicDescription+Extension.swift"; sourceTree = "<group>"; };
|
||||
BC558267240BB40E00011AC0 /* RTMPStreamInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTMPStreamInfo.swift; sourceTree = "<group>"; };
|
||||
BC566F6D25D2ECC500573C4C /* HLSService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HLSService.swift; sourceTree = "<group>"; };
|
||||
|
@ -1019,10 +1015,9 @@
|
|||
29B876571CD70A7900FC07DA /* AudioCodec.swift */,
|
||||
BC44A1A823D31E92002D4297 /* AudioCodecBuffer.swift */,
|
||||
297E69112324E38800D418AB /* AudioCodecFormat.swift */,
|
||||
29B876581CD70A7900FC07DA /* H264Decoder.swift */,
|
||||
29B876591CD70A7900FC07DA /* VideoCodec.swift */,
|
||||
BC4914A128DDD33D009E2DF6 /* VTSessionConvertible.swift */,
|
||||
BC4914A928DDD966009E2DF6 /* VTSessionHolder.swift */,
|
||||
BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */,
|
||||
BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */,
|
||||
BC4914B128DDFE31009E2DF6 /* VTSessionOptionKey.swift */,
|
||||
);
|
||||
|
@ -2080,7 +2075,6 @@
|
|||
296242621D8DB86500C451A3 /* TSWriter.swift in Sources */,
|
||||
BC9CFA9323BDE8B700917EEF /* NetStreamDrawable.swift in Sources */,
|
||||
29B8769B1CD70B1100FC07DA /* MIME.swift in Sources */,
|
||||
BC4914AA28DDD966009E2DF6 /* VTSessionHolder.swift in Sources */,
|
||||
BCC1A727264FA1C100661156 /* ProfileLevelIndicationIndexDescriptor.swift in Sources */,
|
||||
29B8769C1CD70B1100FC07DA /* NetClient.swift in Sources */,
|
||||
BC94E530264146540094C169 /* MP4ReaderConvertible.swift in Sources */,
|
||||
|
@ -2162,6 +2156,7 @@
|
|||
29B8769D1CD70B1100FC07DA /* NetService.swift in Sources */,
|
||||
29B8769E1CD70B1100FC07DA /* NetSocket.swift in Sources */,
|
||||
2958911A1EEB8E3F00CE51E1 /* FLVAudioCodec.swift in Sources */,
|
||||
BC4914B628DEC2FE009E2DF6 /* VTSessionMode.swift in Sources */,
|
||||
295891261EEB8EF300CE51E1 /* FLVAACPacket.swift in Sources */,
|
||||
29B876791CD70ACE00FC07DA /* HTTPStream.swift in Sources */,
|
||||
BCA97C15263D93DB0027213C /* MP4VisualSampleEntry.swift in Sources */,
|
||||
|
@ -2183,7 +2178,6 @@
|
|||
BCB976D126107B1200C9A649 /* TSAdaptationExtensionField.swift in Sources */,
|
||||
BCA97C05263C61940027213C /* MP4MediaHeaderBox.swift in Sources */,
|
||||
29B876771CD70ACE00FC07DA /* HTTPResponse.swift in Sources */,
|
||||
29B8765C1CD70A7900FC07DA /* H264Decoder.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -2231,7 +2225,6 @@
|
|||
29B876EC1CD70D5900FC07DA /* AudioCodec.swift in Sources */,
|
||||
BCA97C0B263D80F40027213C /* MP4SampleEntry.swift in Sources */,
|
||||
BCC1A6D826446B2D00661156 /* MP4SoundMediaHeaderBox.swift in Sources */,
|
||||
29B876ED1CD70D5900FC07DA /* H264Decoder.swift in Sources */,
|
||||
BCB977402621812800C9A649 /* AVCFormatStream.swift in Sources */,
|
||||
29B876EE1CD70D5900FC07DA /* VideoCodec.swift in Sources */,
|
||||
BCA97BF1263C31020027213C /* MP4SampleDescriptionBox.swift in Sources */,
|
||||
|
@ -2291,6 +2284,7 @@
|
|||
BCC1A6C62643F41600661156 /* MP4MovieFragmentBox.Builder.swift in Sources */,
|
||||
296242641D8DBA9000C451A3 /* TSWriter.swift in Sources */,
|
||||
2958912F1EEB8F4100CE51E1 /* FLVSoundType.swift in Sources */,
|
||||
BC4914B728DEC2FE009E2DF6 /* VTSessionMode.swift in Sources */,
|
||||
BC94E4FF263FE6B80094C169 /* MP4MovieFragmentHeaderBox.swift in Sources */,
|
||||
29B877031CD70D5A00FC07DA /* AVAudioIOUnit.swift in Sources */,
|
||||
BCC1A6C02643F41600661156 /* MP4TrackFragmentBox.Builder.swift in Sources */,
|
||||
|
@ -2368,7 +2362,6 @@
|
|||
29EA87DA1E79A00E0043A5F8 /* ExpressibleByIntegerLiteral+Extension.swift in Sources */,
|
||||
29D0E3681DD4CE3700863B3B /* AnyUtil.swift in Sources */,
|
||||
29B8771C1CD70D5A00FC07DA /* CRC32.swift in Sources */,
|
||||
BC4914AB28DDD966009E2DF6 /* VTSessionHolder.swift in Sources */,
|
||||
2958912B1EEB8F1D00CE51E1 /* FLVSoundSize.swift in Sources */,
|
||||
BC94E50B263FEBB60094C169 /* MP4TrackRunBox.swift in Sources */,
|
||||
BC4914B328DDFE31009E2DF6 /* VTSessionOptionKey.swift in Sources */,
|
||||
|
@ -2476,7 +2469,6 @@
|
|||
BCA97BF6263C390E0027213C /* CustomXmlStringConvertible.swift in Sources */,
|
||||
BCC1A6BB2643F41600661156 /* MP4File.Builder.swift in Sources */,
|
||||
BC94E52E264146530094C169 /* MP4ReaderConvertible.swift in Sources */,
|
||||
BC4914AC28DDD966009E2DF6 /* VTSessionHolder.swift in Sources */,
|
||||
BCC1A72D264FAC1800661156 /* ElementaryStreamSpecificData.swift in Sources */,
|
||||
BCB976E126107B5600C9A649 /* TSAdaptationField.swift in Sources */,
|
||||
BC4914B028DDF445009E2DF6 /* VTDecompressionSession+Extension.swift in Sources */,
|
||||
|
@ -2492,7 +2484,6 @@
|
|||
29EB3E151ED0588C001CAE8B /* VideoEffect.swift in Sources */,
|
||||
29EB3E061ED05865001CAE8B /* MP4Reader.swift in Sources */,
|
||||
29EB3E041ED05860001CAE8B /* AVCConfigurationRecord.swift in Sources */,
|
||||
29EB3DEF1ED05766001CAE8B /* H264Decoder.swift in Sources */,
|
||||
29EB3DF71ED05797001CAE8B /* URL+Extension.swift in Sources */,
|
||||
29DF20682312A436004057C3 /* RTMPSocketCompatible.swift in Sources */,
|
||||
29EB3E0B1ED05871001CAE8B /* TSReader.swift in Sources */,
|
||||
|
@ -2550,6 +2541,7 @@
|
|||
29EB3E181ED05896001CAE8B /* NetService.swift in Sources */,
|
||||
295891281EEB8EF300CE51E1 /* FLVAACPacket.swift in Sources */,
|
||||
BCC1A7152647F28F00661156 /* SLConfigDescriptor.swift in Sources */,
|
||||
BC4914B828DEC2FE009E2DF6 /* VTSessionMode.swift in Sources */,
|
||||
BCA97B88263AC0F30027213C /* MP4BoxConvertible.swift in Sources */,
|
||||
2958911C1EEB8E3F00CE51E1 /* FLVAudioCodec.swift in Sources */,
|
||||
BC94E500263FE6B80094C169 /* MP4MovieFragmentHeaderBox.swift in Sources */,
|
||||
|
|
|
@ -1,219 +0,0 @@
|
|||
import AVFoundation
|
||||
import CoreFoundation
|
||||
import CoreVideo
|
||||
import VideoToolbox
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
protocol VideoDecoderDelegate: AnyObject {
|
||||
func sampleOutput(video sampleBuffer: CMSampleBuffer)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
final class H264Decoder {
|
||||
static let defaultDecodeFlags: VTDecodeFrameFlags = [
|
||||
._EnableAsynchronousDecompression,
|
||||
._EnableTemporalProcessing
|
||||
]
|
||||
static let defaultMinimumGroupOfPictures: Int = 12
|
||||
static let defaultAttributes: [NSString: AnyObject] = [
|
||||
kCVPixelBufferPixelFormatTypeKey: NSNumber(value: kCVPixelFormatType_32BGRA),
|
||||
kCVPixelBufferIOSurfacePropertiesKey: [:] as AnyObject,
|
||||
kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue
|
||||
]
|
||||
|
||||
var formatDescription: CMFormatDescription? {
|
||||
didSet {
|
||||
if let atoms: [String: AnyObject] = formatDescription?.`extension`(by: "SampleDescriptionExtensionAtoms"), let avcC: Data = atoms["avcC"] as? Data {
|
||||
let config = AVCConfigurationRecord(data: avcC)
|
||||
isBaseline = config.AVCProfileIndication == 66
|
||||
}
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
var isRunning: Atomic<Bool> = .init(false)
|
||||
weak var delegate: VideoDecoderDelegate?
|
||||
var lockQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.H264Decoder.lock")
|
||||
|
||||
var needsSync: Atomic<Bool> = .init(true)
|
||||
var isBaseline = true
|
||||
private var buffers: [CMSampleBuffer] = []
|
||||
private var attributes: [NSString: AnyObject] {
|
||||
H264Decoder.defaultAttributes
|
||||
}
|
||||
private var minimumGroupOfPictures: Int = H264Decoder.defaultMinimumGroupOfPictures
|
||||
private(set) var status: OSStatus = noErr {
|
||||
didSet {
|
||||
if status != noErr {
|
||||
logger.warn("\(self.status)")
|
||||
}
|
||||
}
|
||||
}
|
||||
private var invalidateSession = true
|
||||
private var callback: VTDecompressionOutputCallback = {(decompressionOutputRefCon: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?, status: OSStatus, infoFlags: VTDecodeInfoFlags, imageBuffer: CVBuffer?, presentationTimeStamp: CMTime, duration: CMTime) in
|
||||
let decoder: H264Decoder = Unmanaged<H264Decoder>.fromOpaque(decompressionOutputRefCon!).takeUnretainedValue()
|
||||
decoder.didOutputForSession(status, infoFlags: infoFlags, imageBuffer: imageBuffer, presentationTimeStamp: presentationTimeStamp, duration: duration)
|
||||
}
|
||||
|
||||
private var _session: VTDecompressionSession?
|
||||
private var session: VTDecompressionSession! {
|
||||
get {
|
||||
if _session == nil {
|
||||
guard let formatDescription: CMFormatDescription = formatDescription else {
|
||||
return nil
|
||||
}
|
||||
var record = VTDecompressionOutputCallbackRecord(
|
||||
decompressionOutputCallback: callback,
|
||||
decompressionOutputRefCon: Unmanaged.passUnretained(self).toOpaque()
|
||||
)
|
||||
guard VTDecompressionSessionCreate(
|
||||
allocator: kCFAllocatorDefault,
|
||||
formatDescription: formatDescription,
|
||||
decoderSpecification: nil,
|
||||
imageBufferAttributes: attributes as CFDictionary?,
|
||||
outputCallback: &record,
|
||||
decompressionSessionOut: &_session ) == noErr else {
|
||||
return nil
|
||||
}
|
||||
invalidateSession = false
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
set {
|
||||
if let session: VTDecompressionSession = _session {
|
||||
VTDecompressionSessionInvalidate(session)
|
||||
}
|
||||
_session = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func decodeSampleBuffer(_ sampleBuffer: CMSampleBuffer) -> OSStatus {
|
||||
if invalidateSession {
|
||||
session = nil
|
||||
needsSync.mutate { $0 = true }
|
||||
}
|
||||
if !sampleBuffer.isNotSync {
|
||||
needsSync.mutate { $0 = false }
|
||||
}
|
||||
guard let session: VTDecompressionSession = session, !needsSync.value else {
|
||||
return kVTInvalidSessionErr
|
||||
}
|
||||
var flagsOut: VTDecodeInfoFlags = []
|
||||
return VTDecompressionSessionDecodeFrame(
|
||||
session,
|
||||
sampleBuffer: sampleBuffer,
|
||||
flags: H264Decoder.defaultDecodeFlags,
|
||||
frameRefcon: nil,
|
||||
infoFlagsOut: &flagsOut
|
||||
)
|
||||
}
|
||||
|
||||
func didOutputForSession(_ status: OSStatus, infoFlags: VTDecodeInfoFlags, imageBuffer: CVImageBuffer?, presentationTimeStamp: CMTime, duration: CMTime) {
|
||||
guard let imageBuffer: CVImageBuffer = imageBuffer, status == noErr else {
|
||||
return
|
||||
}
|
||||
|
||||
var timingInfo = CMSampleTimingInfo(
|
||||
duration: duration,
|
||||
presentationTimeStamp: presentationTimeStamp,
|
||||
decodeTimeStamp: CMTime.invalid
|
||||
)
|
||||
|
||||
var videoFormatDescription: CMVideoFormatDescription?
|
||||
self.status = CMVideoFormatDescriptionCreateForImageBuffer(
|
||||
allocator: kCFAllocatorDefault,
|
||||
imageBuffer: imageBuffer,
|
||||
formatDescriptionOut: &videoFormatDescription
|
||||
)
|
||||
|
||||
var sampleBuffer: CMSampleBuffer?
|
||||
self.status = CMSampleBufferCreateForImageBuffer(
|
||||
allocator: kCFAllocatorDefault,
|
||||
imageBuffer: imageBuffer,
|
||||
dataReady: true,
|
||||
makeDataReadyCallback: nil,
|
||||
refcon: nil,
|
||||
formatDescription: videoFormatDescription!,
|
||||
sampleTiming: &timingInfo,
|
||||
sampleBufferOut: &sampleBuffer
|
||||
)
|
||||
|
||||
guard let buffer: CMSampleBuffer = sampleBuffer else {
|
||||
return
|
||||
}
|
||||
|
||||
if isBaseline {
|
||||
delegate?.sampleOutput(video: buffer)
|
||||
} else {
|
||||
buffers.append(buffer)
|
||||
buffers.sort {
|
||||
$0.presentationTimeStamp < $1.presentationTimeStamp
|
||||
}
|
||||
if minimumGroupOfPictures <= buffers.count {
|
||||
delegate?.sampleOutput(video: buffers.removeFirst())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
@objc
|
||||
private func applicationWillEnterForeground(_ notification: Notification) {
|
||||
invalidateSession = true
|
||||
}
|
||||
|
||||
@objc
|
||||
private func didAudioSessionInterruption(_ notification: Notification) {
|
||||
guard
|
||||
let userInfo: [AnyHashable: Any] = notification.userInfo,
|
||||
let value: NSNumber = userInfo[AVAudioSessionInterruptionTypeKey] as? NSNumber,
|
||||
let type = AVAudioSession.InterruptionType(rawValue: value.uintValue) else {
|
||||
return
|
||||
}
|
||||
switch type {
|
||||
case .ended:
|
||||
invalidateSession = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extension H264Decoder: Running {
|
||||
// MARK: Running
|
||||
func startRunning() {
|
||||
lockQueue.async {
|
||||
self.isRunning.mutate { $0 = true }
|
||||
#if os(iOS)
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(self.didAudioSessionInterruption),
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(self.applicationWillEnterForeground),
|
||||
name: UIApplication.willEnterForegroundNotification,
|
||||
object: nil
|
||||
)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func stopRunning() {
|
||||
lockQueue.async {
|
||||
self.session = nil
|
||||
self.needsSync.mutate { $0 = true }
|
||||
self.invalidateSession = true
|
||||
self.buffers.removeAll()
|
||||
self.formatDescription = nil
|
||||
#if os(iOS)
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
#endif
|
||||
self.isRunning.mutate { $0 = false }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ protocol VTSessionConvertible {
|
|||
func setOptions(_ options: Set<VTSessionOption>) -> OSStatus
|
||||
func copySupportedPropertyDictionary() -> [AnyHashable: Any]
|
||||
func inputBuffer(_ imageBuffer: CVImageBuffer, presentationTimeStamp: CMTime, duration: CMTime, outputHandler: @escaping VTCompressionOutputHandler)
|
||||
func inputBuffer(_ sampleBuffer: CMSampleBuffer, outputHandler: @escaping VTDecompressionOutputHandler)
|
||||
func invalidate()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import Foundation
|
||||
import VideoToolbox
|
||||
|
||||
struct VTSessionHolder {
|
||||
private(set) var isInvalidateSession = false
|
||||
private(set) var session: VTSessionConvertible?
|
||||
|
||||
mutating func makeSession(_ videoCodec: VideoCodec) -> OSStatus {
|
||||
session?.invalidate()
|
||||
session = nil
|
||||
var session: VTCompressionSession?
|
||||
var status = VTCompressionSessionCreate(
|
||||
allocator: kCFAllocatorDefault,
|
||||
width: videoCodec.width,
|
||||
height: videoCodec.height,
|
||||
codecType: kCMVideoCodecType_H264,
|
||||
encoderSpecification: nil,
|
||||
imageBufferAttributes: nil,
|
||||
compressedDataAllocator: nil,
|
||||
outputCallback: nil,
|
||||
refcon: nil,
|
||||
compressionSessionOut: &session
|
||||
)
|
||||
guard status == noErr, let session else {
|
||||
videoCodec.delegate?.videoCodec(videoCodec, errorOccurred: .failedToCreate(status: status))
|
||||
return status
|
||||
}
|
||||
status = session.setOptions(videoCodec.options())
|
||||
status = session.prepareToEncodeFrame()
|
||||
guard status == noErr else {
|
||||
videoCodec.delegate?.videoCodec(videoCodec, errorOccurred: .failedToPrepare(status: status))
|
||||
return status
|
||||
}
|
||||
self.session = session
|
||||
isInvalidateSession = false
|
||||
return noErr
|
||||
}
|
||||
|
||||
mutating func invalidateSession() {
|
||||
isInvalidateSession = true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import Foundation
|
||||
import VideoToolbox
|
||||
|
||||
enum VTSessionMode {
|
||||
case compression
|
||||
case decompression
|
||||
|
||||
func makeSession(_ videoCodec: VideoCodec) -> VTSessionConvertible? {
|
||||
switch self {
|
||||
case .compression:
|
||||
var session: VTCompressionSession?
|
||||
var status = VTCompressionSessionCreate(
|
||||
allocator: kCFAllocatorDefault,
|
||||
width: videoCodec.width,
|
||||
height: videoCodec.height,
|
||||
codecType: kCMVideoCodecType_H264,
|
||||
encoderSpecification: nil,
|
||||
imageBufferAttributes: videoCodec.attributes as CFDictionary?,
|
||||
compressedDataAllocator: nil,
|
||||
outputCallback: nil,
|
||||
refcon: nil,
|
||||
compressionSessionOut: &session
|
||||
)
|
||||
guard status == noErr, let session else {
|
||||
videoCodec.delegate?.videoCodec(videoCodec, errorOccurred: .failedToCreate(status: status))
|
||||
return nil
|
||||
}
|
||||
status = session.setOptions(videoCodec.options())
|
||||
status = session.prepareToEncodeFrames()
|
||||
guard status == noErr else {
|
||||
videoCodec.delegate?.videoCodec(videoCodec, errorOccurred: .failedToPrepare(status: status))
|
||||
return nil
|
||||
}
|
||||
return session
|
||||
case .decompression:
|
||||
guard let formatDescription = videoCodec.formatDescription else {
|
||||
videoCodec.delegate?.videoCodec(videoCodec, errorOccurred: .failedToCreate(status: kVTParameterErr))
|
||||
return nil
|
||||
}
|
||||
var attributes = videoCodec.attributes
|
||||
attributes?.removeValue(forKey: kCVPixelBufferWidthKey)
|
||||
attributes?.removeValue(forKey: kCVPixelBufferHeightKey)
|
||||
var session: VTDecompressionSession?
|
||||
let status = VTDecompressionSessionCreate(
|
||||
allocator: kCFAllocatorDefault,
|
||||
formatDescription: formatDescription,
|
||||
decoderSpecification: nil,
|
||||
imageBufferAttributes: attributes as CFDictionary?,
|
||||
outputCallback: nil,
|
||||
decompressionSessionOut: &session
|
||||
)
|
||||
guard status == noErr else {
|
||||
videoCodec.delegate?.videoCodec(videoCodec, errorOccurred: .failedToCreate(status: status))
|
||||
return nil
|
||||
}
|
||||
return session
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
import Foundation
|
||||
|
||||
public struct VTSessionOption: Hashable {
|
||||
public struct VTSessionOption {
|
||||
let key: VTSessionOptionKey
|
||||
let value: AnyObject
|
||||
}
|
||||
|
||||
extension VTSessionOption: Hashable {
|
||||
// MARK: Hashable
|
||||
public static func == (lhs: VTSessionOption, rhs: VTSessionOption) -> Bool {
|
||||
return lhs.key.CFString == rhs.key.CFString
|
||||
}
|
||||
|
||||
let key: VTSessionOptionKey
|
||||
let value: AnyObject
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
return hasher.combine(key.CFString)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ struct VTSessionOptionKey {
|
|||
#endif
|
||||
static let multiPassStorage = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_MultiPassStorage)
|
||||
static let forceKeyFrame = VTSessionOptionKey(CFString: kVTEncodeFrameOptionKey_ForceKeyFrame)
|
||||
static let fpixelTransferProperties = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_PixelTransferProperties)
|
||||
static let pixelTransferProperties = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_PixelTransferProperties)
|
||||
static let averageBitRate = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_AverageBitRate)
|
||||
static let dataRateLimits = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_DataRateLimits)
|
||||
static let moreFramesAfterEnd = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_MoreFramesAfterEnd)
|
||||
|
@ -41,7 +41,7 @@ struct VTSessionOptionKey {
|
|||
static let realTime = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_RealTime)
|
||||
static let maxH264SliceBytes = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_MaxH264SliceBytes)
|
||||
static let maxFrameDelayCount = VTSessionOptionKey(CFString: kVTCompressionPropertyKey_MaxFrameDelayCount)
|
||||
static let encoderUsage = VTSessionOptionKey(CFString: "EncoderUsage" as CFString)
|
||||
static let encoderID = VTSessionOptionKey(CFString: kVTVideoEncoderSpecification_EncoderID)
|
||||
|
||||
let CFString: CFString
|
||||
}
|
||||
|
|
|
@ -12,8 +12,10 @@ import UIKit
|
|||
public protocol VideoCodecDelegate: AnyObject {
|
||||
/// Tells the receiver to set a formatDescription.
|
||||
func videoCodec(_ codec: VideoCodec, didSet formatDescription: CMFormatDescription?)
|
||||
/// Tells the receiver to output a encoded or decoded sampleBuffer.
|
||||
func videoCodec(_ codec: VideoCodec, didOutput sampleBuffer: CMSampleBuffer)
|
||||
/// Tells the receiver to output an encoded sampleBuffer.
|
||||
func videoCodec(_ codec: VideoCodec, didCompress sampleBuffer: CMSampleBuffer)
|
||||
/// Tells the receiver to output a decodec sampleBuffer.
|
||||
func videoCodec(_ codec: VideoCodec, didDecompress sampleBuffer: CMSampleBuffer)
|
||||
/// Tells the receiver to occured an error.
|
||||
func videoCodec(_ codec: VideoCodec, errorOccurred error: VideoCodec.Error)
|
||||
}
|
||||
|
@ -23,6 +25,16 @@ public protocol VideoCodecDelegate: AnyObject {
|
|||
* The VideoCodec class provides methods for encode or decode for video.
|
||||
*/
|
||||
public class VideoCodec {
|
||||
static let defaultMinimumGroupOfPictures: Int = 12
|
||||
|
||||
#if os(OSX)
|
||||
#if arch(arm64)
|
||||
static let encoderName = NSString(string: "com.apple.videotoolbox.videoencoder.ave.avc")
|
||||
#else
|
||||
static let encoderName = NSString(string: "com.apple.videotoolbox.videoencoder.h264.gva")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The VideoCodec error domain codes.
|
||||
*/
|
||||
|
@ -111,29 +123,29 @@ public class VideoCodec {
|
|||
public private(set) var isRunning: Atomic<Bool> = .init(false)
|
||||
|
||||
var muted = false
|
||||
var scalingMode: ScalingMode = VideoCodec.defaultScalingMode {
|
||||
var scalingMode = VideoCodec.defaultScalingMode {
|
||||
didSet {
|
||||
guard scalingMode != oldValue else {
|
||||
return
|
||||
}
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
|
||||
var width: Int32 = VideoCodec.defaultWidth {
|
||||
var width = VideoCodec.defaultWidth {
|
||||
didSet {
|
||||
guard width != oldValue else {
|
||||
return
|
||||
}
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
var height: Int32 = VideoCodec.defaultHeight {
|
||||
var height = VideoCodec.defaultHeight {
|
||||
didSet {
|
||||
guard height != oldValue else {
|
||||
return
|
||||
}
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
|
@ -142,35 +154,35 @@ public class VideoCodec {
|
|||
guard enabledHardwareEncoder != oldValue else {
|
||||
return
|
||||
}
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
#endif
|
||||
var bitrate: UInt32 = VideoCodec.defaultBitrate {
|
||||
var bitrate = VideoCodec.defaultBitrate {
|
||||
didSet {
|
||||
guard bitrate != oldValue else {
|
||||
return
|
||||
}
|
||||
let option = VTSessionOption(key: .averageBitRate, value: NSNumber(value: bitrate))
|
||||
if let status = sessionHolder.session?.setOption(option), status != noErr {
|
||||
if let status = session?.setOption(option), status != noErr {
|
||||
delegate?.videoCodec(self, errorOccurred: .failedToSetOption(status: status, option: option))
|
||||
}
|
||||
}
|
||||
}
|
||||
var profileLevel: String = kVTProfileLevel_H264_Baseline_3_1 as String {
|
||||
var profileLevel = kVTProfileLevel_H264_Baseline_3_1 as String {
|
||||
didSet {
|
||||
guard profileLevel != oldValue else {
|
||||
return
|
||||
}
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
var maxKeyFrameIntervalDuration: Double = 2.0 {
|
||||
var maxKeyFrameIntervalDuration = 2.0 {
|
||||
didSet {
|
||||
guard maxKeyFrameIntervalDuration != oldValue else {
|
||||
return
|
||||
}
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
// swiftlint:disable discouraged_optional_boolean
|
||||
|
@ -179,19 +191,18 @@ public class VideoCodec {
|
|||
guard allowFrameReordering != oldValue else {
|
||||
return
|
||||
}
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
}
|
||||
var locked: UInt32 = 0
|
||||
var lockQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.VideoCodec.lock")
|
||||
var expectedFPS: Float64 = AVMixer.defaultFPS {
|
||||
var expectedFrameRate = AVMixer.defaultFPS {
|
||||
didSet {
|
||||
guard expectedFPS != oldValue else {
|
||||
guard expectedFrameRate != oldValue else {
|
||||
return
|
||||
}
|
||||
let option = VTSessionOption(key: .expectedFrameRate, value: NSNumber(value: expectedFPS))
|
||||
if let status = sessionHolder.session?.setOption(option), status != noErr {
|
||||
print(status)
|
||||
let option = VTSessionOption(key: .expectedFrameRate, value: NSNumber(value: expectedFrameRate))
|
||||
if let status = session?.setOption(option), status != noErr {
|
||||
delegate?.videoCodec(self, errorOccurred: .failedToSetOption(status: status, option: option))
|
||||
}
|
||||
}
|
||||
|
@ -201,11 +212,16 @@ public class VideoCodec {
|
|||
guard !CMFormatDescriptionEqual(formatDescription, otherFormatDescription: oldValue) else {
|
||||
return
|
||||
}
|
||||
if let atoms: [String: AnyObject] = formatDescription?.`extension`(by: "SampleDescriptionExtensionAtoms"), let avcC: Data = atoms["avcC"] as? Data {
|
||||
let config = AVCConfigurationRecord(data: avcC)
|
||||
isBaseline = config.AVCProfileIndication == 66
|
||||
}
|
||||
delegate?.videoCodec(self, didSet: formatDescription)
|
||||
}
|
||||
}
|
||||
weak var delegate: VideoCodecDelegate?
|
||||
private var attributes: [NSString: AnyObject]? {
|
||||
var needsSync: Atomic<Bool> = .init(true)
|
||||
var isBaseline = true
|
||||
var attributes: [NSString: AnyObject]? {
|
||||
guard VideoCodec.defaultAttributes != nil else {
|
||||
return nil
|
||||
}
|
||||
|
@ -217,8 +233,17 @@ public class VideoCodec {
|
|||
attributes[kCVPixelBufferHeightKey] = NSNumber(value: height)
|
||||
return attributes
|
||||
}
|
||||
weak var delegate: VideoCodecDelegate?
|
||||
|
||||
private var lastImageBuffer: CVImageBuffer?
|
||||
private var sessionHolder = VTSessionHolder()
|
||||
private var session: VTSessionConvertible? {
|
||||
didSet {
|
||||
oldValue?.invalidate()
|
||||
}
|
||||
}
|
||||
private var invalidateSession = true
|
||||
private var buffers: [CMSampleBuffer] = []
|
||||
private var minimumGroupOfPictures: Int = VideoCodec.defaultMinimumGroupOfPictures
|
||||
|
||||
init() {
|
||||
settings.observer = self
|
||||
|
@ -228,10 +253,10 @@ public class VideoCodec {
|
|||
guard isRunning.value && locked == 0 else {
|
||||
return
|
||||
}
|
||||
if sessionHolder.isInvalidateSession {
|
||||
_ = sessionHolder.makeSession(self)
|
||||
if invalidateSession {
|
||||
session = VTSessionMode.compression.makeSession(self)
|
||||
}
|
||||
sessionHolder.session?.inputBuffer(
|
||||
session?.inputBuffer(
|
||||
muted ? lastImageBuffer ?? imageBuffer : imageBuffer,
|
||||
presentationTimeStamp: presentationTimeStamp,
|
||||
duration: duration
|
||||
|
@ -241,32 +266,91 @@ public class VideoCodec {
|
|||
return
|
||||
}
|
||||
self.formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
|
||||
self.delegate?.videoCodec(self, didOutput: sampleBuffer)
|
||||
self.delegate?.videoCodec(self, didCompress: sampleBuffer)
|
||||
if !self.muted || self.lastImageBuffer == nil {
|
||||
self.lastImageBuffer = imageBuffer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func inputBuffer(_ sampleBuffer: CMSampleBuffer) {
|
||||
if invalidateSession {
|
||||
session = VTSessionMode.decompression.makeSession(self)
|
||||
needsSync.mutate { $0 = true }
|
||||
}
|
||||
if !sampleBuffer.isNotSync {
|
||||
needsSync.mutate { $0 = false }
|
||||
}
|
||||
session?.inputBuffer(sampleBuffer) { [unowned self] status, _, imageBuffer, presentationTimeStamp, duration in
|
||||
guard let imageBuffer = imageBuffer, status == noErr else {
|
||||
self.delegate?.videoCodec(self, errorOccurred: .failedToFlame(status: status))
|
||||
return
|
||||
}
|
||||
|
||||
var timingInfo = CMSampleTimingInfo(
|
||||
duration: duration,
|
||||
presentationTimeStamp: presentationTimeStamp,
|
||||
decodeTimeStamp: .invalid
|
||||
)
|
||||
|
||||
var videoFormatDescription: CMVideoFormatDescription?
|
||||
var status = CMVideoFormatDescriptionCreateForImageBuffer(
|
||||
allocator: kCFAllocatorDefault,
|
||||
imageBuffer: imageBuffer,
|
||||
formatDescriptionOut: &videoFormatDescription
|
||||
)
|
||||
|
||||
guard status == noErr else {
|
||||
self.delegate?.videoCodec(self, errorOccurred: .failedToFlame(status: status))
|
||||
return
|
||||
}
|
||||
|
||||
var sampleBuffer: CMSampleBuffer?
|
||||
status = CMSampleBufferCreateForImageBuffer(
|
||||
allocator: kCFAllocatorDefault,
|
||||
imageBuffer: imageBuffer,
|
||||
dataReady: true,
|
||||
makeDataReadyCallback: nil,
|
||||
refcon: nil,
|
||||
formatDescription: videoFormatDescription!,
|
||||
sampleTiming: &timingInfo,
|
||||
sampleBufferOut: &sampleBuffer
|
||||
)
|
||||
|
||||
guard let buffer = sampleBuffer, status == noErr else {
|
||||
self.delegate?.videoCodec(self, errorOccurred: .failedToFlame(status: status))
|
||||
return
|
||||
}
|
||||
|
||||
if self.isBaseline {
|
||||
self.delegate?.videoCodec(self, didDecompress: buffer)
|
||||
} else {
|
||||
self.buffers.append(buffer)
|
||||
self.buffers.sort {
|
||||
$0.presentationTimeStamp < $1.presentationTimeStamp
|
||||
}
|
||||
if self.minimumGroupOfPictures <= buffers.count {
|
||||
self.delegate?.videoCodec(self, didDecompress: buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func options() -> Set<VTSessionOption> {
|
||||
let isBaseline = profileLevel.contains("Baseline")
|
||||
var options = Set<VTSessionOption>([
|
||||
.init(key: .realTime, value: kCFBooleanTrue),
|
||||
.init(key: .profileLevel, value: profileLevel as NSObject),
|
||||
.init(key: .averageBitRate, value: NSNumber(value: bitrate)),
|
||||
.init(key: .expectedFrameRate, value: NSNumber(value: expectedFPS)),
|
||||
.init(key: .expectedFrameRate, value: NSNumber(value: expectedFrameRate)),
|
||||
.init(key: .maxKeyFrameIntervalDuration, value: NSNumber(value: maxKeyFrameIntervalDuration)),
|
||||
.init(key: .allowFrameReordering, value: (allowFrameReordering ?? !isBaseline) as NSObject)
|
||||
])
|
||||
#if os(OSX)
|
||||
if enabledHardwareEncoder {
|
||||
#if arch(arm64)
|
||||
options.insert(.init(key: .init(CFString: kVTVideoEncoderSpecification_EncoderID), value: "com.apple.videotoolbox.videoencoder.ave.avc" as NSObject))
|
||||
#else
|
||||
options.insert(.init(key: .init(CFString: kVTVideoEncoderSpecification_EncoderID), value: "com.apple.videotoolbox.videoencoder.h264.gva" as NSObject))
|
||||
#endif
|
||||
options.insert(.init(key: .init(CFString: "EnableHardwareAcceleratedVideoEncoder" as CFString), value: kCFBooleanTrue))
|
||||
options.insert(.init(key: .init(CFString: "RequireHardwareAcceleratedVideoEncoder" as CFString), value: kCFBooleanTrue))
|
||||
options.insert(.init(key: .encoderID, value: VideoCodec.encoderName))
|
||||
options.insert(.init(key: .enableHardwareAcceleratedVideoEncoder, value: kCFBooleanTrue))
|
||||
options.insert(.init(key: .requireHardwareAcceleratedVideoEncoder, value: kCFBooleanTrue))
|
||||
}
|
||||
#endif
|
||||
if !isBaseline {
|
||||
|
@ -278,7 +362,7 @@ public class VideoCodec {
|
|||
#if os(iOS)
|
||||
@objc
|
||||
private func applicationWillEnterForeground(_ notification: Notification) {
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -291,7 +375,7 @@ public class VideoCodec {
|
|||
}
|
||||
switch type {
|
||||
case .ended:
|
||||
sessionHolder.invalidateSession()
|
||||
invalidateSession = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -324,7 +408,10 @@ extension VideoCodec: Running {
|
|||
|
||||
public func stopRunning() {
|
||||
lockQueue.async {
|
||||
self.sessionHolder.invalidateSession()
|
||||
self.session = nil
|
||||
self.invalidateSession = true
|
||||
self.needsSync.mutate { $0 = true }
|
||||
self.buffers.removeAll()
|
||||
self.lastImageBuffer = nil
|
||||
self.formatDescription = nil
|
||||
#if os(iOS)
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import Foundation
|
||||
import VideoToolbox
|
||||
|
||||
extension VTCompressionSession {
|
||||
func prepareToEncodeFrames() -> OSStatus {
|
||||
VTCompressionSessionPrepareToEncodeFrames(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension VTCompressionSession: VTSessionConvertible {
|
||||
// MARK: VTSessionConvertible
|
||||
func inputBuffer(_ imageBuffer: CVImageBuffer, presentationTimeStamp: CMTime, duration: CMTime, outputHandler: @escaping VTCompressionOutputHandler) {
|
||||
var flags: VTEncodeInfoFlags = []
|
||||
VTCompressionSessionEncodeFrame(
|
||||
|
@ -15,13 +22,10 @@ extension VTCompressionSession: VTSessionConvertible {
|
|||
)
|
||||
}
|
||||
|
||||
func inputBuffer(_ sampleBuffer: CMSampleBuffer, outputHandler: @escaping VTDecompressionOutputHandler) {
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
VTCompressionSessionInvalidate(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension VTCompressionSession {
|
||||
func prepareToEncodeFrame() -> OSStatus {
|
||||
VTCompressionSessionPrepareToEncodeFrames(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,25 @@ import Foundation
|
|||
import VideoToolbox
|
||||
|
||||
extension VTDecompressionSession: VTSessionConvertible {
|
||||
static let defaultDecodeFlags: VTDecodeFrameFlags = [
|
||||
._EnableAsynchronousDecompression,
|
||||
._EnableTemporalProcessing
|
||||
]
|
||||
|
||||
func inputBuffer(_ imageBuffer: CVImageBuffer, presentationTimeStamp: CMTime, duration: CMTime, outputHandler: @escaping VTCompressionOutputHandler) {
|
||||
}
|
||||
|
||||
func inputBuffer(_ sampleBuffer: CMSampleBuffer, outputHandler: @escaping VTDecompressionOutputHandler) {
|
||||
var flagsOut: VTDecodeInfoFlags = []
|
||||
VTDecompressionSessionDecodeFrame(
|
||||
self,
|
||||
sampleBuffer: sampleBuffer,
|
||||
flags: Self.defaultDecodeFlags,
|
||||
infoFlagsOut: &flagsOut,
|
||||
outputHandler: outputHandler
|
||||
)
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
VTDecompressionSessionInvalidate(self)
|
||||
}
|
||||
|
|
|
@ -256,6 +256,7 @@ extension AVMixer {
|
|||
public func startDecoding(_ audioEngine: AVAudioEngine?) {
|
||||
mediaLink.startRunning()
|
||||
audioIO.startDecoding(audioEngine)
|
||||
videoIO.codec.delegate = videoIO
|
||||
videoIO.startDecoding()
|
||||
}
|
||||
|
||||
|
@ -270,7 +271,7 @@ extension AVMixer {
|
|||
extension AVMixer: MediaLinkDelegate {
|
||||
// MARK: MediaLinkDelegate
|
||||
func mediaLink(_ mediaLink: MediaLink, dequeue sampleBuffer: CMSampleBuffer) {
|
||||
_ = videoIO.decoder.decodeSampleBuffer(sampleBuffer)
|
||||
videoIO.codec.inputBuffer(sampleBuffer)
|
||||
}
|
||||
|
||||
func mediaLink(_ mediaLink: MediaLink, didBufferingChanged: Bool) {
|
||||
|
|
|
@ -29,7 +29,7 @@ final class AVVideoIOUnit: NSObject, AVIOUnit {
|
|||
|
||||
var formatDescription: CMVideoFormatDescription? {
|
||||
didSet {
|
||||
decoder.formatDescription = formatDescription
|
||||
codec.formatDescription = formatDescription
|
||||
}
|
||||
}
|
||||
lazy var codec: VideoCodec = {
|
||||
|
@ -37,11 +37,6 @@ final class AVVideoIOUnit: NSObject, AVIOUnit {
|
|||
codec.lockQueue = lockQueue
|
||||
return codec
|
||||
}()
|
||||
lazy var decoder: H264Decoder = {
|
||||
var decoder = H264Decoder()
|
||||
decoder.delegate = self
|
||||
return decoder
|
||||
}()
|
||||
weak var mixer: AVMixer?
|
||||
|
||||
private(set) var effects: Set<VideoEffect> = []
|
||||
|
@ -87,7 +82,7 @@ final class AVVideoIOUnit: NSObject, AVIOUnit {
|
|||
}
|
||||
|
||||
fps = data.fps
|
||||
codec.expectedFPS = data.fps
|
||||
codec.expectedFrameRate = data.fps
|
||||
logger.info("\(data)")
|
||||
|
||||
do {
|
||||
|
@ -434,11 +429,11 @@ extension AVVideoIOUnit {
|
|||
|
||||
extension AVVideoIOUnit {
|
||||
func startDecoding() {
|
||||
decoder.startRunning()
|
||||
codec.startRunning()
|
||||
}
|
||||
|
||||
func stopDecoding() {
|
||||
decoder.stopRunning()
|
||||
codec.stopRunning()
|
||||
drawable?.enqueue(nil)
|
||||
}
|
||||
}
|
||||
|
@ -458,9 +453,18 @@ extension AVVideoIOUnit: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension AVVideoIOUnit: VideoDecoderDelegate {
|
||||
// MARK: VideoDecoderDelegate
|
||||
func sampleOutput(video sampleBuffer: CMSampleBuffer) {
|
||||
extension AVVideoIOUnit: VideoCodecDelegate {
|
||||
// MARK: VideoCodecDelegate
|
||||
func videoCodec(_ codec: VideoCodec, didSet formatDescription: CMFormatDescription?) {
|
||||
}
|
||||
|
||||
func videoCodec(_ codec: VideoCodec, didCompress sampleBuffer: CMSampleBuffer) {
|
||||
}
|
||||
|
||||
func videoCodec(_ codec: VideoCodec, didDecompress sampleBuffer: CMSampleBuffer) {
|
||||
drawable?.enqueue(sampleBuffer)
|
||||
}
|
||||
|
||||
func videoCodec(_ codec: VideoCodec, errorOccurred error: VideoCodec.Error) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -644,7 +644,7 @@ final class RTMPVideoMessage: RTMPMessage {
|
|||
}
|
||||
|
||||
private func enqueueSampleBuffer(_ stream: RTMPStream, type: RTMPChunkType) {
|
||||
let isBaseline = stream.mixer.videoIO.decoder.isBaseline
|
||||
let isBaseline = stream.mixer.videoIO.codec.isBaseline
|
||||
|
||||
// compositionTime -> SI24
|
||||
var compositionTime = isBaseline ? 0 : Int32(data: [0] + payload[2..<5]).bigEndian
|
||||
|
@ -678,7 +678,7 @@ final class RTMPVideoMessage: RTMPMessage {
|
|||
dataLength: length,
|
||||
flags: 0,
|
||||
blockBufferOut: &blockBuffer) == noErr else {
|
||||
stream.mixer.videoIO.decoder.needsSync.mutate { $0 = true }
|
||||
stream.mixer.videoIO.codec.needsSync.mutate { $0 = true }
|
||||
return
|
||||
}
|
||||
guard CMBlockBufferReplaceDataBytes(
|
||||
|
|
|
@ -59,7 +59,7 @@ extension RTMPMuxer: VideoCodecDelegate {
|
|||
delegate?.muxer(self, didOutputVideo: buffer, withTimestamp: 0)
|
||||
}
|
||||
|
||||
func videoCodec(_ codec: VideoCodec, didOutput sampleBuffer: CMSampleBuffer) {
|
||||
func videoCodec(_ codec: VideoCodec, didCompress sampleBuffer: CMSampleBuffer) {
|
||||
let keyframe: Bool = !sampleBuffer.isNotSync
|
||||
var compositionTime: Int32 = 0
|
||||
let presentationTimeStamp: CMTime = sampleBuffer.presentationTimeStamp
|
||||
|
@ -80,6 +80,9 @@ extension RTMPMuxer: VideoCodecDelegate {
|
|||
videoTimeStamp = decodeTimeStamp
|
||||
}
|
||||
|
||||
func videoCodec(_ codec: VideoCodec, didDecompress sampleBuffer: CMSampleBuffer) {
|
||||
}
|
||||
|
||||
func videoCodec(_ codec: VideoCodec, errorOccurred error: VideoCodec.Error) {
|
||||
delegate?.muxer(self, videoCodecErrorOccurred: error)
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ extension TSWriter: VideoCodecDelegate {
|
|||
videoConfig = AVCConfigurationRecord(data: avcC)
|
||||
}
|
||||
|
||||
public func videoCodec(_ codec: VideoCodec, didOutput sampleBuffer: CMSampleBuffer) {
|
||||
public func videoCodec(_ codec: VideoCodec, didCompress sampleBuffer: CMSampleBuffer) {
|
||||
guard let dataBuffer = sampleBuffer.dataBuffer else {
|
||||
return
|
||||
}
|
||||
|
@ -277,6 +277,9 @@ extension TSWriter: VideoCodecDelegate {
|
|||
)
|
||||
}
|
||||
|
||||
public func videoCodec(_ codec: VideoCodec, didDecompress sampleBuffer: CMSampleBuffer) {
|
||||
}
|
||||
|
||||
public func videoCodec(_ codec: VideoCodec, errorOccurred error: VideoCodec.Error) {
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue