当前位置:蜗牛素材网>综合资讯>图文>正文

ios更改视频编码:iOS,VideoTool

人气:275 ℃/2023-09-25 16:52:36

引言

视频编解码是音视频技术中重要的一部分,苹果在WWDC2014开发者大会开放了支持视频硬编解码功能的Video Toolbox框架,本文旨在介绍该框架的基础知识、使用细节和常见问题等内容。

Video Toolbox基础软编解码和硬编解码

视频编码是为了压缩视频数据,降低视频传输或存储开销,视频解码则是为了播放编码后的视频进行的逆向操作。视频的编解码分为软编解码和硬编解码:

基于以上对比,目前大部分业务场景的编解码策略是:手机端采用硬编码生成视频文件发送给服务器,服务器进行软编转码为支持更多的格式或码率的视频,再分发给观看端。考虑到有些设备不支持硬编解码,通常需要软编解码做兜底。

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也沿用了这一分层架构。

  • 视频编码层(VCL):用于独立于网络进行视频编码,编码完成后输出SODB(String Of Data Bits)数据。
  • 网络抽象层(NAL):该层的作用是将视频编码数据根据内容的不同划分成不同类型的NALU,以适配到各种各样的网络和多元环境中。对VCL输出的SODB数据后添加结尾比特,一个比特 1 和若干个比特 0,用于字节对齐,称为RBAP,然后再在 RBSP 头部加上 NAL Header 来组成一个一个的NAL单元(unit)即NALU。

H.264和H.265的NAL Header结构:

【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】

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格式:

  1. 从关键帧中获取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);

【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】

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));

苹果官方文档列举了可配置的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不同场景的硬解码流程图如下

注意事项
  1. 前后台切换会导致编解码出错,需要重新创建会话。
  2. 有些视频流虽然是AVCC格式,但NALU size的大小是3个字节,需要转为4字节格式。
  3. 切换分辨率时需要拿到新的参数集,重启*********。
  4. 编码后的视频帧之间存在参考关系,为避免遗漏最后几帧,需要在解码完最后一帧数据后调用VTDecompressionSessionWaitForAsynchronousFrames,该接口会等待所有未输出的视频帧输出结束后再返回。

搜索更多有关“ios更改视频编码:iOS,VideoTool”的信息 [百度搜索] [SoGou搜索] [头条搜索] [360搜索]
本网站部分内容、图文来自于网络,如有侵犯您的合法权益,请及时与我们联系,我们将第一时间安排核实及删除!
CopyRight © 2008-2024 蜗牛素材网 All Rights Reserved. 手机版