在实时通信中,如果你希望用户可以分享本端设备的屏幕和设备播放的音频,可以使用 RTC 内建的屏幕采集功能,也可以自行实现屏幕采集逻辑(自定义采集),并通过屏幕共享功能,与远端用户共享。

仅可见的用户可以发布屏幕流。

适用场景

你可以在多种行业、多种场景下使用屏幕共享功能:

行业场景
在线教育老师共享屏幕给学生上课;美术老师共享屏幕给学生教画画。
游戏直播主播共享屏幕给观众,展现自己的游戏画面。
互动直播主播共享自己的屏幕和观众互动。
视频会议会议成员共享屏幕观看 PPT 或者文档。
功能变更日志

本文最近更新时的 SDK 版本为 3.50.1。如果你使用的 SDK 为之前版本,请查看以下变动,并进行相应适配。

前提条件
功能实现

alt

通过苹果的 Replaykit 组件进行录屏。由于苹果的隐私设置,不同 App 之间数据无法互通,因此,需要利用 Broadcast Upload Extension 组件,把组件和 App 配置为同一 App Group,以实现了 iOS 端的屏幕内容分享。

创建 Broadcast Upload Extension 组件

新建 Broadcast Upload Extension 的 Target,并集成 RTC SDK 提供的 VolcEngineRTCScreenCapturer.framework

  1. 在 Xcode 中,点击 File > New > Target...,在弹出对话框中选择 Broadcast Upload Extension,点击 Next。

  2. 填写相关信息,取消勾选 Broadcast Setup UI Extension,点击 Finish 完成创建。

    RTC 暂不支持 Broadcast Setup UI Extension。如需开启该扩展,请确认已自行实现相关逻辑。

  3. 选择刚创建的 Target,进行配置。
    Bundle Identifier 必须以你的 App target 的 Bundle Identifier 为前缀。

  4. 将 VolcEngineRTCSDK 包中的 VolcEngineRTCScreenCapturer.framework 拖动到工程中。

配置相同的 App Group
  1. 为使 Extension 录屏进程可以和 App 进程进行跨进程通信,需要将 Extension 录屏进程和 App 分别配置为同一 App Group。参见如何创建和配置 App Group

  2. 创建 RTC 引擎后,调用 setExtensionConfig: 接口,填写上一个步骤中创建的 App Group ID,响应从系统的控制中心发起的屏幕共享。

