iOS ReplayKit中RPBroadcastSampleHandler回调出现视频帧重叠问题
解决ReplayKit中RPBroadcastSampleHandler回调视频帧重叠及数据异常问题
问题现象
使用ReplayKit录屏时,在processSampleBuffer:withType:回调内通过memcpy复制视频像素数据后,原数据发生变化,屏幕内容快速切换时该问题频发,最终导致画面出现帧重叠。测试发现从第672字节开始的数据值会在复制后改变,测试代码及日志如下:
测试代码
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { switch (sampleBufferType) { case RPSampleBufferTypeVideo: { CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); int ret = 0; uint8_t *oYData = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); size_t oYSize = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0) * CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); uint8_t *oUVData = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); size_t oUVSize = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1) * CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); if (oYSize <= 672) { return; } uint8_t tempValue = oYData[672]; uint8_t *tYData = malloc(oYSize); memcpy(tYData, oYData, oYSize); if (tYData[672] != oYData[672]) { NSLog(@"$$$$$$$$$$$$$$$$------ t:%d o:%d temp:%d", tYData[672], oYData[672], tempValue); } free(tYData); CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); break; } default: { break; } } }
日志输出
$$$$$$$$$$$$$$$$------ t:110 o:124 temp:110 $$$$$$$$$$$$$$$$------ t:111 o:133 temp:111 $$$$$$$$$$$$$$$$------ t:124 o:138 temp:124 $$$$$$$$$$$$$$$$------ t:133 o:144 temp:133 $$$$$$$$$$$$$$$$------ t:138 o:151 temp:138 $$$$$$$$$$$$$$$$------ t:144 o:156 temp:144 $$$$$$$$$$$$$$$$------ t:151 o:135 temp:151 $$$$$$$$$$$$$$$$------ t:156 o:78 temp:156 $$$$$$$$$$$$$$$$------ t:135 o:76 temp:135 $$$$$$$$$$$$$$$$------ t:78 o:77 temp:78 $$$$$$$$$$$$$$$$------ t:76 o:80 temp:76 $$$$$$$$$$$$$$$$------ t:77 o:80 temp:77 $$$$$$$$$$$$$$$$------ t:80 o:79 temp:80 $$$$$$$$$$$$$$$$------ t:79 o:80 temp:79
问题原因分析
核心原因是ReplayKit传递的CMSampleBufferRef及内部CVPixelBufferRef是系统复用的内存块,即使添加kCVPixelBufferLock_ReadOnly锁,屏幕快速刷新时系统仍会异步覆盖这部分缓冲区。你在memcpy完成后再读取原数据,此时内存已被系统更新,导致复制前后数据不一致,最终处理时拿到新旧帧混合的数据,引发帧重叠。
解决方案
方案1:锁内完成复制,立即解锁缓冲区
将像素数据复制操作放在锁内完成,复制后立即解锁,后续仅使用复制后的内存,不再访问原缓冲区:
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { switch (sampleBufferType) { case RPSampleBufferTypeVideo: { CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); uint8_t *oYData = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); size_t oYSize = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0) * CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); uint8_t *oUVData = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); size_t oUVSize = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1) * CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); // 锁内完成所有复制 uint8_t *tYData = malloc(oYSize); uint8_t *tUVData = malloc(oUVSize); memcpy(tYData, oYData, oYSize); memcpy(tUVData, oUVData, oUVSize); // 立即解锁,避免系统占用缓冲区 CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); // 后续仅使用复制后的内存处理业务 if (oYSize > 672) { NSLog(@"校验值:%d", tYData[672]); } // 释放内存 free(tYData); free(tUVData); break; } default: { break; } } }
方案2:创建CVPixelBuffer独立副本
利用CoreVideo API直接创建像素缓冲区的完整副本,彻底规避内存复用问题:
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { switch (sampleBufferType) { case RPSampleBufferTypeVideo: { CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferRef copiedPixelBuffer = NULL; // 创建独立副本 CVPixelBufferCreateCopy(kCFAllocatorDefault, pixelBuffer, &copiedPixelBuffer); if (copiedPixelBuffer) { CVPixelBufferLockBaseAddress(copiedPixelBuffer, kCVPixelBufferLock_ReadOnly); uint8_t *oYData = CVPixelBufferGetBaseAddressOfPlane(copiedPixelBuffer, 0); size_t oYSize = CVPixelBufferGetHeightOfPlane(copiedPixelBuffer, 0) * CVPixelBufferGetBytesPerRowOfPlane(copiedPixelBuffer, 0); // 业务处理逻辑 if (oYSize > 672) { NSLog(@"像素值:%d", oYData[672]); } CVPixelBufferUnlockBaseAddress(copiedPixelBuffer, kCVPixelBufferLock_ReadOnly); CFRelease(copiedPixelBuffer); } break; } default: { break; } } }
方案3:优化回调内处理速度
ReplayKit要求processSampleBuffer:withType:回调快速处理,耗时过长会触发系统强制复用缓冲区:
- 避免在回调内执行网络请求、复杂计算等耗时操作
- 锁内完成复制后,将处理逻辑放到后台队列异步执行
验证效果
采用上述方案后,复制的数据与原帧数据完全一致,屏幕快速切换时不会再出现数据异常及帧重叠现象。
内容的提问来源于stack exchange,提问作者zm2529
相关产品推荐
相关产品推荐

