本文主要介紹如何利用 VideoToolBox 實現(xiàn)對視頻的硬編/解碼冗恨。
先來簡單看下 音視頻的采集 咖为。
一缔莲、音視頻的采集
音視頻采集的核心流程:- 用到的視頻輸出的類是
AVCaptureVideoDataOutput
动壤,音頻輸出的類是AVCaptureAudioDataOutput
零截。 - 采集成功后的代理方法輸出的音視頻對象為
CMSampleBufferRef
類型的sampleBuffer
岖瑰。這里我們可以使用AVCaptureConnection
來判斷是音頻還是視頻杀怠。
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
if (connection == self.audioConnection) { //音頻
}else if (connection == self.videoConnection) { //視頻
}
}
采集的核心流程跟 AVFoundation 拍照/錄制視頻 和 AVFoundation 人臉識別 的采集流程基本一致排龄,大家可以了解下阀坏。
二如暖、視頻的編解碼
2.1 視頻的編碼
1.首先需要初始化編碼器,看代碼:
- (instancetype)initWithConfigure:(CQVideoCoderConfigure *)configure {
self = [super init];
if (self) {
self.configure = configure;
self.encodeQueue = dispatch_queue_create("h264 hard encode queue", DISPATCH_QUEUE_SERIAL);
self.callbackQueue = dispatch_queue_create("h264 hard encode callback queue", DISPATCH_QUEUE_SERIAL);
//1.創(chuàng)建編碼session
OSStatus status = VTCompressionSessionCreate(kCFAllocatorDefault, (int32_t)self.configure.width, (int32_t)self.configure.height, kCMVideoCodecType_H264, NULL, NULL, NULL, compressionOutputCallback, (__bridge void * _Nullable)(self), &_encodeSesion);
if (status != noErr) {
NSLog(@"VTCompressionSessionCreate error status: %d", (int)status);
return self;
}
//2忌堂、設(shè)置編碼器參數(shù)
//是否實時執(zhí)行
status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
NSLog(@"VTSessionSetProperty RealTime status: %d", (int)status);
//指定編碼比特流的配置文件和級別盒至。直播一般使用baseline,可減少由b幀減少帶來的延遲士修。
status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
NSLog(@"VTSessionSetProperty ProfileLevel status: %d", (int)status);
//設(shè)置比特率均值(比特率可以高于此枷遂。默認(rèn)比特率為零,表示視頻編碼器棋嘲。應(yīng)該確定壓縮數(shù)據(jù)的大小酒唉。)
//注意:比特率設(shè)置只在定時的時候有效
status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFNumberRef)@(self.configure.bitrate));
NSLog(@"VTSessionSetProperty AverageBitRate status: %d", (int)status);
//碼率限制
CFArrayRef limits = (__bridge CFArrayRef)@[@(self.configure.bitrate / 4),@(self.configure.bitrate * 4)];
status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_DataRateLimits,limits);
NSLog(@"VTSessionSetProperty DataRateLimits status: %d", (int)status);
//設(shè)置關(guān)鍵幀間隔
status = VTSessionSetProperty(self.encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFNumberRef)@(self.configure.fps * 2));
NSLog(@"VTSessionSetProperty MaxKeyFrameInterval status: %d", (int)status);
//設(shè)置預(yù)期的fps
CFNumberRef expectedFrameRate = (__bridge CFNumberRef)@(self.configure.fps);
status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, expectedFrameRate);
NSLog(@"VTSessionSetProperty ExpectedFrameRate status: %d", (int)status);
//3、準(zhǔn)備編碼
status = VTCompressionSessionPrepareToEncodeFrames(self.encodeSesion);
NSLog(@"VTSessionSetProperty: set PrepareToEncodeFrames return: %d", (int)status);
}
return self;
}
- 1沸移、
VTCompressionSessionCreate
:創(chuàng)建壓縮會話痪伦,并且添加了編碼成功后的回調(diào)函數(shù)compressionOutputCallback
侄榴。
參數(shù)1:會話的分配器。傳遞NULL
使用默認(rèn)分配器网沾。
參數(shù)2:幀的寬度癞蚕,以像素為單位。如果視頻編碼器不支持所提供的寬度和高度辉哥,系統(tǒng)可能會自動修改桦山。
參數(shù)3:幀的高度。
參數(shù)4:編碼類型醋旦。
參數(shù)5:編碼規(guī)范度苔。NULL
由videoToolbox
自己選擇。
參數(shù)6:源像素緩沖區(qū)屬性浑度,NULL
不讓videToolbox
創(chuàng)建寇窑,自己創(chuàng)建。
參數(shù)7: 壓縮數(shù)據(jù)分配器箩张。NULL
默認(rèn)的分配甩骏。
參數(shù)8:回調(diào)函數(shù)。異步調(diào)用先慷。
參數(shù)9:客戶端為輸出回調(diào)定義的引用值饮笛。這里傳的事我們自定義的編碼器,也就是self
论熙。
參數(shù)10: 要創(chuàng)建的編碼會話對象福青。 - 2、
VTSessionSetProperty
屬性配置脓诡。 - 3无午、
VTCompressionSessionPrepareToEncodeFrames
:準(zhǔn)備編碼。
2祝谚、進(jìn)行編碼宪迟,看代碼:
- (void)encoderSampleBuffers:(CMSampleBufferRef)sampleBuffer {
CFRetain(sampleBuffer);
dispatch_async(self.encodeQueue, ^{
CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);//幀數(shù)據(jù)
self->frameID++;
CMTime timeStamp = CMTimeMake(self->frameID, 1000);//該幀的時間戳
CMTime duration = kCMTimeInvalid;//持續(xù)時間
VTEncodeInfoFlags flags;
OSStatus status = VTCompressionSessionEncodeFrame(self.encodeSesion, imageBuffer, timeStamp, duration, NULL, NULL, &flags);//編碼
if (status != noErr) {
NSLog(@"VTCompressionSessionEncodeFrame error status: %d",(int)status);
}
CFRelease(sampleBuffer);
});
}
- 1、
CMSampleBufferGetImageBuffer
從采集到的視頻CMSampleBufferRef
中獲取CVImageBufferRef
交惯。 - 2次泽、
VTCompressionSessionEncodeFrame
壓縮編碼:
參數(shù)1:編碼會話encodeSesion
參數(shù)2:CVImageBuffer
對象,包含視頻幀數(shù)據(jù)
參數(shù)3:對應(yīng)該幀的時間戳席爽,每個示例的時間戳必須大于前一個意荤。
參數(shù)4:該演示幀的持續(xù)時間,沒有可填kCMTimeInvalid
參數(shù)5:編碼該幀的鍵/值對屬性信息只锻。注意玖像,某些會話屬性也可能在幀之間更改。這種變化對隨后編碼的幀有影響炬藤。
參數(shù)6:將要傳遞給回調(diào)函數(shù)的幀的引用值御铃。
參數(shù)7:VTEncodeInfoFlags
接收有關(guān)編碼操作的信息.
3碴里、編碼成功后回調(diào)處理:
// startCode 長度 4
const Byte startCode[] = "\x00\x00\x00\x01";
void compressionOutputCallback(void * CM_NULLABLE outputCallbackRefCon, void * CM_NULLABLE sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CM_NULLABLE CMSampleBufferRef sampleBuffer ) {
if (status != noErr) {
NSLog(@"compressionOutputCallback error status: %d", (int)status);
return;
}
if (!CMSampleBufferDataIsReady(sampleBuffer)) {
NSLog(@"CMSampleBufferDataIsReady is not ready");
return;
}
CQVideoEncoder *encoder = (__bridge CQVideoEncoder *)outputCallbackRefCon;
BOOL keyFrame = NO;
CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
keyFrame = !CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachmentsArray, 0), kCMSampleAttachmentKey_NotSync);
//是否為關(guān)鍵幀,并且有沒有獲取過sps 和 pps 數(shù)據(jù)上真。
if (keyFrame && !encoder->hasSpsPps) {
size_t spsSize, spsCount, ppsSize, ppsCount;
const uint8_t *spsData, *ppsData;
//獲取圖像原格式
CMFormatDescriptionRef formatDes = CMSampleBufferGetFormatDescription(sampleBuffer);
OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 0, &spsData, &spsSize, &spsCount, 0);
OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDes, 1, &ppsData, &ppsSize, &ppsCount, 0);
if (status1 == noErr & status2 == noErr) {//sps/pps獲取成功
NSLog(@"Get sps and pps successRб浮!睡互!");
//sps 和 pps 數(shù)據(jù)只需保存在H264文件開頭即可根竿。
encoder->hasSpsPps = true;
NSMutableData *spsDataM = [NSMutableData dataWithCapacity:4 + spsSize];
[spsDataM appendBytes:startCode length:4];
[spsDataM appendBytes:spsData length:spsSize];
NSMutableData *ppsDataM = [NSMutableData dataWithCapacity:4 + ppsSize];
[ppsDataM appendBytes:startCode length:4];
[ppsDataM appendBytes:ppsData length:ppsSize];
dispatch_async(encoder.encodeQueue, ^{
if ([encoder.delegate respondsToSelector:@selector(encodeCallbackWithSps:pps:)]) {
[encoder.delegate encodeCallbackWithSps:spsDataM pps:ppsDataM];
}
});
} else {
NSLog(@"Get sps and pps failed, spsStatus:%d, ppsStatus:%d", (int)status1, (int)status2);
}
}
//獲取NAL Unit數(shù)據(jù)
size_t lengthAtOffset, totalLength;
char *dataPoint;
//將數(shù)據(jù)復(fù)制到dataPoint
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);
if (error != kCMBlockBufferNoErr) {
NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);
return;
}
//循環(huán)獲取NAL Unit數(shù)據(jù)
size_t offet = 0;
//返回的NAL Unit數(shù)據(jù)前四個字節(jié)不是系統(tǒng)端的startCode(0001)
//而是大端模式的幀長度
const int lengthStartCode = 4;
const int lengthBigFrame = 4;
while (offet < totalLength - lengthBigFrame) {
//獲取NAL Unit數(shù)據(jù)長度
uint32_t lengthNALU = 0;
memcpy(&lengthNALU, dataPointerOut + offet, lengthBigFrame);
lengthNALU = CFSwapInt32BigToHost(lengthBigFrame);//大端轉(zhuǎn)系統(tǒng)端
//獲取到編碼好的視頻startCode + NAL Uint
NSMutableData *data = [NSMutableData dataWithCapacity:lengthStartCode + lengthNALU];
[data appendBytes:startCode length:lengthStartCode];
[data appendBytes:dataPointerOut + offet + lengthBigFrame length:lengthNALU];
dispatch_async(encoder.encodeQueue, ^{
if ([encoder.delegate respondsToSelector:@selector(encodeVideoCallback:)]) {
[encoder.delegate encodeVideoCallback:data];
}
});
offet += lengthStartCode + lengthNALU;
}
}
- 1、通過編碼成功后的
CMSampleBufferRef
獲取到當(dāng)前幀的相關(guān)屬性就珠,判斷是否是關(guān)鍵幀寇壳。編碼前后的視頻數(shù)據(jù)都是CMSampleBufferRef
類型。 - 2妻怎、是關(guān)鍵幀并且沒有設(shè)置過
sps壳炎、pps
。
?2.1逼侦、獲取圖像原格式和圖像的sps匿辩、pps
?2.2、將二進(jìn)制格式的sps榛丢、pps
拼接到NSData
中铲球,并且開頭加上startCode(00 00 00 01)
,NAL Unit
之間使用startCode(00 00 00 01)
進(jìn)行分割的晰赞。
?2.3稼病、將NSData
格式的sps、pps
回調(diào)出去掖鱼。 - 3然走、將數(shù)據(jù)復(fù)制到
dataPointerOut
?3.1、CMSampleBufferGetDataBuffer
:從CMSampleBufferRef
中獲取CMBlockBufferRef
锨用。
?3.2丰刊、CMBlockBufferGetDataPointer
:將數(shù)據(jù)復(fù)制到dataPointerOut
,獲取到數(shù)據(jù)的總長度增拥。 - 4、循環(huán)獲取
NAL Unit
數(shù)據(jù)寻歧。
?4.1掌栅、memcpy
:獲取NAL Unit
數(shù)據(jù)長度。
?4.2码泛、獲取到編碼好的視頻猾封,開頭加上startCode(00 00 00 01)
。
?4.3噪珊、將視頻數(shù)據(jù)回調(diào)出去晌缘。
2.2視頻的解碼
解析H264格式數(shù)據(jù):
- (void)decodeH264Data:(NSData *)frame {
dispatch_async(self.decodeQueue, ^{
uint8_t *frameNALU = (uint8_t *)frame.bytes;
uint32_t lengthFrame = (uint32_t)frame.length;
int type = (frameNALU[4] & 0x1F);
//0 01 00111 & 39
//0 00 11111 31
//0 00 00111 7
//NSLog(@"type: %hhu, %d", frame[4], type);
//將NAL Unit開始碼轉(zhuǎn)為4字節(jié)大端NAL Unit的長度信息齐莲。
uint32_t naluSize = lengthFrame - 4;
uint8_t *pNaluSize = (uint8_t *)(&naluSize);
frameNALU[0] = *(pNaluSize + 3);
frameNALU[1] = *(pNaluSize + 2);
frameNALU[2] = *(pNaluSize + 1);
frameNALU[3] = *(pNaluSize);
CVPixelBufferRef pixelBuffer = NULL;
switch (type) {
case 0x05://I幀(關(guān)鍵幀)
if ([self createDecoder]) {
pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];
}
break;
case 0x06://增強信息
break;
case 0x07://sps
self->_spsSize = naluSize;
self->_sps = malloc(self->_spsSize);
memcpy(self->_sps, &frameNALU[4], self->_spsSize);
break;
case 0x08://pps
self->_ppsSize = naluSize;
self->_pps = malloc(self->_ppsSize);
memcpy(self->_pps, &frameNALU[4], self->_ppsSize);
break;
default://其他幀(0x01到 0x05)
if ([self createDecoder]) {
pixelBuffer = [self decodeNALUFrame:frameNALU withFrameLength:lengthFrame];
}
break;
}
});
}
- 1、獲取幀的二進(jìn)制數(shù)據(jù)磷箕,這里的
NSData
數(shù)據(jù)就是我門上面編碼回調(diào)過來的編碼后的視頻數(shù)據(jù)选酗。 - 2、
int type = (frameNALU[4] & 0x1F);
:獲取該數(shù)據(jù)類型岳枷。type
為7是sps
, 8是pps
芒填。
?注意:因為前4個字節(jié)是NAL Unit
數(shù)據(jù)的分割碼也就是前面說的startCode(\x00\x00\x00\x01)
,所以這里取第5個字節(jié)的數(shù)據(jù)空繁,frameNALU[4]
也就是NAL Unit
的header
殿衰。
?例如:
0 01 00111(frameNALU[4])
&0 00 11111(0x1F)
=0 00 00111
轉(zhuǎn)為十進(jìn)制就是7
代表sps
。
?我們將header
分為三部分0-01-00111
:
?第一部分占一位0
代表 禁止位盛泡,用以檢查傳輸過程中是否發(fā)生錯誤闷祥,0表示正常,1表示違反語法傲诵。
?第二部分占兩位01
用來表示當(dāng)前NAL單元的優(yōu)先級蜀踏。非0值表示參考字段/幀/圖片數(shù)據(jù),其他不那么重要的數(shù)據(jù)則為0掰吕。對于非0值果覆,值越大表示NAL Unit
重要性越高。
?第三部分占五位00111
指定NAL Unit
類型殖熟,這就是為什么按位與運算用0 00 11111(0x1F)
局待。 - 3、當(dāng)
type
為0x05
時代表時I
幀(關(guān)鍵幀)這時我們需要先創(chuàng)建解碼器菱属。0x07
時創(chuàng)建sps
數(shù)據(jù)钳榨。0x08
時創(chuàng)建pps
數(shù)據(jù)。
創(chuàng)建解碼器:
- (BOOL)createDecoder {
if (self.decodeSesion) return YES;
const uint8_t * const parameterSetPointers[2] = {_sps, _pps};
const size_t parameterSetSize[2] = {_spsSize, _ppsSize};
int lengthStartCode = 4;
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSize, lengthStartCode, &_decoderDesc);
if (status != noErr) {
NSLog(@"CMVideoFormatDescriptionCreateFromH264ParameterSets error status: %d", (int)status);
return NO;
}
NSDictionary *decoderAttachments =
@{
(id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //攝像頭的輸出數(shù)據(jù)格式
(id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:self.configure.width],
(id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:self.configure.height],
(id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:YES]
};
//解碼回調(diào)設(shè)置
VTDecompressionOutputCallbackRecord decompressionCallback;
decompressionCallback.decompressionOutputCallback = decoderVideoOutputCallback;
decompressionCallback.decompressionOutputRefCon = (__bridge void * _Nullable)self;
VTDecompressionSessionCreate(kCFAllocatorDefault, _decoderDesc, NULL, (__bridge CFDictionaryRef _Nullable)decoderAttachments, &decompressionCallback, &_decodeSesion);
if (status != noErr) {
NSLog(@"VTDecompressionSessionCreate error status: %d", (int)status);
return NO;
}
//實時編碼
status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue);
if (status != noErr) {
NSLog(@"VTSessionSetProperty RealTime error status:%d", (int)status);
}
return YES;
}
- 1纽门、
CMVideoFormatDescriptionCreateFromH264ParameterSets
設(shè)置解碼參數(shù):
參數(shù)1:kCFAllocatorDefault
使用默認(rèn)的內(nèi)存分配
參數(shù)2: 參數(shù)個數(shù)
參數(shù)3: 參數(shù)集指針
參數(shù)4: 參數(shù)集大小
參數(shù)5:startCode
的長度 4
參數(shù)6: 解碼格式器描述對象 - 2薛耻、
VTDecompressionOutputCallbackRecord
解碼回調(diào)設(shè)置。 - 3赏陵、
VTDecompressionSessionCreate
創(chuàng)建解碼器饼齿。
參數(shù)1:kCFAllocatorDefault
使用默認(rèn)的內(nèi)存分配。
參數(shù)2: 解碼格式器描述對象蝙搔。
參數(shù)3: 指定必須使用的特定視頻解碼器缕溉。NULL
讓video toolbox
選擇解碼器。
參數(shù)4: 描述源像素緩沖區(qū)的要求,NULL
無要求吃型。
參數(shù)5: 已解壓縮的幀調(diào)用的回調(diào)函數(shù)证鸥。
參數(shù)6: 指向一個變量以接收新的解壓會話。 - 4、
VTSessionSetProperty
設(shè)置解碼會話屬性枉层。
解碼:
- (CVPixelBufferRef)decodeNALUFrame:(uint8_t *)frameNALU withFrameLength:(uint32_t)lengthFrame {
CVPixelBufferRef outputPixelBuffer = NULL;
CMBlockBufferRef blockBufferOut = NULL;
CMBlockBufferFlags flag0 = 0;
//1.
OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frameNALU, lengthFrame, kCFAllocatorNull, NULL, 0, lengthFrame, flag0, &blockBufferOut);
if (status != kCMBlockBufferNoErr) {
NSLog(@"CMBlockBufferCreateWithMemoryBlock error status:%d", (int)status);
return outputPixelBuffer;
}
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {lengthFrame};
//2.創(chuàng)建sampleBuffer
status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBufferOut, _decoderDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
if (status != noErr || !sampleBuffer) {
NSLog(@"CMSampleBufferCreateReady error status:%d", (int)status);
CFRelease(blockBufferOut);
return outputPixelBuffer;
}
//解碼
VTDecodeFrameFlags decodeFrameFlags = kVTDecodeFrame_1xRealTimePlayback;
VTDecodeInfoFlags decodeInfoFlags = kVTDecodeInfo_Asynchronous; //異步解碼
status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, decodeFrameFlags, &outputPixelBuffer, &decodeInfoFlags);
if (status == kVTInvalidSessionErr) {
NSLog(@"VTDecompressionSessionDecodeFrame InvalidSessionErr status:%d", (int)status);
} else if (status == kVTVideoDecoderBadDataErr) {
NSLog(@"VTDecompressionSessionDecodeFrame BadData status:%d", (int)status);
} else if (status != noErr) {
NSLog(@"VTDecompressionSessionDecodeFrame status:%d", (int)status);
}
CFRelease(sampleBuffer);
CFRelease(blockBuffer);
return outputPixelBuffer;
}
- 1锉试、
CMBlockBufferCreateWithMemoryBlock
創(chuàng)建CMBlockBufferRef
:
參數(shù)1:kCFAllocatorDefault
使用默認(rèn)內(nèi)存分配
參數(shù)2: 幀的內(nèi)存塊窘奏,這里就用frameNALU
參數(shù)3: 幀大小
參數(shù)4: 管理 內(nèi)存塊 的分配器熄云,參數(shù)2為NULL
時用于分配內(nèi)存塊席揽,參數(shù)2不為NULL
時用于釋放內(nèi)存塊,kCFAllocatorNull
不需要釋放內(nèi)存塊矩欠。
參數(shù)5: 如果非空财剖,則用于內(nèi)存塊的分配和釋放(參數(shù)4blockAllocator
會被忽略)。如果 參數(shù)2內(nèi)存塊 為NULL
癌淮,則其Allocate()
必須為非NULL
躺坟。如果分配成功,將在分配內(nèi)存塊時調(diào)用一次Allocate
乳蓄。釋放CMBlockBuffer
時將調(diào)用Free()
咪橙。
參數(shù)6: 數(shù)據(jù)偏移量
參數(shù)7: 數(shù)據(jù)長度
參數(shù)8: 功能和控制標(biāo)志
參數(shù)9: 接收新創(chuàng)建的CMBlockBuffer
地址,不能為空虚倒。 - 2美侦、
CMSampleBufferCreateReady
創(chuàng)建CMSampleBufferRef
:
參數(shù)1:kCFAllocatorDefault
使用默認(rèn)內(nèi)存分配
參數(shù)2:需要編碼的數(shù)據(jù)blockBufferOut
.不能為NULL
參數(shù)3:視頻輸出格式
參數(shù)4:CMSampleBuffer
個數(shù).
參數(shù)5: 必須為0、1 或numSamples
參數(shù)6: 數(shù)組.為空
參數(shù)7: 必須為0魂奥、1 或numSamples
, 默認(rèn)為1
參數(shù)8: 幀大小的數(shù)組菠剩。
參數(shù)9: 新的CMSampleBufferRef
對象 - 3、
VTDecompressionSessionDecodeFrame
解碼:
參數(shù)1: 解碼會話對象耻煤。
參數(shù)2:CMSampleBufferRef
對象具壮,包含一個或多個視頻幀。
參數(shù)3: 解碼標(biāo)志
參數(shù)4: 解碼后數(shù)據(jù)CVPixelBufferRef
哈蝇。
參數(shù)5: 同步還是異步解碼棺妓。
解碼成功后回調(diào)函數(shù):
void decoderVideoOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,
void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration ) {
if (status != noErr) {
NSLog(@"decoderVideoOutputCallback error status:%d", (int)status);
return;
}
CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
*outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
CQVideoDecoder *decoder = (__bridge CQVideoDecoder *)decompressionOutputRefCon;
dispatch_async(decoder.callbackQueue, ^{
if ([decoder.delegate respondsToSelector:@selector(videoDecodeCallback:)]) {
[decoder.delegate videoDecodeCallback:imageBuffer];
}
//釋放數(shù)據(jù)
CVPixelBufferRelease(imageBuffer);
});
}
1、回調(diào)函數(shù)的參數(shù):
參數(shù)1:回調(diào)的引用值炮赦。
參數(shù)2:幀的引用值怜跑。
參數(shù)3:壓縮失敗/成功的狀態(tài)碼。
參數(shù)4:如果設(shè)置了kVTDecodeInfo_Asynchronous
表示異步解碼吠勘,
如果設(shè)置了kVTDecodeInfo_FrameDropped
可以丟幀性芬,
如果設(shè)置了kVTDecodeInfo_ImageBufferModifiable
可以安全地修改imageBuffer
(實際圖像的緩沖).
參數(shù)5:實際圖像的緩沖。如果未設(shè)置kVTDecodeInfo_ImageBufferModifiable
標(biāo)志看幼,則視頻解壓縮器可能仍在引用此回調(diào)中返回的imageBuffer
批旺,此時修改返回的imageBuffer
是不安全的。
參數(shù)6:幀的時間戳诵姜。
參數(shù)7:幀的持續(xù)時間。2、將指針
*outputPixelBuffer
指向?qū)嶋H圖像緩沖區(qū)imageBuffer
3棚唆、將圖像緩沖區(qū)
imageBuffer
回調(diào)出去用來展示暇赤。