在实时通信中,如果你希望用户可以分享本端设备的屏幕和设备播放的音频,可以使用 RTC 内建的屏幕采集功能,也可以自行实现屏幕采集逻辑(自定义采集),并通过屏幕共享功能,与远端用户共享。
仅可见的用户可以发布屏幕流。
你可以在多种行业、多种场景下使用屏幕共享功能:
| 行业 | 场景 |
|---|---|
| 在线教育 | 老师共享屏幕给学生上课;美术老师共享屏幕给学生教画画。 |
| 游戏直播 | 主播共享屏幕给观众,展现自己的游戏画面。 |
| 互动直播 | 主播共享自己的屏幕和观众互动。 |
| 视频会议 | 会议成员共享屏幕观看 PPT 或者文档。 |

通过苹果的 Replaykit 组件进行录屏。由于苹果的隐私设置,不同 App 之间数据无法互通,因此,需要利用 Broadcast Upload Extension 组件,把组件和 App 配置为同一 App Group,以实现了 iOS 端的屏幕内容分享。
新建 Broadcast Upload Extension 的 Target,并集成 RTC SDK 提供的 VolcEngineRTCScreenCapturer.framework
在 Xcode 中,点击 File > New > Target...,在弹出对话框中选择 Broadcast Upload Extension,点击 Next。
填写相关信息,取消勾选 Broadcast Setup UI Extension,点击 Finish 完成创建。
RTC 暂不支持 Broadcast Setup UI Extension。如需开启该扩展,请确认已自行实现相关逻辑。
选择刚创建的 Target,进行配置。
Bundle Identifier 必须以你的 App target 的 Bundle Identifier 为前缀。
将 VolcEngineRTCSDK 包中的 VolcEngineRTCScreenCapturer.framework 拖动到工程中。
为使 Extension 录屏进程可以和 App 进程进行跨进程通信,需要将 Extension 录屏进程和 App 分别配置为同一 App Group。参见如何创建和配置 App Group
创建 RTC 引擎后,调用 setExtensionConfig: 接口,填写上一个步骤中创建的 App Group ID,响应从系统的控制中心发起的屏幕共享。
- (void)initEngineAndJoinRoom{
/// 创建引擎 self.rtcKit = [[ByteRTCEngineKit alloc] initWithAppId:APPID delegate:self parameters:nil];
/// 填写上一个步骤中创建的 App Group ID
[self.rtcKit setExtensionConfig:APP_GROUP];
}
objectivec在新创建的 Target 中,Xcode 将自动创建 SampleHandler.h。参考如下代码在 SampleHandler.m 中实现屏幕采集逻辑。
#import "SampleHandler.h"
#import <VolcEngineRTCScreenCapturer/ByteRTCScreenCapturerExt.h>
@interface SampleHandler () <ByteRtcScreenCapturerExtDelegate>
@end
@implementation SampleHandler
- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
// 填写上一个步骤中创建的 App Group ID
// NSString *groupId = @"xxxxxxxxx";
// 开始屏幕采集
[[ByteRtcScreenCapturerExt shared] startWithDelegate:self groupId:groupId];
}
- (void)broadcastPaused {
// User has requested to pause the broadcast. Samples will stop being delivered.
}
- (void)broadcastResumed {
// User has requested to resume the broadcast. Samples delivery will resume.
}
- (void)broadcastFinished {
// User has requested to finish the broadcast.
// 结束屏幕采集
[[ByteRtcScreenCapturerExt shared] stop];
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
switch (sampleBufferType) {
case RPSampleBufferTypeVideo:// 采集到的屏幕视频流
case RPSampleBufferTypeAudioApp:// 采集到的设备音频流
[[ByteRtcScreenCapturerExt shared] processSampleBuffer:sampleBuffer withType:sampleBufferType];
break;
case RPSampleBufferTypeAudioMic:
// 采集到的麦克风音频流
break;
default:
break;
}
}
#pragma mark - ByteRtcScreenCapturerExtDelegate
// 通知 Broadcast Upload Extension 停止采集屏幕并退出。
- (void)onQuitFromApp {
NSDictionary *dic = @{
NSLocalizedFailureReasonErrorKey : @"您停止了屏幕共享"};
NSError *error = [NSError errorWithDomain:RPRecordingErrorDomain
code:RPRecordingErrorUserDeclined
userInfo:dic];
[self finishBroadcastWithError:error];
}
@end
objectivec接口调用时序如下图:

