iOS 硬件编码 VideoToolBox

212 阅读1分钟

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");
    }];
}