如何通过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

