VideoToolbox 是一个很底层的框架,提供对硬件编码器和解码器的直接访问能力。它提供视频压缩和解压缩服务,以及存储在 CoreVideo 像素缓冲区中的光栅图像格式之间的转换服务。这些服务以会话对象(压缩、解压缩和像素传输)的形式提供,并以 Core Foundation (CF) 类型进行数据传输。
初始化
这里设置的文件名是为了方便演示demo,编码后使用AVAssetWriter直接写入文件。创建编码器的时候需要一个size。 使用 VTSessionSetProperty 可以设置相关的编码参数,如 Profile、BitRate、FPS 等。
- (void)setupFileName:(NSString *)fileName {
NSError *error = nil;
NSString *fileType = AVFileTypeQuickTimeMovie;
NSURL *outputURL = [NSURL fileURLWithPath:fileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:fileName]) {
[[NSFileManager defaultManager] removeItemAtPath:fileName error:&error];
}
self.assetWriter = [AVAssetWriter assetWriterWithURL:outputURL fileType:fileType error:&error];
if (error) {
NSString *formatString = @"Could not creat AVAssetWriter: %@";
NSLog(@"%@",[NSString stringWithFormat:formatString, error]);
return;
}
_assetWriterInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:nil];
if ([_assetWriter canAddInput:_assetWriterInput]) {
[_assetWriter addInput:_assetWriterInput];
}
_bridge.assetWriterInput = _assetWriterInput;
int32_t width = _size.width;
int32_t height = _size.height;
CMVideoCodecType codecType = kCMVideoCodecType_H264;
int formatType = kCVPixelFormatType_32BGRA;
NSDictionary *imageBufferAttributes = @{
(NSString *)kCVPixelBufferPixelFormatTypeKey: @(formatType),
(NSString *)kCVPixelBufferWidthKey: @(width),
(NSString *)kCVPixelBufferHeightKey: @(height),
};
CFMutableDictionaryRef encoderSpecification = NULL;
OSStatus status = VTCompressionSessionCreate(kCFAllocatorDefault, width, height, codecType, encoderSpecification, (__bridge CFDictionaryRef)(imageBufferAttributes), kCFAllocatorDefault, outputCallback, &_bridge, &_session);
if (status != noErr) {
printf("VTCompressionSessionCreate error \n");
}
}
开始编码
- (void)startEncode {
printf("startEncode \n");
_isWriting = [_assetWriter startWriting];
[_assetWriter startSessionAtSourceTime:kCMTimeZero];
VTCompressionSessionPrepareToEncodeFrames(_session);
}
送帧编码
pts 是每一帧的时间戳,duration 是每一帧显示的时长。编码好的数据会通过outputCallback回调给我们,里面的CMSampleBufferRef就是压缩好的数据。
- (void)encodeFrame:(CVPixelBufferRef)frame pts:(CMTime)pts duration:(CMTime)duration {
if (!_isWriting) {
return;
}
VTEncodeInfoFlags flags;
OSStatus status = VTCompressionSessionEncodeFrame(_session, frame, pts, duration, NULL, NULL, &flags);
if (status != noErr) {
printf("VTCompressionSessionEncodeFrame error \n");
}
}
void outputCallback(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {
ZFCompressionBridge *bridge = (ZFCompressionBridge *)outputCallbackRefCon;
if (bridge->assetWriterInput.readyForMoreMediaData) {
[bridge->assetWriterInput appendSampleBuffer:sampleBuffer];
}
}
结束编码
注意这里是同步的,可能会耗时。
- (void)stopEncode {
printf("stopEncode \n");
_isWriting = NO;
OSStatus status = VTCompressionSessionCompleteFrames(_session, kCMTimeInvalid);
if (status != noErr) {
printf("VTCompressionSessionCompleteFrames error \n");
}
[_assetWriter finishWritingWithCompletionHandler:^{
printf("finishWritingWithCompletionHandler \n");
}];
}