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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 19:04:55