You need to enable JavaScript to run this app.
导航

iOS 端屏幕共享

最近更新时间2023.12.13 16:13:03

首次发布时间2022.07.14 17:30:18

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

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

适用场景

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

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

前提条件

其中,iOS 12 ~ iOS 12.2 之间版本需要集成 RTC SDK v3.52 或更高版本。

功能实现

alt

iOS 端基于苹果提供的 Replaykit 框架实现屏幕录制,可以分享整个系统的屏幕内容。但由于苹果的隐私设置,不同 App 之间数据无法互通,因此需要当前 App(主 App 进程)额外提供一个 Extension 扩展组件(Extension 进程),并且把 App 和 Extension 配置为同一 App Group,让 Extension 录屏进程可以同主 App 进程进行跨进程通信,实现屏幕内容分享。

步骤一:创建 App Group

为使 Extension 录屏进程可以和主 App 进程进行跨进程通信,需要将 Extension 和 App 配置为同一 App Group。参看如何创建和配置 App Group

步骤二:创建 Extension 扩展组件

新建 Broadcast Upload Extension 组件并进行相关配置

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

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

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

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

  4. 将 RTC SDK 的屏幕共享插件引入到工程中。你可以下载 iOS SDK 包,将其中的 VolcEngineRTCScreenCapturer.framework 文件拖动到工程中,也可以在项目 Podfile 文件添加如下代码:

    target 'ScreenShareExtension' do
    use_frameworks!
    pod 'VolcEngineRTC', '3.xx.xx', :subspecs => ['ScreenCapture']
    end
    

步骤三:在 Extension 组件中实现屏幕采集逻辑

参考如下代码在 SampleHandler.m 文件中实现屏幕采集逻辑。在新创建的 Target 中,Xcode 将自动创建 SampleHandler.h 文件。

#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

步骤四:为 RTC Video 设置 App Group ID

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

- (void)initEngineAndJoinRoom{
    /// 创建引擎 
    self.rtcVideo = [ByteRTCVideo createRTCVideo:APPID delegate:self parameters:@{}];
    /// 填写步骤一中创建的 App Group ID
    [self.rtcVideo setExtensionConfig:APP_GROUP];
}

步骤五:调用 RTC 接口共享屏幕

内部采集

接口调用时序如下图:

API flow

【可选】设置编码参数

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

  • RTC 对屏幕源进行缩放时,不会改变原始宽高比。当宽高与源分辨率不一致时,

    • 如果源分辨率大于目标分辨率,录制视频将被等比例缩小。

    • 如果源分辨率小于目标分辨率,录制分辨率保持不变。

  • 当宽高均为默认值 0 时,录制分辨率保持不变。但是,如果屏幕物理分辨率长边高于 1920 px,录制视频将被等比例缩小,长边将被缩小到 1920

开启屏幕采集
方式一:在代码中开启采集。
- (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];
}
方式二:从系统控制中心发起。
  1. 在 iOS 系统的控制中心,长按录屏按钮

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

自定义采集屏幕视频流

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

  1. 实现屏幕音视频流采集逻辑。

  2. 指定为外部输入源。

  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;
            });
        }
    }
}

远端渲染屏幕流

//接收端监听回调,在本地渲染远端的屏幕共享流
- (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];
        });
    }
    
}

停止屏幕共享

- (void)stopScreenShare {
    // 停止使用 RTC SDK 内部采集方式采集屏幕音视频
    [self.rtcVideo stopScreenCapture];
    [self.rtcRoom unpublishScreen:ByteRTCMediaStreamTypeBoth];
}

远端移除屏幕流

- (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 = @"";
                }
            }
        });
    }
}

常见问题

  1. App 进入后台后,如何防止共享中断?

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

  1. 如何以 pod 形式引入?
source 'https://github.com/volcengine/volcengine-specs.git'
target 'your target' 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 参考

功能变更日志

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

  • 3.50.1 版本中
    • 设置共享流的编码参数类型名称变更为 ByteRTCScreenVideoEncoderConfig
    • 自定义屏幕流相关的接口名称和行为有变更。
  • 3.43.1 版本中
    • publishScreen:unpublishScreen: 的类名由 ByteRTCEngineKit 变更为 RTCRoom。其他方法的类名变更为 RTCVideo
    • 回调类名由 ByteRTCEngineDelegate 变更为 RTCVideoDelegate
  • 3.36.1 版本中
    • 设置共享流的编码参数方法由 SetVideoEncoderConfig: 变更为 SetScreenVideoEncoderConfig:
    • PublishScreen: 等 API 的参数有变更。