- (void)initEngineAndJoinRoom{
    /// 创建引擎 
    self.rtcVideo = [ByteRTCVideo createRTCVideo:APPID delegate:self parameters:@{}];
    /// 填写上一个步骤中创建的 App Group ID
    [self.rtcVideo 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
调用 RTC 接口共享屏幕
内部采集

接口调用时序如下图:

Sequence

【可选】设置编码参数

如果默认的编码参数不能满足你的要求,你可以在开始屏幕采集前,通过 setScreenVideoEncoderConfig 方法设置编码参数。

开启屏幕采集
方式一:在代码中开启采集。
- (void)startScreenShare {
    // 设置屏幕流视频参数(可选)
    ByteRTCScreenVideoEncoderConfig * config = [[ByteRTCScreenVideoEncoderConfig alloc] init];
    config.width = self.roomSetting.resolution.width;
    config.height = self.roomSetting.resolution.height;
    config.frameRate = self.roomSetting.fps;
    config.maxBitrate = self.roomSetting.bitrate;
    config.minBitrate = 0;
    [self.rtcVideo setScreenVideoEncoderConfig:config];
        
    // 开启屏幕共享
    [self.rtcVideo startScreenCapture:ByteRTCScreenMediaTypeVideoAndAudio bundleId:EXTENSION_BUNDLE_ID];
}
objectivec
方式二:从系统控制中心发起。
  1. 在 iOS 系统的控制中心,长按录屏按钮

  2. 在弹窗中选择你的应用,点击 开始直播,开始录屏

自定义采集屏幕视频流

RTC 强烈建议你使用内部采集。如果你仍然希望使用自定义采集,参看以下步骤。

  1. 你需要自行实现屏幕音视频流采集逻辑。

  2. 调用 setScreenAudioSourceType: 设置屏幕音频自定义采集。

  3. 调用 pushScreenAudioFrame: 和 pushScreenVideoFrame:time:rotation: 将采集得到的音视频帧推送到 RTC SDK 用于编码传输。

发布/停止发布屏幕流

发布与停止发布屏幕流

// 在ByteRTCVideoDelegate中,发布与停止发布屏幕流
- (void)rtcEngine:(ByteRTCVideo *)engine onVideoDeviceStateChanged:(NSString *)device_id device_type:(ByteRTCVideoDeviceType)device_type device_state:(ByteRTCMediaDeviceState)device_state device_error:(ByteRTCMediaDeviceError)device_error {
    if (device_type == ByteRTCVideoDeviceTypeScreenCaptureDevice && self.preJoinSetting.isScreenShare) {
        
        if (device_state == ByteRTCMediaDeviceStateStarted) {
            [_rtcRoom publishScreen:ByteRTCMediaStreamTypeBoth];//发布屏幕共享
                
            dispatch_async(dispatch_get_main_queue(), ^{
                self.localView.uid = @"正在共享";
                self.screenShareBtn.selected = YES;
                self.screenShareBtn.imageView.backgroundColor = UIColor.clearColor;
            });
        } else if (device_state == ByteRTCMediaDeviceStateStopped ||
                   device_state == ByteRTCMediaDeviceStateRuntimeError) {
            [_rtcRoom unpublishScreen:ByteRTCMediaStreamTypeBoth];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.localView.uid = @"等待屏幕共享";
                self.screenShareBtn.selected = NO;
                self.screenShareBtn.imageView.backgroundColor = UIColor.lightGrayColor;
            });
        }
    }
}
objectivec
远端渲染屏幕流
//接收端监听回调,在本地渲染远端的屏幕共享流
- (void)rtcRoom:(ByteRTCRoom *)rtcRoom onUserPublishScreen:(NSString *)userId type:(ByteRTCMediaStreamType)type {
    if (type == ByteRTCMediaStreamTypeVideo || type == ByteRTCMediaStreamTypeBoth) {
        ByteRTCRemoteStreamKey *streamKey = [[ByteRTCRemoteStreamKey alloc] init];
        streamKey.userId = userId;
        streamKey.streamIndex = ByteRTCStreamIndexScreen;
        streamKey.roomId = self.roomID;
        
        dispatch_async(dispatch_get_main_queue(), ^{
            UIView *view = [[UIView alloc] init];
            ByteRTCVideoCanvas *canvas = [[ByteRTCVideoCanvas alloc] init];
            canvas.view = view;
            canvas.renderMode = ByteRTCRenderModeHidden;
            canvas.uid = userId;
            [self.rtcVideo setRemoteVideoCanvas:streamKey withCanvas:canvas];
        });
    }
    
}
objectivec
停止屏幕共享
- (void)stopScreenShare {
    // 停止使用 RTC SDK 内部采集方式采集屏幕音视频
    [self.rtcVideo stopScreenCapture];
    [self.rtcRoom unpublishScreen:ByteRTCMediaStreamTypeBoth];
}
objectivec
远端移除屏幕流
- (void)rtcRoom:(ByteRTCRoom *)rtcRoom 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
常见问题
  1. App 进入后台后,如何防止共享中断?

屏幕共享依赖于主进程向 RTC 服务端发布媒体流,建议采用播放静音文件等,在 App 进入后台进行保活。

  1. 如何以 pod 形式引入?
source 'https://github.com/volcengine/volcengine-specs.git'
target 'your taget' do
  pod 'VolcEngineRTC', '3.xx.xxx'
end

// 屏幕共享模块
target 'ScreenShareExtension' do
  use_frameworks!
  pod 'VolcEngineRTC', '3.xx.xxx', :subspecs => ['ScreenCapture']
end

其中,3.xx.xxx 为 RTC SDK 的版本号

API 参考