如何将处理后的CIImage转换为CMSampleBuffer用于AVAssetWriter录屏
嘿,我在做AVAssetWriter录制MP4的实时帧处理时,经常碰到这个问题——原CMSampleBuffer是不可变的,没法直接修改,得重新生成一个才行。下面给你梳理具体的实现步骤和注意事项:
核心思路
你没法直接修改原有的CMSampleBuffer,必须基于处理后的CIImage数据,重新创建一个符合AVAssetWriter要求的新样本缓冲区。整个流程大概是:CIImage → CVPixelBuffer → CMSampleBuffer。
具体实现步骤
1. 将CIImage渲染到CVPixelBuffer
首先得把处理好的CIImage转换成CVPixelBuffer,这是因为CMSampleBuffer的图像数据载体通常是CVPixelBuffer。这里要注意必须保证CVPixelBuffer的尺寸、像素格式和原视频流一致,不然AVAssetWriterInput会拒绝写入。
你可以用CIContext的render(_:to:)方法来完成转换,而且CIContext尽量复用,别每次都新建,不然性能会崩:
// 先准备和原视频匹配的CVPixelBuffer属性 let pixelBufferAttrs: [String: Any] = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, // 和原格式一致,比如原视频是420YpCbCr8BiPlanarFullRange就用对应值 kCVPixelBufferWidthKey as String: Int(processedCIImage.extent.width), kCVPixelBufferHeightKey as String: Int(processedCIImage.extent.height), kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true ] // 创建空的CVPixelBuffer var outputPixelBuffer: CVPixelBuffer? CVPixelBufferCreate( kCFAllocatorDefault, Int(processedCIImage.extent.width), Int(processedCIImage.extent.height), kCVPixelFormatType_32BGRA, pixelBufferAttrs as CFDictionary, &outputPixelBuffer ) guard let pixelBuffer = outputPixelBuffer else { print("Failed to create pixel buffer") return } // 把处理后的CIImage渲染到CVPixelBuffer CIContext.shared.render(processedCIImage, to: pixelBuffer)
2. 复用原CMSampleBuffer的关键信息
为了保证录制的视频时序、格式和原输入一致,要从原样本缓冲区里提取两个关键信息:
- 媒体格式描述(CMFormatDescription):包含视频的分辨率、像素格式等元数据,直接从原缓冲区拿就行
- 时序信息(CMSampleTimingInfo):保证每一帧的时间戳、时长和原输入同步,避免录制的视频跳帧或速度异常
// 获取原样本的格式描述 guard let originalFormatDesc = CMSampleBufferGetFormatDescription(originalSampleBuffer) else { print("Failed to get format description") return } // 获取原样本的时序信息 var timingInfo = CMSampleTimingInfo() CMSampleBufferGetSampleTimingInfo(originalSampleBuffer, at: 0, timingInfoOut: &timingInfo)
3. 创建新的CMSampleBuffer
用CMSampleBufferCreateReadyWithImageBuffer方法,把上面准备好的CVPixelBuffer、格式描述和时序信息传进去,生成新的样本缓冲区:
var newSampleBuffer: CMSampleBuffer? let status = CMSampleBufferCreateReadyWithImageBuffer( kCFAllocatorDefault, pixelBuffer, originalFormatDesc, &timingInfo, &newSampleBuffer ) if status == noErr, let sampleBufferToWrite = newSampleBuffer { // 检查AVAssetWriterInput是否准备好,然后写入新的样本 if assetWriterInput.isReadyForMoreMediaData { assetWriterInput.append(sampleBufferToWrite) } } else { print("Failed to create new CMSampleBuffer, status: \(status)") }
重要注意事项
- 像素格式匹配:一定要和原视频输入的像素格式保持一致,比如原视频是
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,就别用BGRA,不然写入会失败 - 队列调度:帧处理和样本创建的操作要放在专门的串行队列里,别阻塞主线程,同时要和AVAssetWriter的写入操作做好同步
- 性能优化:复用CIContext和CVPixelBuffer池(可以用
CVPixelBufferPoolCreate创建缓冲池),避免频繁创建销毁对象,减少卡顿 - 错误处理:每个Core Media的API都要检查返回的OSStatus,及时处理错误,避免录制中途崩溃
内容的提问来源于stack exchange,提问作者user924
相关产品推荐
相关产品推荐

