在 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);
复制代码 相关系列
- 一文搞懂 Android 音视频编解码器 MediaCodec
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |