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

如何通过ReplayKit结合WebRTC实现iOS设备屏幕采集传输?

问题分析与解决方案

你的代码里有几个关键问题,导致无法正常将ReplayKit的画面推送到WebRTC对等端,我来逐一拆解:


1. 每帧回调重复创建核心资源(致命问题)

processSampleBuffer是每收到一帧画面就会触发一次的回调,但你在这个方法里每次都新建localVideoSource、RTCVideoCapturer、RTCVideoTrack、RTCMediaStream,还反复调用add(mediaStream)添加到PeerConnection。这会导致:

  • WebRTC PeerConnection被重复添加相同/重复的媒体轨道,远端无法正确处理
  • 大量内存泄漏,因为这些资源没有被释放
  • 帧推送逻辑完全混乱,WebRTC无法建立稳定的视频流

这些核心资源应该只初始化一次,比如在你的类初始化时(或者PeerConnection建立完成后)创建,而不是每帧都重建。

2. 时间戳使用错误

你用NSDate().timeIntervalSince1970生成时间戳,但WebRTC要求的是单调递增的、基于采集开始的相对时间戳,系统时间可能会因为校准、时区等原因跳变,导致WebRTC无法正确排序帧。正确的做法是使用ReplayKit提供的CMSampleBuffer自带的展示时间戳。

3. 不必要的RTCVideoCapturer实例化

你每次都新建一个RTCVideoCapturer,但其实WebRTC的videoSource.capturer(_:didCapture:)方法里的capturer参数只需要一个固定的实例(可以是自定义的空实现,或者全局的capturer对象),不需要每帧都创建。

4. 像素格式兼容性(潜在问题)

ReplayKit输出的CVPixelBuffer可能是BGRA格式,而WebRTC默认更适配I420格式。如果直接推送BGRA帧,部分远端设备可能无法正常解码渲染,建议转换为WebRTC支持的格式,或者初始化RTCVideoFrame时指定正确的格式。


修正后的代码示例

首先,在你的类里定义全局的视频资源变量:

var peerConnectionFactory: RTCPeerConnectionFactory?
var localVideoSource: RTCVideoSource?
var localVideoTrack: RTCVideoTrack?
var videoCapturer: RTCVideoCapturer? // 全局的capturer实例

然后在PeerConnection初始化完成后(比如连接建立前)初始化这些资源:

// 在合适的时机(比如创建PeerConnection后)初始化视频资源
func setupVideoSource() {
    guard let factory = peerConnectionFactory else { return }
    videoCapturer = RTCVideoCapturer(delegate: nil) // 不需要delegate,因为我们手动推帧
    localVideoSource = factory.videoSource()
    localVideoTrack = factory.videoTrack(with: localVideoSource!, trackId: "screen-track-100")
    
    // 创建媒体流并添加轨道,添加到PeerConnection(只做一次)
    let mediaStream = factory.mediaStream(withStreamId: "screen-stream-1")
    mediaStream.addVideoTrack(localVideoTrack!)
    newPeerConnection?.add(mediaStream)
}

最后修改processSampleBuffer回调,只负责推送帧:

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
    switch sampleBufferType {
    case .video:
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
              let videoSource = localVideoSource,
              let capturer = videoCapturer else {
            return
        }
        
        // 使用SampleBuffer自带的时间戳,转换为纳秒
        let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
        let timestampNs = Int64(CMTimeGetSeconds(presentationTime) * 1_000_000_000)
        
        // 创建RTCVideoFrame,注意旋转角度(根据你的需求调整,比如ReplayKit输出可能是竖屏)
        let videoFrame = RTCVideoFrame(pixelBuffer: pixelBuffer,
                                      rotation: ._0,
                                      timeStampNs: timestampNs)
        
        // 推送帧到WebRTC
        videoSource.capturer(capturer, didCapture: videoFrame)
        
    default:
        break
    }
}

额外注意事项

  • 确保setupVideoSource()在processSampleBuffer触发前调用,不然全局变量都是nil,无法推帧
  • 如果遇到像素格式不兼容的问题,可以使用WebRTC的RTCVideoFrameBuffer进行格式转换,比如将BGRA转换为I420
  • 记得在不需要推流时(比如停止录屏),释放相关资源,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:54:10