iPhone 5s及更低设备添加视频文字水印后音频丢失问题求助
解决iPhone 5s添加文字水印后音频丢失的问题
首先得明确核心原因:iPhone 5s的A7芯片性能有限,高画质下实时叠加文字的像素操作耗时过长,挤占了音频写入的实时处理窗口,导致音频输入缓冲区无法及时处理数据,最终出现音频丢失的情况。下面给出几个针对性的解决方案,按优先级排序:
1. 优化文字叠加的像素处理逻辑,减少实时计算量
你的Watermark类里每次处理帧都要重新创建CIContext、CGContext和文字图像,这是很大的性能浪费。可以把静态资源提前缓存,复用上下文对象:
class Watermark { private var ciContext: CIContext! private var staticTextImage: UIImage? // 缓存静态文字(如用户名、日期) override init() { super.init() // 初始化一次CIContext,复用它来渲染 let options: [CIContextOption: Any] = [ .useSoftwareRenderer: false, // 优先用GPU渲染 .cacheIntermediates: true ] ciContext = CIContext(options: options) } func preloadStaticText(username: String, date: String, size: CGSize) { // 提前渲染静态文字,避免每次帧处理都生成 staticTextImage = createStaticTextImage(username: username, date: date, size: size) } func addWatermark(data: CMSampleBuffer, values: Dictionary<String, Any>) -> CVPixelBuffer{ guard let pixelBuffer = CMSampleBufferGetImageBuffer(data) else { fatalError("Failed to get pixel buffer from sample") } CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) // 只渲染动态变化的文字(如计时器、次数) let dynamicTextImage = createDynamicTextImage(values: values, size: CGSize(width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))) // 合并静态+动态文字,再叠加到帧上 writeCombinedWatermark(staticImage: staticTextImage, dynamicImage: dynamicTextImage, toBuffer: pixelBuffer) CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) return pixelBuffer } private func writeCombinedWatermark(staticImage: UIImage?, dynamicImage: UIImage?, toBuffer pixelBuffer:CVPixelBuffer){ guard let inputImage = CIImage(cvImageBuffer: pixelBuffer) else { return } var outputImage = inputImage // 叠加静态文字 if let staticCiImage = CIImage(image: staticImage!) { let staticFilter = CIFilter(name: "CISourceOverCompositing")! staticFilter.setValue(outputImage, forKey: kCIInputBackgroundImageKey) staticFilter.setValue(staticCiImage, forKey: kCIInputImageKey) outputImage = staticFilter.outputImage! } // 叠加动态文字 if let dynamicCiImage = CIImage(image: dynamicImage!) { let dynamicFilter = CIFilter(name: "CISourceOverCompositing")! dynamicFilter.setValue(outputImage, forKey: kCIInputBackgroundImageKey) dynamicFilter.setValue(dynamicCiImage, forKey: kCIInputImageKey) outputImage = dynamicFilter.outputImage! } // 复用预创建的CIContext渲染 ciContext.render(outputImage, to: pixelBuffer, bounds: outputImage.extent, colorSpace: CGColorSpaceCreateDeviceRGB()) } }
2. 调整视频编码参数,降低CPU/GPU负载
给视频输出设置添加压缩属性,限制帧率和码率,匹配iPhone 5s的性能:
// 修改VideoWriter的videoOutputSettings let videoOutputSettings: [String: Any] = [ AVVideoCodecKey : AVVideoCodecH264, AVVideoWidthKey : width, AVVideoHeightKey : height, AVVideoCompressionPropertiesKey: [ AVVideoAverageBitRateKey: 6_000_000, // 6Mbps,根据分辨率调整(iPhone 5s是1080p的话用6-8Mbps,720p用4-6Mbps) AVVideoMaxKeyFrameIntervalKey: 60, // 每60帧一个关键帧,减少编码压力 AVVideoExpectedSourceFrameRateKey: 30, // 固定30fps,避免动态帧率波动 AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel // 用合适的编码级别 ] ];
3. 用后台队列异步处理视频帧,避免阻塞音频写入
把视频帧的水印处理放到独立的串行队列,不要在音频写入的线程里同步处理,防止音频写入被阻塞:
class VideoWriter : NSObject{ private let videoProcessingQueue = DispatchQueue(label: "com.yourapp.video.watermark.processing", qos: .userInitiated) // ... 其他属性和方法 ... func write(_ sample: CMSampleBuffer, isVideo: Bool){ if CMSampleBufferDataIsReady(sample) { if fileWriter.status == AVAssetWriterStatus.unknown { print("Start writing, isVideo = \(isVideo), status = \(fileWriter.status.rawValue)") let startTime = CMSampleBufferGetPresentationTimeStamp(sample) fileWriter.startWriting() fileWriter.startSession(atSourceTime: startTime) } if fileWriter.status == AVAssetWriterStatus.failed { print("Error occured, isVideo = \(isVideo), status = \(fileWriter.status.rawValue), \(fileWriter.error!.localizedDescription)") return } if isVideo { // 异步处理水印,避免阻塞音频写入 let sampleCopy = CMSampleBufferCreateCopy(nil, sample)! videoProcessingQueue.async { [weak self] in guard let self = self, let watermarkData = self.watermarkData else { return } let time = CMSampleBufferGetPresentationTimeStamp(sampleCopy) let pixelBuffer = self.watermark.addWatermark(data: sampleCopy, values: watermarkData) // 回到assetWriter的写入线程(如果你的写入是在主线程,就用DispatchQueue.main) DispatchQueue.main.async { if self.fileWriter.status == .writing && self.videoInput.isReadyForMoreMediaData { self.assetWriterPixelBufferInput!.append(pixelBuffer, withPresentationTime: time) } } } }else{ // 音频写入要保证实时,所以在当前线程直接处理 if audioInput.isReadyForMoreMediaData { audioInput.append(sample) } } } } }
4. 切换到更高效的像素格式
iPhone 5s的GPU对YUV格式的像素缓冲器处理更高效,把像素格式从kCVPixelFormatType_32BGRA换成kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
// 修改VideoWriter的sourcePixeBufferAttributes let sourcePixeBufferAttributes :[String: Any] = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, kCVPixelBufferWidthKey as String: width, kCVPixelBufferHeightKey as String: height ]
注意:切换像素格式后,Watermark类里的渲染逻辑也要适配YUV图像,比如在创建CIImage时要正确设置颜色空间。
为什么原来的方法会出问题?
当你用assetWriterPixelBufferInput.append时,每帧都要执行像素缓冲器加锁、文字渲染、解锁等操作,高分辨率下A7芯片的CPU/GPU被占满,导致音频输入的isReadyForMoreMediaData状态一直无法变为true,系统会认为音频输入超时,停止接收音频数据。而videoInput.append(sample)是直接写入原始帧,没有额外计算,所以音频能正常工作。
内容的提问来源于stack exchange,提问作者Dhiru201
相关产品推荐
相关产品推荐

