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
相关产品推荐
相关产品推荐

