如何在Swift中将CMSampleBuffer转换为视频Data?
嘿,我来帮你搞定把CMSampleBuffer转换成视频Data的事儿!从相机捕获的CMSampleBuffer是未编码的原始视频帧,要得到可复用的视频Data,我们需要用AVAssetWriter把这些帧编码成标准视频格式(比如MP4),下面是具体实现步骤和代码:
核心思路
相机输出的每一帧CMSampleBuffer都是原始像素数据,我们通过AVAssetWriter将这些帧编码为H.264格式的视频流,最终读取编码后的临时文件内容得到视频Data。
步骤1:初始化编码工具
首先准备AVAssetWriter(负责编码)和AVAssetWriterInput(负责接收帧数据),因为AVAssetWriter需要写入文件,我们用临时文件存储编码结果,最后再转成Data:
import AVFoundation private var assetWriter: AVAssetWriter? private var videoInput: AVAssetWriterInput? private var tempVideoURL: URL? func setupAssetWriter(with firstSampleBuffer: CMSampleBuffer) { do { // 创建唯一的临时文件URL tempVideoURL = FileManager.default.temporaryDirectory.appendingPathComponent("temp_\(UUID().uuidString).mp4") guard let tempURL = tempVideoURL else { return } // 清理已存在的同名临时文件 if FileManager.default.fileExists(atPath: tempURL.path) { try FileManager.default.removeItem(at: tempURL) } // 初始化AVAssetWriter,指定输出格式为MP4 assetWriter = try AVAssetWriter(outputURL: tempURL, fileType: .mp4) // 从第一帧获取视频基础信息(分辨率、格式) let formatDesc = CMSampleBufferGetFormatDescription(firstSampleBuffer)! let videoDimensions = CMVideoFormatDescriptionGetDimensions(formatDesc) let frameRate = 30.0 // 可根据相机实际帧率调整,比如从AVCaptureDevice获取 // 配置视频编码参数 let videoSettings: [String: Any] = [ AVVideoCodecKey: AVVideoCodecType.h264, AVVideoWidthKey: videoDimensions.width, AVVideoHeightKey: videoDimensions.height, AVVideoCompressionPropertiesKey: [ AVVideoAverageBitRateKey: 5_000_000, // 比特率,数值越大画质越好但文件越大 AVVideoMaxKeyFrameIntervalKey: Int(frameRate) // 关键帧间隔,通常设为帧率值 ] ] // 创建视频输入,开启实时数据接收模式 videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings) videoInput?.expectsMediaDataInRealTime = true // 将视频输入添加到编码工具 if let input = videoInput, assetWriter?.canAdd(input) == true { assetWriter?.add(input) } // 开始编码会话 assetWriter?.startWriting() assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(firstSampleBuffer)) } catch { print("初始化编码工具失败:\(error.localizedDescription)") } }
步骤2:在捕获回调中写入帧数据
在你现有的captureOutput方法里,把每一帧CMSampleBuffer喂给编码工具:
public func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // 首次捕获帧时初始化编码工具 if assetWriter == nil { setupAssetWriter(with: sampleBuffer) return } guard let assetWriter = assetWriter, let videoInput = videoInput else { return } // 确保编码工具处于写入状态,且输入端口准备好接收数据 if assetWriter.status == .writing && videoInput.isReadyForMoreMediaData { videoInput.append(sampleBuffer) } // 你可以根据业务逻辑,在合适时机调用stopRecording()结束录制 // 比如用户点击停止按钮时触发 }
步骤3:停止录制并获取视频Data
当需要结束录制时,调用下面的方法完成编码并提取视频Data:
func stopRecording(completion: @escaping (Data?) -> Void) { guard let assetWriter = assetWriter, let videoInput = videoInput else { completion(nil) return } // 标记输入数据结束 videoInput.markAsFinished() // 完成编码并处理结果 assetWriter.finishWriting { [weak self] in guard let self = self, assetWriter.status == .completed else { print("视频编码失败:\(assetWriter.error?.localizedDescription ?? "未知错误")") completion(nil) return } do { // 读取临时文件内容为Data let videoData = try Data(contentsOf: self.tempVideoURL!) completion(videoData) // 清理临时文件 try FileManager.default.removeItem(at: self.tempVideoURL!) } catch { print("读取视频Data失败:\(error.localizedDescription)") completion(nil) } } }
注意事项
- 格式兼容性:相机输出的
CMSampleBuffer通常是YUV格式(如kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),H.264编码原生支持这种格式,无需额外转换。 - 状态监听:要关注
assetWriter.status的变化,及时处理编码过程中的错误(比如磁盘空间不足、格式不支持等)。 - 实时性保障:因为是相机实时捕获,
videoInput的expectsMediaDataInRealTime必须设为true,确保能及时接收帧数据。
内容的提问来源于stack exchange,提问作者Shuvo Joseph
相关产品推荐
相关产品推荐

