iOS开发基础139-视频解码
要进行视频解码,我们同样可以使用VideoToolbox
框架中的API来实现。以下示例会聚焦于解码H.264编码的视频流。解码过程大致分为几个步骤:创建解码会话、设置解码回调、输入编码后的数据,并在回调中接收解码后的图像。
下面是一个简化的视频解码器类实现,展示了如何设置一个解码会话并接收解码的视频帧。
VideoDecoder.h
// 引入必要的库
#import <Foundation/Foundation.h>
#import <VideoToolbox/VideoToolbox.h>
// 声明一个协议,用于处理解码后的视频帧。
@protocol VideoDecoderDelegate <NSObject>
- (void)videoDecoderDidDecodeFrame:(CVImageBufferRef)imageBuffer;
@end
// 定义VideoDecoder类,用于视频解码
@interface VideoDecoder : NSObject
@property (weak, nonatomic) id<VideoDecoderDelegate> delegate; // 委托对象,用于回调处理解码后的视频帧
- (void)decodeVideoData:(NSData *)videoData; // 解码视频数据的方法
@end
VideoDecoder.m
#import "VideoDecoder.h"
@interface VideoDecoder ()
@property (assign, nonatomic) VTDecompressionSessionRef decompressionSession; // 视频解码会话
@property (assign, nonatomic) CMVideoFormatDescriptionRef videoFormatDescription; // 视频格式描述符
@end
@implementation VideoDecoder
- (instancetype)init {
if (self = [super init]) {
_videoFormatDescription = NULL; // 初始化时,将视频格式描述符设置为NULL
_decompressionSession = NULL; // 初始化时,将解码会话设置为NULL
}
return self;
}
- (void)createDecompressionSession {
if (_decompressionSession != NULL) {
// 若解码会话已存在,先使其失效并释放资源
VTDecompressionSessionInvalidate(_decompressionSession);
CFRelease(_decompressionSession);
_decompressionSession = NULL;
}
// 设置解码输出的图像缓存的格式
NSDictionary *destinationImageBufferAttributes = @{
(NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) // 使用YUV格式(420YpCbCr8BiPlanarFullRange)来表示解码后的图像
};
VTDecompressionOutputCallbackRecord callbackRecord; // 解码输出回调记录
callbackRecord.decompressionOutputCallback = decompressionOutputCallback; // 设置解码输出的回调方法
callbackRecord.decompressionOutputRefCon = (__bridge void *)(self); // 将当前对象传递给回调方法,以便在回调中可以访问当前对象的属性或方法
OSStatus status = VTDecompressionSessionCreate(kCFAllocatorDefault, // 使用默认内存分配器
_videoFormatDescription, // 输入的视频格式描述
NULL, // 不使用解码规范
(__bridge CFDictionaryRef)(destinationImageBufferAttributes), // 解码输出图像缓存的设置
&callbackRecord, // 解码输出的回调记录
&_decompressionSession); // 创建的解码会话
if (status != noErr) {
NSLog(@"Error creating decompression session: %d", status); // 若创建失败,打印错误信息
}
}
- (void)decodeVideoData:(NSData *)videoData timestamp:(CMTime)timestamp {
// 要从`NSData`中的视频数据转换为`CMSampleBufferRef`,我们需要创建或获取一个有效的`CMSampleBufferRef`。这个过程相对复杂,因为它涉及到对输入数据的格式有一定的了解。下面是一个简化的示例,展示了如果你已经有了编码的视频帧(这里简化处理为H.264编码),如何构建一个基本的`CMSampleBufferRef`。这个过程涉及到生成或解析SPS(Sequence Parameter Set)和PPS(Picture Parameter Set),这两者用于生成一个视频格式描述来创建`CMSampleBuffer`。
// 以下主要目的是为了演示过程,并不包含所有细节
if (!videoData.length || !_decompressionSession) {
NSLog(@"No video data or decompression session is null.");
return;
}
// 这里假设videoData中已经包含了H.264帧数据。
// 真实情况下,你可能需要从更复杂的数据结构中提取出H.264的NALU单元,并处理它们。
// 1. 创建CMBlockBuffer
CMBlockBufferRef blockBuffer = NULL;
OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
(void *)videoData.bytes, // video data's pointer
videoData.length, // data length
kCFAllocatorNull,
NULL,
0,
videoData.length,
0,
&blockBuffer);
if (status != kCMBlockBufferNoErr) {
NSLog(@"Error creating CMBlockBuffer: %d", status);
return;
}
// 2. 创建CMSampleBuffer
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {videoData.length}; // 样本大小数组
status = CMSampleBufferCreateReady(kCFAllocatorDefault,
blockBuffer, // 数据块
_videoFormatDescription, // 前面通过SPS和PPS数据创建的视频格式描述
1, // sample count
0,
NULL, // timing info array
1, // sample size array entry count
sampleSizeArray, // sample size array
&sampleBuffer);
// 不再需要blockBuffer,释放资源
CFRelease(blockBuffer);
if (status != kCMBlockBufferNoErr || !sampleBuffer) {
NSLog(@"Error creating CMSampleBuffer: %d", status);
return;
}
// 3. 解码CMSampleBuffer
VTDecodeFrameFlags flags = 0;
VTDecodeInfoFlags flagOut = 0;
status = VTDecompressionSessionDecodeFrame(_decompressionSession,
sampleBuffer,
flags,
NULL, // output callback reference
&flagOut);
if (status != noErr) {
NSLog(@"Decode failed status: %d", status);
}
// 释放sampleBuffer资源
CFRelease(sampleBuffer);
}
static void decompressionOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,
void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration) {
if (status != noErr) {
NSLog(@"Error in decompression output callback: %d", status); // 若解码过程中有错误,打印错误信息
return;
}
VideoDecoder *decoder = (__bridge VideoDecoder *)decompressionOutputRefCon; // 从传递到回调的引用中取回VideoDecoder对象
if ([decoder.delegate respondsToSelector:@selector(videoDecoderDidDecodeFrame:)]) {
[decoder.delegate videoDecoderDidDecodeFrame:imageBuffer]; // 将解码后的图像帧通过协议中的方法回调出去
}
}
- (void)dealloc {
if (_decompressionSession) {
VTDecompressionSessionInvalidate(_decompressionSession); // 使解码会话失效
CFRelease(_decompressionSession); // 释放解码会话资源
}
if (_videoFormatDescription) {
CFRelease(_videoFormatDescription); // 释放视频格式描述符资源
}
}
@end
使用方法
VideoDecoder *decoder = [[VideoDecoder alloc] init];
decoder.delegate = self;
[decoder decodeVideoData:frameData];
- (void)videoDecoderDidDecodeFrame:(CVImageBufferRef)imageBuffer {
// 处理解码图像缓冲区(显示、处理等)
}
说明
-
创建解码会话(
VTDecompressionSessionCreate
):在初始化解码器类时或者在第一次接收到视频数据时调用。此方法需要正确的CMVideoFormatDescriptionRef来描述输入视频流的格式,如H.264。对于网络流,这通常可以从流的SPS和PPS单元中解析得到。 -
解码视频数据(
VTDecompressionSessionDecodeFrame
):此函数用于输入编码的视频帧,并异步返回解码后的帧。解码的输出是通过设置的回调函数返回的。 -
回调函数:解码的帧通过设置的解码输出回调(decompressionOutputCallback)返回。回调提供CVImageBufferRef格式的解码视频帧,可以用于显示或进一步处理。
-
注意内存管理和线程安全:确保在不需要解码会话时使用
VTDecompressionSessionInvalidate
来销毁会话,并释放相关资源。解码操作是异步进行的,确保回调中的处理逻辑是线程安全的。
以上是一个基础的视频解码器实现,实际应用中可能需要根据特定需求进行适当的调整。例如,处理解码错误、支持不同的输入格式或优化解码性能等。
将来的你会感谢今天如此努力的你!
版权声明:本文为博主原创文章,未经博主允许不得转载。