You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将处理后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:02:06