基于以上比拟,目前大部分业务场景的编解码策略是:手机端采取硬编码天生视频文件发送给做事器,做事器进行软编转码为支持更多的格式或码率的视频,再分发给不雅观看端。考虑到有些设备不支持硬编解码,常日须要软编解码做兜底。
Annex-B 和 AVCC/HVCC我们常见的视频编码格式H.264/AVC是由国际标准组织机构(ISO)下属的运动图象专家组(MPEG)和国际电传视讯同盟远程通信标准化组织(ITU-T)开拓的系列编码标准,之后又相继推出了H.265/HEVC和H.266/VVC,iOS目前尚不支持H.266,本文以H.264和H.265格式先容Video Toolbox的功能。
为了得到高的视频压缩比和网络亲和性(即适用于各种传输网络),将H.264系统架构分为了视频编码层VCL(Video Coding Layer)和网络抽象层(或网络适配层)NAL(Network Abstraction Layer),后续H.265、H.266也沿用了这一分层架构。

H.264和H.265的NAL Header构造:
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
C++程序员必看,捉住音视频开拓的大浪潮!
冲击年薪60万 音视频开拓根本知识和资料包
NAL Header包含当前NALU的类型(nal_unit_type)信息,H.264的NAL Header为一个字节,H.265为两个字节,以是获取类型的方法不同: H.264为:int type = (frame[4] & 0x1F),5代表I帧,7、8分别代表SPS、PPS。 H.265为:int type = int type = (code & 0x7E)>>1,19代表I帧,32、33、34分别代表VPS、SPS、PPS。
由于NALU长度不一,要写到一个文件中须要标识符来分割码流以区分独立的NALU,办理这一问题的两种方案,产生了两种不同的码流格式:
Annex-B:在每个NALU前加上0 0 0 1或者0 0 1,称作start code(起始码),如果原始码流中含有起始码,则起用防竞争字节:如将0 0 0 1处理为0 0 0 3 1。AVCC/HVCC:在NALU前面加上几个字节,用于表示全体NALU的长度(大端序,读取时调用CFSwapInt32BigToHost()转为小端),在读取的时候先将长度读取出来,再读取全体NALU。除了NALU前添加的字节表示的含义不同之外,AVCC/HVCC和Annex-B在处理序列参数集SPS(Sequence Parameter Set)、图像参数集PPS(Picture Parameter Set)和视频参数集VPS(Video Parameter Set)(H.265才有)上也不同(不是必须要对参数集的详细内容做详细理解,我们只要知道这些参数集是解码所必需的数据,在解码前须要拿到这些数据即可)。
H.264可以通过CMVideoFormatDescriptionGetH264ParameterSetAtIndex获取,0、1分别对应SPS和PPS。H.265可以通过CMVideoFormatDescriptionGetHEVCParameterSetAtIndex获取,0、1、2分别对应VPS、SPS和PPS。后面会有完全代码示例。
Annex-B和AVCC/HVCC对参数集的不同处理办法:
Annex-B:参数集当成普通的NALU处理,每个I帧前都须要添加(VPS/)SPS/PPS。AVCC/HVCC:参数集分外处理,放在头部被称为extradata的数据中。为什么分歧一为一种格式?
我们知道视频分为本地视频文件和网络直播流,对付直播流,AVCC/HVCC 格式只在头部添加了参数集,如果是中途进入不雅观看会获取不到参数集,也就无法初始化解码器进行解码,而 Annex-B 在每个I帧前都添加了参数集,可以从最近的I帧初始化解码器解码不雅观看。而 AVCC/HVCC 只在头部添加参数集很适宜用于本地文件,解码本地文件只须要获取一次参数集进行解码就能播放,以是不须要像Annex-B一样重复地存储多份参数集。
为什么要理解这两种格式?
由于Video Toolbox编码和解码只支持 AVCC/HVCC 的码流格式,而Android的 MediaCodec 只支持 Annex-B 的码流格式。因此在流媒体场景下,对付iOS开拓而言,须要在采集编码之后转为Annex-B格式再进行推流,拉流解码时则须要转为AVCC/HVCC格式才能用Video Toolbox进行解码播放。
如果在编码后想直接存储为本地文件,可以利用AVFoundation框架中的AVAssetWriter。
Video Toolbox框架概览Video Toolbox 最早是在OS X上运行,现在看苹果的官方文档的解释,Video Toolbox 的系统支持为iOS 6.0+,实际上苹果在WWDC2014大会上才开放了Video Toolbox框架,即iOS 8.0往后开拓者才可以利用。
官方文档先容:Video Toolbox是一个底层框架,供应对硬件编码器和解码器的直接访问。它供应了视频编码和解码做事,以及存储在CoreVideo像素缓冲区的光栅图像格式之间的转换。这些做事以session(编码、解码和像素转换)的形式供应,并以Core Foundation (CF)类型供应。不须要直接访问硬件编码器和解码器的运用程序不应该直策应用VideoToolbox。
Video Toolbox框架目前分为了编解码和像素转换两个模块,iOS9.0之后支持了多通道编解码,本文要利用的是Compression的前两个类:VTCompressionSession(编码)和VTDecompressionSession(解码)。
Video Toolbox的输入和输出
在利用Video Toolbox前我们先理解Video Toolbox的输入和输出。iOS开拓常日利用AVFoundation框架进行视频录制,AVFoundation框架流利的数据类型为CMSampleBuffer,利用AVCapture模块录制视频的回调方法为- (void)captureOutput:(AVCaptureOutput )output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection )connection,编码须要的输入即原始视频帧CVImageBuffer(或CVPixelBuffer)就包裹在CMSampleBuffer中,经由编码后输出的仍为CMSampleBuffer类型,个中的CMBlockBuffer为编码后数据。相反,解码以CMSampleBuffer类型的CMSampleBuffer作为输入,解码完输出CVImageBuffer(CVPixelBuffer),CMSampleBuffer可以通过AVAssetReader读取文件得到,或者从流媒体中读取NSData利用CMSampleBufferCreateReady手动创建。
编码
VTCompressionSession的文档先容了利用VTCompressionSession进行视频硬编码的事情流程:
1. Create a compression session using VTCompressionSessionCreate.
// 利用VTCompressionSessionCreate创建一个编码会话。
2. Optionally, configure the session with your desired Compression Properties by calling VTSessionSetProperty or VTSessionSetProperties.
// 可选地,通过调用VTSessionSetProperty或VTSessionSetProperties来配置编码器属性。
3. Encode video frames using VTCompressionSessionEncodeFrame and receive the compressed video frames in the session’s VTCompressionOutputCallback.
// 利用VTCompressionSessionEncodeFrame编码视频帧,并在会话的VTCompressionOutputCallback中吸收编码后的视频帧。
4. To force the completion of some or all pending frames, call VTCompressionSessionCompleteFrames.
// 要逼迫完成部分或所有挂起的帧,调用VTCompressionSessionCompleteFrames。
5. When you finish with the compression session, call VTCompressionSessionInvalidate to invalidate it and CFRelease to free its memory.
// 当您完成编码会话时,调用VTCompressionSessionInvalidate来使其无效,并调用CFRelease来开释它的内存。
结合实际的编码和后续处理,逐步解析干系API:
1. 创建编码会话VTCompressionSessionRef/ 参数解析
allocator: session的内存分配器,传NULL表示默认的分配器。
width,height: 指定编码器的像素的宽高,与捕捉到的视频分辨率保持同等,如果视频编码器不能支持供应的宽度和高度,它可能会改变它们。
codecType: 编码类型,如H.264:kCMVideoCodecType_H264、H.265:kCMVideoCodecType_HEVC,最好先调用VTIsHardwareDecodeSupported(codecType) 判断是否支持该编码类型。
encoderSpecification: 指定必须利用特定的编码器,传NULL的话Video Toolbox会自己选择一个编码器。
sourceImageBufferAttributes: 原始视频数据须要的属性,紧张用于创建CVPixelBufferPool,如果不须要Video Toolbox创建,可以传NULL,但是利用自己创建的CVPixelBufferPool会增加须要拷贝图像数据的几率。
compressedDataAllocator: 编码数据的内存分配器,传NULL表示利用默认的分配器.
outputCallback: 吸收编码数据的回调,这个回调可以选择利用同步或异步办法吸收。如果用同步则与VTCompressionSessionEncodeFrame函数线程保持同等,如果用异步会新建一条线程吸收。该参数也可传NULL不过当且仅当我们利用VTCompressionSessionEncodeFrameWithOutputHandler函数作编码时。
outputCallbackRefCon: 可以传入用户自定义数据,紧张用于回调函数与主类之间的交互。
compressionSessionOut: 传入要创建的session的内存地址,把稳,session不能为NULL。
/
VTCompressionSessionCreate(
CM_NULLABLE CFAllocatorRef allocator,
int32_t width,
int32_t height,
CMVideoCodecType codecType,
CM_NULLABLE CFDictionaryRef encoderSpecification,
CM_NULLABLE CFDictionaryRef sourceImageBufferAttributes,
CM_NULLABLE CFAllocatorRef compressedDataAllocator,
CM_NULLABLE VTCompressionOutputCallback outputCallback,
void CM_NULLABLE outputCallbackRefCon,
CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef CM_NONNULL compressionSessionOut) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
个中,视频解码输出的回调参数outputCallback类型为typedef void (VTCompressionOutputCallback)(void outputCallbackRefCon, void sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer),"sampleBuffer"即为编码后的数据。
2. 配置编码器属性/
session: 会话
propertyKey: 属性名称
propertyValue: 属性值,设置为NULL将规复默认值。
/
VT_EXPORT OSStatus
VTSessionSetProperty(
CM_NONNULL VTSessionRef session,
CM_NONNULL CFStringRef propertyKey,
CM_NULLABLE CFTypeRef propertyValue ) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
WWDC2022之后即iOS16.0之后新加了3个key,加上之前支持的53个,统共支持56个不同的key值配置编码参数。下表列举了常用的一些key值,大略感想熏染一下可以配置的内容:
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
音视频开拓根本知识和资料包
3. 准备编码// session: 编码会话
VT_EXPORT OSStatus
VTCompressionSessionPrepareToEncodeFrames( CM_NONNULL VTCompressionSessionRef session ) API_AVAILABLE(macosx(10.9), ios(8.0), tvos(10.2));
4. 开始编码/
session: 编码会话
imageBuffer: 一个CVImageBuffer,包含一个要压缩的视频帧,引用计数不能为0。
presentationTimeStamp: PTS,此帧的显示韶光戳,会添加到CMSampleBuffer中。通报给session的每个PTS必须大于前一帧的。
duration: 此帧的显示持续韶光,会添加到CMSampleBuffer中。如果没有持续韶光信息,则通报kCMTimeInvalid。
frameProperties: 指定此帧编码的属性的键/值对。把稳,一些会话属性也可能在帧之间改变,这样的变革会对随后编码的帧产生影响。
sourceFrameRefCon: 帧的参考值,它将被通报给输出回调函数。
infoFlagsOut: 指向VTEncodeFlags指针,用于吸收有关编码操作的信息。如果编码正在进行,可以设置kVTEncodeInfo_Asynchronous,如果帧被删除;可以设置kVTEncodeInfo_FrameDropped;如果不想吸收次信息可以传NULL。
/
VT_EXPORT OSStatus
VTCompressionSessionEncodeFrame(
CM_NONNULL VTCompressionSessionRef session,
CM_NONNULL CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime duration, // may be kCMTimeInvalid
CM_NULLABLE CFDictionaryRef frameProperties,
void CM_NULLABLE sourceFrameRefcon,
VTEncodeInfoFlags CM_NULLABLE infoFlagsOut ) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
5. 处理编码后的数据在回调outputCallback中处理编码后的数据,即CMSamplaBuffer。要将编码后的CMSamplaBuffer写入mp4、MOV等容器文件,可以利用AVFoundation框架AVAssetWriter。
我们着重讲解流媒体场景下如何将AVCC/HVCC转为Annex-B格式:
从关键帧中获取extradata,获取参数集// 判断是否是关键帧
bool isKeyFrame = !CFDictionaryContainsKey((CFDictionaryRef)CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0), (const void )kCMSampleAttachmentKey_NotSync);
CMSampleBufferGetSampleAttachmentsArray用于获取样本的附加信息数据,苹果文档对kCMSampleAttachmentKey_NotSync的阐明为:一个同步样本,也被称为关键帧或IDR(瞬时解码刷新),可以在不须要任何之前的样本被解码的情形下被解码。同步样本之后的样本也不须要在同步样本之前的样本被解码。以是样本的附加信息字典不包含该key即为I帧。
// 获取编码类型
CMVideoCodecType codecType = CMVideoFormatDescriptionGetCodecType(CMSampleBufferGetFormatDescription(sampleBuffer));
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
if (codecType == kCMVideoCodecType_H264) {
// H.264/AVC 取出formatDescription中index 0、1对应的SPS、PPS
size_t sparameterSetSize, sparameterSetCount, pparameterSetSize, pparameterSetCount;
const uint8_t sparameterSet, pparameterSet;
OSStatus statusCode_sps = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0);
if (statusCode_sps == noErr) { // SPS
NSData sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
}
OSStatus statusCode_pps = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0);
if (statusCode_pps == noErr) { // PPS
NSData pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
}
} else if (codecType == kCMVideoCodecType_HEVC) {
// H.265/HEVC 取出formatDescription中index 0、1、2对应的VPS、SPS、PPS
size_t vparameterSetSize, vparameterSetCount, sparameterSetSize, sparameterSetCount, pparameterSetSize, pparameterSetCount;
const uint8_t vparameterSet, sparameterSet, pparameterSet;
if (@available(iOS 11.0, )) { // H.265/HEVC 哀求iOS11以上
OSStatus statusCode_vps = CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 0, &vparameterSet, &vparameterSetSize, &vparameterSetCount, 0);
if (statusCode_vps == noErr) { // VPS
NSData vps = [NSData dataWithBytes:vparameterSet length:vparameterSetSize];
}
OSStatus statusCode_sps = CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 1, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0);
if (statusCode_sps == noErr) { // SPS
NSData sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
}
OSStatus statusCode_pps = CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 2, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0);
if (statusCode_pps == noErr) { // PPS
NSData pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
}
}
2.将参数集分别组装成单个NALU
// 添加start code(后续都以H.264为例)
NSMutableData annexBData = [NSMutableData new];
uint8_t startcode[] = {0x00, 0x00, 0x00, 0x01};
[annexBData appendBytes:nalPartition length:4];
[annexBData appendData:sps];
[annexBData appendBytes:nalPartition length:4];
[annexBData appendData:pps];
3.将AVCC/HVCC格式中表示长度的4字节更换为start code
// 获取编码数据。这里的数据是 AVCC/HVCC 格式的。
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length, totalLength;
char dataPointer;
OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer);
if (statusCodeRet == noErr) {
size_t bufferOffset = 0;
static const int NALULengthHeaderLength = 4;
// 拷贝编码数据。
while (bufferOffset < totalLength - NALULengthHeaderLength) {
// 通过 length 字段获取当前这个 NALU 的长度。
uint32_t NALUnitLength = 0;
memcpy(&NALUnitLength, dataPointer + bufferOffset, NALULengthHeaderLength);
NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
// 添加start code
[annexBData appendData:[NSData dataWithBytes:startcode length:4]];
[annexBData appendData:[NSData dataWithBytes:(dataPointer + bufferOffset + NALULengthHeaderLength) length:NALUnitLength]];
bufferOffset += NALULengthHeaderLength + NALUnitLength;
}
}
之后就可以进行上传推流或者直接将H.264/H.265写入文件了。
6. 结束编码// 逼迫完成部分或所有挂起的帧
VTCompressionSessionCompleteFrames(_compressionSession, kCMTimeInvalid);
// 销毁编码器
VTCompressionSessionInvalidate(_compressionSession);
// 开释内存
CFRelease(_compressionSession);
按是否须要转化格式来分,iOS硬编码流程图如下:
解码
iOS视频硬解码是个从CMSampleBuffer中获取视频帧CVImageBuffer和用于播放所必须的显示韶光戳presentationTimeStamp和显示持续韶光presentationDuration的过程,Video Toolbox用于解码的类是VTDecompressionSession,文档也先容了利用VTDecompressionSession进行视频硬解码的事情流程:
1. Create a decompression session by calling VTDecompressionSessionCreate.
// 调用VTDecompressionSessionCreate创建一个解码会话
2. Optionally, configure the session with your desired Decompression Properties by calling VTSessionSetProperty or VTSessionSetProperties.
// 可选地,通过调用VTSessionSetProperty或VTSessionSetProperties来配置解码器属性。
3. Decode video frames using VTDecompressionSessionDecodeFrame.
// 解码视频帧利用VTDecompressionSessionDecodeFrame,并在解码会话的VTDecompressionOutputCallbackRecord回调中处理解码后的视频帧、显示韶光戳、显示持续韶光等信息。
4. When you finish with the decompression session, call VTDecompressionSessionInvalidate to tear it down, and call CFRelease to free its memory.
// 完成解码会话时,调用VTDecompressionSessionInvalidate来删除它,并调用CFRelease来开释它的内存。
结合实际的解码和后续处理,逐步解析干系API:
1. 创建解码会话VTDecompressionSessionRef/参数解析
allocator: session的内存分配器,传NULL表示默认的分配器。
videoFormatDescription: 源视频帧的描述信息。
videoDecoderSpecification: 指定必须利用的特定视频解码器。传NULL则Video Toolbox会自动选择一个解码器。
destinationImageBufferAttributes: 目标图像的属性哀求。传NULL则不作哀求。
outputCallback: 解码的回调函数。只能在调用VTDecompressionSessionDecodeFrameWithOutputHandler解码帧时通报NULL。
decompressionSessionOut: 指向一个变量以吸收新的解码会话。
/
VT_EXPORT OSStatus
VTDecompressionSessionCreate(
CM_NULLABLE CFAllocatorRef allocator,
CM_NONNULL CMVideoFormatDescriptionRef videoFormatDescription,
CM_NULLABLE CFDictionaryRef videoDecoderSpecification,
CM_NULLABLE CFDictionaryRef destinationImageBufferAttributes,
const VTDecompressionOutputCallbackRecord CM_NULLABLE outputCallback,
CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTDecompressionSessionRef CM_NONNULL decompressionSessionOut) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
个中destinationImageBufferAttributes可以设置图像显示的分辨率(kCVPixelBufferWidthKey/kCVPixelBufferHeightKey)、像素格式(kCVPixelBufferPixelFormatTypeKey)、是兼容OpenGL(kCVPixelBufferOpenGLCompatibilityKey)等等,例如:
NSDictionary destinationImageBufferAttributes = @{
(id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange],
(id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:_config.width],
(id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_config.height],
(id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]
};
而videoFormatDescription须要分情形处理: 如果是通过AVAssetReader或者其他办法可以直接获取到CMSampleBuffer,我们可以直接调用CMSampleBufferGetFormatDescription(sampleBuffer)获取。 如果是从流媒体读取等无法直接获取CMSampleBuffer的情形,我们须要从Annex-B格式转为AVCC/HVCC,并且手动创建videoFormatDescription和CMSampleBuffer。
格式转换过程与编码恰好相反:将start code 转为4字节的NALU长度;判断数据类型,如果是参数集则存储用于初始化解码器,如果是帧数据则进行解码。
// 从流媒体读取NSData
NSData h264Data = ...;
uint8_t nalu = (uint8_t )h264Data.bytes;
// 获取NALU类型,以H.264为例
int type = (naluData[4] & 0x1F);
// 将start code 转为4字节的NALU长度
uint32_t naluSize = frameSize - 4;
uint8_t pNaluSize = (uint8_t )(&naluSize);
naluData[0] = (pNaluSize + 3);
naluData[1] = (pNaluSize + 2);
naluData[2] = (pNaluSize + 1);
naluData[3] = (pNaluSize);
// 处理不同类型数据
switch (type) {
case 0x05:
// I帧,去解码(解码前先确保解码器会话存在,否则就创建)
break;
case 0x06:
// SEI信息,不处理,H.265也有一些不用处理的信息,详情可以去理解一下H.265的type表
break;
case 0x07:
// sps
_spsSize = naluSize;
_sps = malloc(_spsSize);
// 从下标4(也便是第五个元素)开始复制数据
memcpy(_sps, &naluData[4], _spsSize);
break;
case 0x08:
// pps
_ppsSize = naluSize;
_pps = malloc(_ppsSize);
// 从下标4(也便是第五个元素)开始复制数据
memcpy(_pps, &naluData[4], _ppsSize);
break;
default:
// 其他帧(1-5),去解码(解码前先确保解码器会话存在,否则就创建)
break;
}
利用参数集创建CMVideoFormatDescriptionRef:
CMVideoFormatDescriptionRef videoDesc;
const uint8_t const parameterSetPointers[2] = {_sps, _pps};
const size_t parameterSetSizes[2] = {_spsSize, _ppsSize};
int naluHeaderLen = 4; // 大端模式起始位长度
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &videoDesc);
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
C++程序员必看,捉住音视频开拓的大浪潮!
冲击年薪60万
/
session: 会话
propertyKey: 属性名称
propertyValue: 属性值,设置为NULL将规复默认值。
/
VT_EXPORT OSStatus
VTSessionSetProperty(
CM_NONNULL VTSessionRef session,
CM_NONNULL CFStringRef propertyKey,
CM_NULLABLE CFTypeRef propertyValue ) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
苹果官方文档列举了可配置的key目前有24个,不再赘述,可查阅Decompression Properties。
3. 开始解码/
session: 解码器会话
sampleBuffer: 包含一个或多个视频帧的CMSampleBuffer工具
decodeFlags: kVTDecodeFrame EnableAsynchronousDecompression位表示视频解码器是否可以异步解压缩帧。kVTDecodeFrame EnableTemporalProcessing位指示解码器是否可以延迟对输出回调的调用,以便以韶光(显示)顺序进行处理。如果这两个标志都被打消,解压完成,输出回调函数将在VTDecompressionSessionDecodeFrame返回之前被调用。如果设置了个中一个标志,VTDecompressionSessionDecodeFrame可能会在调用输出回调函数之前返回。
sourceFrameRefCon: 解码标识。如果sampleBuffer包含多个帧,输出回调函数将利用这个sourceFrameRefCon值多次调用。
infoFlagsOut: 指向VTEncodeInfoFlags指针,用于吸收有关解码操作的信息。如果解码正在进行,可以设置kVTDecodeInfo_Asynchronous,如果帧被删除;可以设置kVTDecodeInfo_FrameDropped;如果不想吸收次信息可以传NUL。
/
VTDecompressionSessionDecodeFrame(
CM_NONNULL VTDecompressionSessionRef session,
CM_NONNULL CMSampleBufferRef sampleBuffer,
VTDecodeFrameFlags decodeFlags, // bit 0 is enableAsynchronousDecompression
void CM_NULLABLE sourceFrameRefCon,
VTDecodeInfoFlags CM_NULLABLE infoFlagsOut) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
个中的sampleBuffer如果须要从NSData中读取,我们须要利用视频帧的数据创建CMBlockBuffer,再结合参数集信息创建CMSampleBuffer:
CVPixelBufferRef outputPixelBuffer = NULL;
CMBlockBufferRef blockBuffer = NULL;
CMBlockBufferFlags flag0 = 0;
// 创建blockBuffer
OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {frameSize};
// 创建sampleBuffer
status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _videoDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
4. 处理解码后的数据在解码的回调videoDecoderCallBack中我们可以获取图像和显示韶光戳、显示持续韶光,结合OpenGL、Core Imge进行显示,不再深入先容。
5. 结束解码// 销毁解码器
VTDecompressionSessionInvalidate(self.decodeSession);
// 开释内存
CFRelease(self.decodeSession);
iOS不同场景的硬解码流程图如下
把稳事变前后台切换会导致编解码出错,须要重新创建会话。有些视频流虽然是AVCC格式,但NALU size的大小是3个字节,须要转为4字节格式。切换分辨率时须要拿到新的参数集,重启解码器。编码后的视频帧之间存在参考关系,为避免遗漏末了几帧,须要在解码完末了一帧数据后调用VTDecompressionSessionWaitForAsynchronousFrames,该接口会等待所有未输出的视频帧输出结束后再返回。