如果默认的编码参数不能满足你的要求,你可以在开始屏幕共享前,通过 setScreenVideoEncoderConfig 方法设置编码参数。
RTC 对屏幕源进行缩放时,不会改变原始宽高比。当宽高与源分辨率不一致时,
如果源分辨率大于目标分辨率,录制视频将被等比例缩小。
如果源分辨率小于目标分辨率,录制分辨率保持不变。
当宽高均为默认值 0 时,录制分辨率保持不变。但是,如果屏幕物理分辨率长边高于 1920 px,录制视频将被等比例缩小,长边将被缩小到 1920。
- (void)startScreenShare {
// 设置屏幕流视频参数(可选)
ByteRTCVideoEncoderConfig * config = [[ByteRTCVideoEncoderConfig alloc] init];
config.width = 0;
config.height = 0;
config.frameRate = 15;
config.maxKbps = -1;
[self.rtcKit SetScreenVideoEncoderConfig:config];
// 开启屏幕共享,选择同时采集音视频,或只采集音频/视频,bundleId 为扩展组件的 Bundle Identifier
[self.rtcKit startScreenCapture:ByteRTCScreenMediaTypeVideoAndAudio bundleId:EXTENSION_BUNDLE_ID];
// 更新采集类型,调整采集的媒体类型,例如静音等。
[self.rtcKit updateScreenCapture:ByteRTCScreenMediaTypeVideoAndAudio]
}
objectivec在 iOS 系统的控制中心,长按录屏按钮
在弹窗中选择你的应用,点击 开始直播,开始录屏
RTC 强烈建议你使用内部采集。如果你仍然希望使用自定义采集,参看以下步骤。
你需要自行实现屏幕音视频流采集逻辑。
调用 setScreenAudioSourceType 设置屏幕音频自定义采集。
调用 pushScreenAudioFrame 和 pushScreenCaptureFrame:time:rotation: 将采集得到的音视频帧推送到 RTC SDK 用于编码传输。
发布与停止发布屏幕流
- (void)rtcEngine:(ByteRTCEngineKit *)engine onVideoDeviceStateChanged:(NSString *)device_id device_type:(ByteRTCVideoDeviceType)device_type device_state:(ByteRTCMediaDeviceState)device_state device_error:(ByteRTCMediaDeviceError)device_error {
if (device_type == ByteRTCVideoDeviceTypeScreenCaptureDevice) {
if (device_state == ByteRTCMediaDeviceStateStarted) {
[engine publishScreen:ByteRTCMediaStreamTypeBoth];//发布屏幕共享流
} else if (device_state == ByteRTCMediaDeviceStateStopped ||
device_state == ByteRTCMediaDeviceStateRuntimeError) {
[engine unpublishScreen:ByteRTCMediaStreamTypeBoth];//关闭屏幕共享
}
}
}
objectivec//接收端监听回调,在本地渲染远端的屏幕共享流
- (void)rtcEngine:(ByteRTCEngineKit *)engine onFirstRemoteVideoFrameDecoded:(ByteRTCRemoteStreamKey *)streamKey withFrameInfo:(ByteRTCVideoFrameInfo *)frameInfo{
UIView *canvasView = [[UIView alloc] init];
ByteRTCVideoCanvas *canvas = [[ByteRTCVideoCanvas alloc] init];
canvas.view = canvasView;
canvas.renderMode = ByteRTCRenderModeHidden;
/// 设置远端用户视频渲染视图
[self.rtcKit setRemoteVideoCanvas:streamKey.userId withIndex:streamKey.streamIndex withCanvas:canvas];
}
objectivec- (void)stopScreenShare {
// 停止使用 RTC SDK 内部采集方式采集屏幕音视频
[self.rtcKit stopScreenCapture];
}
objectivec- (void)rtcEngine:(ByteRTCEngineKit *)engine onUserUnPublishStream:(NSString *)userId type:(ByteRTCMediaStreamType)type reason:(ByteRTCStreamRemoveReason)reason {
if (type != ByteRTCMediaStreamTypeAudio) {
dispatch_async(dispatch_get_main_queue(), ^{
// 移除远端用户屏幕流渲染
for (UserLiveView *liveView in self.containerView.subviews) {
if ([userId isEqualToString:liveView.uid]) {
liveView.uid = @"";
}
}
});
}
}
objectivec屏幕共享依赖于主进程向 RTC 服务端发布媒体流,建议采用播放静音文件等,在 App 进入后台进行保活。