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

iOS基于ReplayKit的RPBroadcastSampleHandler高帧率下异常YUV帧问题

问题分析与解决方案

核心问题:CVPixelBuffer锁的生命周期不匹配

你当前代码中,processSampleBuffer里对CVPixelBuffer加了只读锁,但defer语句会在asyncDump调用完成后立即解锁——而asyncDump里的fwrite是在异步队列执行的,此时buffer已被系统解锁并可能被复用、修改,导致异步写入时拿到的是被篡改的内存数据,最终出现画面分割的异常YUV帧。

高帧率(90fps)会加剧这个问题:系统在高帧率下会更频繁复用CVPixelBuffer对象,buffer解锁后很快就会被新帧覆盖,异步写入时会读取到新旧帧混合的内存数据。

修复方案

调整内存拷贝时机,在同步线程内完成所有plane的内存拷贝,再将拷贝好的数据异步写入文件,确保buffer锁的覆盖范围包含整个拷贝过程:

修改后的代码

extension CVPixelBuffer{
    func asyncDump(_ f:UnsafeMutablePointer<FILE>){
        NSLog("dump a frame.")
        let planeCount = CVPixelBufferGetPlaneCount(self)
        // 同步线程内完成所有平面内存拷贝,暂存数据
        var copiedPlanes: [(data: UnsafeMutableRawPointer, size: Int)] = []
        for plane in 0 ..< planeCount {
            let source      = CVPixelBufferGetBaseAddressOfPlane(self, plane)
            let height      = CVPixelBufferGetHeightOfPlane(self, plane)
            let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(self, plane)
            let planeSize   = height * bytesPerRow

            let rawFrame = malloc(planeSize)
            memcpy(rawFrame, source, planeSize)
            copiedPlanes.append((rawFrame, planeSize))
        }
        
        // 异步写入文件
        DispatchQueue.global(qos: .default).async{
            for planeData in copiedPlanes {
                fwrite(planeData.data, 1, planeData.size, f)
                free(planeData.data)
            }
            NSLog("dump a frame done.")
        }
    }
}

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
    guard DUMP_YUV, sampleBufferType == .video else {
        // 处理非视频帧或非dump模式逻辑
        switch sampleBufferType {
        case .video:
            // 你的其他视频帧处理逻辑
        default:
            break
        }
        return
    }

    videoFrames += 1
    guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
    
    let height = UInt32(CVPixelBufferGetHeight(imageBuffer))
    let width = UInt32(CVPixelBufferGetWidth(imageBuffer))
    printFPS("video-\(width)x\(height)")
    
    // 锁定buffer直到所有平面拷贝完成
    CVPixelBufferLockBaseAddress(imageBuffer, [.readOnly])
    defer { CVPixelBufferUnlockBaseAddress(imageBuffer, [.readOnly]) }
    
    if videoFrames % 150 == 0 {
        if let f = f {
            imageBuffer.asyncDump(f)
        }
    }
}

额外说明

  • 帧率控制:ReplayKit的回调帧率由系统根据屏幕刷新率动态调整(如ProMotion屏幕可达90/120fps),目前无公开API强制固定帧率。若需控制输出帧率,可在帧处理逻辑中添加丢帧逻辑(如每两帧保留一帧),但需同步音频帧时间戳避免音画不同步。
  • IO优化:异步写入时可考虑批量处理帧,减少频繁磁盘IO操作,避免IO成为性能瓶颈。

内容的提问来源于stack exchange,提问作者Water dragon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 18:45:39