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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:53:19