在 IOS 平台上,我们常常需求处理音视频数据,比方播映视频、录制音频等。为了高效处理这些数据,IOS 提供了 VideoToolbox
类,它答应我们对音视频进行编解码操作。
什么是 VideoToolbox?
IOS 8.0之后,苹果开放了硬编解码API,即 VideoToolbox.framework的API。VideoToolbox是一套纯C言语API,能够直接访问硬件编解码器。它提供视频紧缩宽和紧缩以及存储在像素缓存区中的数据转化服务。
下面以编码器为例进行阐明:
先介绍VideoToolbox相关的几种数据结构
(1)CVPixelBuffer
:存储编码前或解码后的视频帧,包含了图像的像素数据以及有关像素格局、巨细和颜色空间等信息。
(2)CMBlockBuffer
:存储紧缩后的视频数据,例如H.264视频流中的NAL单元。
(3)CMSampleBuffer
:包含时刻戳和持续时刻等元数据的样本数据。它能够包含一个CVPixelBuffer
或者CMBlockBuffer
,相当于寄存视频图像的容器数据结构。
H.264编码全体流程:
- CVPixelBuffer → CMSampleBuffer → H.264
VideoToolbox用法
1. 创立 VideoToolbox 会话
要使用 VideoToolbox
,首要需求创立一个 VTCompressionSessionRef
实例作为编码器的句柄。以下是创立编码器实例的基本步骤:
VTCompressionSessionRef compressionSession;
OSStatus status = VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, compressionCallback, NULL, &compressionSession);
if (status != noErr) {
NSLog(@"Failed to create compression session: %d", (int)status);
return;
}
其中,width
和 height
是要编码的视频帧的宽度和高度,kCMVideoCodecType_H264
是视频编码器类型。compressionCallback
是一个回调函数,用于处理编码后的数据。
2. 装备 VideoToolbox
NSDictionary *compressionProperties = @{
(__bridge NSString *)kVTCompressionPropertyKey_RealTime: @YES, // 开启实时编码,降低延迟
(__bridge NSString *)kVTCompressionPropertyKey_ProfileLevel: (__bridge NSString *)kVTProfileLevel_H264_Baseline_AutoLevel, // BaseLine等级
(__bridge NSString *)kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration: @(10), // 关键帧距离
(__bridge NSString *)kVTCompressionPropertyKey_AverageBitRate: @(500000), // 均匀码率,单位bps
(__bridge NSString *)kVTCompressionPropertyKey_DataRateLimits: @(600000/8), // 最高码率,单位byte/s
};
VTSessionSetProperties(compressionSession, (__bridge CFDictionaryRef)compressionProperties);
IOS只支撑ABR码控形式:
ABR
:稳定均匀方针码率,简单场景分配较低bit,杂乱场景分配满足bit,使得有限的bit数能够在不同场景下合理分配,这类似VBR。同时一定时刻内,均匀码率又接近设置的方针码率,这样能够控制输出文件的巨细,这又类似CBR。能够认为是CBR和VBR的折中计划。
3. 处理输入数据
将需求编码的原始视频帧传递给编码器进行处理。首要将原始数据放入 CMSampleBufferRef
中,然后将其传递给 VideoToolbox
:
CVPixelBufferRef pixelBuffer; // 寄存原始视频帧数据
CFDictionaryRef frameProperties; // 设置编码帧特点,如是否为关键帧
CMTime presentationTimeStamp = CMTimeMake(value, timescale); // 显示时刻戳
VTCompressionSessionEncodeFrame(compressionSession, pixelBuffer, presentationTimeStamp, kCMTimeInvalid, frameProperties, NULL, NULL);
注意事项:
CMTime是IOS专门描绘视频时刻的一种数据类型
官网注释 /*@field value the value of the CMTime. value/timescale = seconds*/
举个比方阐明,假设以下景象:
A视频15帧,播映速度5fps,持续时刻=15/5=3s
B视频60帧,播映速度20fps,持续时刻60/20=3s
由此可知,timesclae应设置为帧率
4. 处理输出数据
编码器处理完数据后,会通过上述界说的回调函数回来编码后的数据。在回调函数中能够处理编码后的数据,比方将其写入文件或传输到网络上。
void compressionCallback(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {
if (status != noErr) {
NSLog(@"Failed to encode frame: %d", (int)status);
return;
}
// 从sampleBuffer中取出编码后的数据
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 AVCCHeaderLength = 4; // 回来的NALU数据前四个字节是大端形式的帧长度
// 循环获取NALU数据
while (bufferOffset < totalLength - AVCCHeaderLength) {
uint32_t NALUnitLength = 0;
// 读取NALU长度的数据
memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);
// 从大端转系统端
NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
NSData* data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength];
// 移动到下一个NALU单元
bufferOffset += AVCCHeaderLength+NALUnitLength;
}
}
}
注意事项:
VideoToolbox编码以AVCC格局(头四个字节表明NALU长度)存储数据,且字节次序是反的,在转化为H.264格局时需取出头四个字节并替换为Annex B格局(头四个字节是0001),并将字节次序回转得到H.264数据。
5. 销毁编码器
VTCompressionSessionCompleteFrames(compressionSession, kCMTimeInvalid);
VTCompressionSessionInvalidate(compressionSession);