录屏推流

最近更新时间:2022-04-14 21:33:51

功能介绍

手机录屏直播,即可以直接把主播的手机画面作为直播源,同时可以叠加摄像头预览,应用于游戏直播、移动端 App 演示等需要手机屏幕画面的场景。腾讯云 LiteAVSDK 通过 V2TXLivePusher 接口提供录屏推流能力,如下是 SDK API-Example 工程中演示录屏推流的相关操作界面:

限制说明

  • 录屏推流功能仅11.0以上系统可体验,本文主要介绍 iOS 11 的 ReplayKit2 录屏使用 SDK 推流的方法,涉及 SDK 的使用介绍同样适用于其它方式的自定义推流。更详细的使用说明可以参考 Demo 里 TXReplayKit_Screen 文件夹示例代码。
  • 录屏功能是 iOS 10 新推出的特性,苹果在 iOS 9 的 ReplayKit 保存录屏视频的基础上,增加了视频流实时直播功能,官方介绍见 Go Live with ReplayKit。iOS 11 增强为 ReplayKit2,进一步提升了 Replaykit 的易用性和通用性,并且可以对整个手机实现屏幕录制,并非只是支持 ReplayKit 功能,因此录屏推流建议直接使用 iOS 11 的 ReplayKit2 屏幕录制方式。系统录屏采用的是扩展方式,扩展程序有单独的进程,iOS 系统为了保证系统流畅,给扩展程序的资源相对较少,扩展程序内存占用过大也会被 Kill 掉。腾讯云 LiteAV SDK 在原有直播的高质量、低延迟的基础上,进一步降低系统消耗,保证了扩展程序稳定。

示例代码

针对开发者的接入反馈的高频问题,腾讯云提供有更加简洁的 API-Example 工程,方便开发者可以快速的了解相关 API 的使用,欢迎使用。

所属平台 GitHub 地址
iOS LivePushScreenViewController.m
Android LivePushScreenActivity.java
Flutter live_screen_push.dart

Xcode 准备

Xcode 9 及以上的版本,手机也必须升级至 iOS 11 以上,否则模拟器无法使用录屏特性。

创建直播扩展

在现有工程选择 New > Target…,选择 Broadcast Upload Extension,如图所示。

配置好 Product Name。单击 Finish 后可以看到,工程多了所输 Product Name 的目录,目录下有个系统自动生成的 SampleHandler 类,这个类负责录屏的相关处理。

对接攻略

1. 下载 SDK 开发包

下载 SDK 开发包,并按照 SDK 集成指引 将 SDK 嵌入您的 App 工程中。

2. 给 SDK 配置 License 授权

单击 License 申请 获取测试用 License,您会获得两个字符串:一个字符串是 licenseURL,另一个字符串是解密 key。
在您的 App 调用 LiteAVSDK 的相关功能之前(建议在 - [AppDelegate application:didFinishLaunchingWithOptions:] 中)进行如下设置:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSString * const licenceURL = @"<获取到的licenseUrl>";
    NSString * const licenceKey = @"<获取到的key>";

    // V2TXLivePremier 位于 "V2TXLivePremier.h" 头文件中
    [V2TXLivePremier setLicence:licenceURL key:licenceKey];
    [V2TXLivePremier setObserver:self];
    NSLog(@"SDK Version = %@", [V2TXLivePremier getSDKVersionStr]);
    return YES;
}

#pragma mark - V2TXLivePremierObserver
- (void)onLicenceLoaded:(int)result Reason:(NSString *)reason {
    NSLog(@"onLicenceLoaded: result:%d reason:%@", result, reason);
}

3. 初始化 V2TXLivePusher 组件

创建一个V2TXLivePusher对象,需要指定对应的 V2TXLiveMode

// 指定对应的直播协议为 RTMP,该协议不支持连麦
V2TXLivePusher *pusher = [[V2TXLivePusher alloc] initWithLiveMode:V2TXLiveMode_RTMP];
// 指定对应的直播协议为 RTC,该协议支持连麦。如果在直播过程中有连麦需求,需要选择该协议
V2TXLivePusher *pusher = [[V2TXLivePusher alloc] initWithLiveMode:V2TXLiveMode_RTC];

说明:

如果您有连麦需求,需要创建 V2TXLivePusher 对象时选择 RTC 协议。RTC 协议与 RTMP 协议在 API 使用上没有区别。

4. 配置 RPBroadcastSampleHandler

录屏需要使用系统 API RPBroadcastSampleHandler 的子类获取屏幕音视频数据,所以这里我们通过自定义子类 SampleHandler.m 中添加下面代码实现录屏推流:

#import "SampleHandler.h"
@import TXLiteAVSDK_ReplayKitExt;

@implementation SampleHandler
- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
    // App group ID,此处 ID 与 V2TXLivePusher startScreenCapture 对应,可以指定为 nil,但按照文档设置会使功能更加可靠。
    [[TXReplayKitExt sharedInstance] setupWithAppGroup:APPGROUP delegate:self]; 
}

- (void)broadcastPaused {
    // User has requested to pause the broadcast. Samples will stop being delivered.
    [[TXReplayKitExt sharedInstance] broadcastPaused];
}

- (void)broadcastResumed {
    // User has requested to resume the broadcast. Samples delivery will resume.
    [[TXReplayKitExt sharedInstance] broadcastResumed];
}

- (void)broadcastFinished {
    // User has requested to finish the broadcast.
    [[TXReplayKitExt sharedInstance] broadcastFinished];
}

- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
    [[TXReplayKitExt sharedInstance] sendSampleBuffer:sampleBuffer withType:sampleBufferType];
}

#pragma mark - TXReplayKitExtDelegate
- (void)broadcastFinished:(TXReplayKitExt *)broadcast reason:(TXReplayKitExtReason)reason
{
    NSString *tip = @"";
    switch (reason) {
        case TXReplayKitExtReasonRequestedByMain:
            tip = NSLocalizedString(@"MLVB-API-Example.liveStop", "");
            break;
        case TXReplayKitExtReasonDisconnected:
            tip = NSLocalizedString(@"MLVB-API-Example.appReset", "");
            break;
        case TXReplayKitExtReasonVersionMismatch:
            tip = NSLocalizedString(@"MLVB-API-Example.sdkError", "");
            break;
    }

    NSError *error = [NSError errorWithDomain:NSStringFromClass(self.class)
                                         code:0
                                     userInfo:@{
                                         NSLocalizedFailureReasonErrorKey:tip
                                     }];
    [self finishBroadcastWithError:error];
}

@end

  • RPBroadcastSampleHandler 的实现类中的需要调用对应 TXReplayKitExt 的方法来设置录屏信息和处理录屏事件。
  • broadcastFinished 回调可以获取录屏结束的原因,用以提示客户进一步处理操作。
  • 扩展与主 App 间的通信请参见 扩展与宿主 App 之间的通信与数据传递方式

5. 开启屏幕录制和推流

调用 startScreenCapture 启动屏幕录制,然后调用 V2TXLivePusher 中的 startPush 接口开始推流。

说明:

如果在 第3步 中选择 RTMP 协议推流,推流地址的生成请参见 RTMP 地址
如果在 第3步 中选择 RTC 协议推流,推流地址的生成请参见 RTC 地址

// appGroup 主 App 与 Broadcast 共享的 Application Group Identifier,可以指定为 nil,但按照文档设置会使功能更加可靠。
[livePusher startScreenCapture:@"group.com.xxx"]; 
[livePusher startMicrophone];

// 根据推流协议传入对应的 URL 即可启动推流, RTMP 协议以 rtmp:// 开头,该协议不支持连麦
NSString * const url = @"rtmp://test.com/live/streamid?txSecret=xxxxx&txTime=xxxxxxxx";
// 根据推流协议传入对应的 URL 即可启动推流, RTC 协议以 trtc:// 开头,该协议支持连麦
NSString * const url = @"trtc://cloud.tencent.com/push/streamid?sdkappid=1400188888&userId=A&usersig=xxxxx";
V2TXLiveCode code = [livePusher startPush:url];
if (code != V2TXLIVE_OK) {
    // 请检查错误码
}

返回 V2TXLIVE_ERROR_INVALID_LICENSE 的原因?

如果 startPush 接口返回 V2TXLIVE_ERROR_INVALID_LICENSE,则代表您的 License 校验失败了,请检查 第2步:给 SDK 配置 License 授权 中回调 onLicenceLoaded 的错误码和错误描述。

  • iOS 11 的系统上仅能通过下拉通知栏,长按录屏按钮的方式开启屏幕录制。
  • iOS 12 及以上的系统可以通过 RPSystemBroadcastPickerView 来弹出录屏选择界面,具体用法请参见 TRTCBroadcastExtensionLauncher.m

6. 横屏推流与分辨率设置

手机录屏直播提供了多个级别的分辨率可供选择。setVideoQuality 方法用来设置分辨率及横竖屏推流,以下录屏推流分辨率与横屏推流设置示例:

    BOOL landScape; //YES:横屏, NO:竖屏
    V2TXLiveVideoEncoderParam *videoParam = [[V2TXLiveVideoEncoderParam alloc] init];
    videoParam.videoResolution = V2TXLiveVideoResolution960x540;
    videoParam.videoResolutionMode = landScape ? V2TXLiveVideoResolutionModeLandscape : V2TXLiveVideoResolutionModePortrait;
    [livePusher setVideoQuality:videoParam];

7. 设置 Logo 水印

设置 V2TXLivePusher 中的 setWatermark 可以让 SDK 在推出的视频流中增加一个水印,水印位置位是由传入参数 (x, y, scale) 所决定。

  • SDK 所要求的水印图片格式为 PNG 而不是 JPG,因为 PNG 这种图片格式有透明度信息,因而能够更好地处理锯齿等问题(将 JPG 图片修改后缀名是不起作用的)。
  • (x, y, scale) 参数设置的是水印图片相对于推流分辨率的归一化坐标。假设推流分辨率为:540 × 960,该字段设置为:(0.1,0.1,0.1),那么水印的实际像素坐标为:(540 × 0.1,960 × 0.1,水印宽度 × 0.1,水印高度会被自动计算)。

// 设置视频水印
[livePusher setWatermark:image x:0 y:0 scale:1];

8. 结束推流

因为用于推流的 V2TXLivePusher 对象同一时刻只能有一个在运行,所以结束推流时要做好清理工作。

// 停止推流
[livePusher stopScreenCapture];
[livePusher stopPush];

事件处理

1. 事件监听

SDK 通过 V2TXLivePusherObserver 代理来监听推流相关的事件通知和错误通知,详细的事件表和错误码表请参见 错误码表

2. 错误通知

SDK 发现部分严重问题,推流无法继续。

事件 ID 数值 含义说明
V2TXLIVE_ERROR_FAILED -1 暂未归类的通用错误。
V2TXLIVE_ERROR_INVALID_PARAMETER -2 调用 API 时,传入的参数不合法。
V2TXLIVE_ERROR_REFUSED -3 API 调用被拒绝。
V2TXLIVE_ERROR_NOT_SUPPORTED -4 当前 API 不支持调用。
V2TXLIVE_ERROR_INVALID_LICENSE -5 license 不合法,调用失败。
V2TXLIVE_ERROR_REQUEST_TIMEOUT -6 请求服务器超时。
V2TXLIVE_ERROR_SERVER_PROCESS_FAILED -7 服务器无法处理您的请求。

3. 警告事件

SDK 发现部分警告问题,但 WARNING 级别的事件都会触发一些尝试性的保护逻辑或者恢复逻辑,而且有很大概率能够恢复。

事件 ID 数值 含义说明
V2TXLIVE_WARNING_NETWORK_BUSY 1101 网络状况不佳:上行带宽太小,上传数据受阻。
V2TXLIVE_WARNING_VIDEO_BLOCK 2105 当前视频播放出现卡顿
V2TXLIVE_WARNING_CAMERA_START_FAILED -1301 摄像头打开失败。
V2TXLIVE_WARNING_CAMERA_OCCUPIED -1316 摄像头正在被占用中,可尝试打开其他摄像头。
V2TXLIVE_WARNING_CAMERA_NO_PERMISSION -1314 摄像头设备未授权,通常在移动设备出现,可能是权限被用户拒绝了。
V2TXLIVE_WARNING_MICROPHONE_START_FAILED -1302 麦克风打开失败。
V2TXLIVE_WARNING_MICROPHONE_OCCUPIED -1319 麦克风正在被占用中,例如移动设备正在通话时,打开麦克风会失败。
V2TXLIVE_WARNING_MICROPHONE_NO_PERMISSION -1317 麦克风设备未授权,通常在移动设备出现,可能是权限被用户拒绝了。
V2TXLIVE_WARNING_SCREEN_CAPTURE_NOT_SUPPORTED -1309 当前系统不支持屏幕分享。
V2TXLIVE_WARNING_SCREEN_CAPTURE_START_FAILED -1308 开始录屏失败,如果在移动设备出现,可能是权限被用户拒绝了。
V2TXLIVE_WARNING_SCREEN_CAPTURE_INTERRUPTED -7001 录屏被系统中断。

附: 扩展与宿主 App 之间的通信与数据传递方式参考

ReplayKit2 录屏只唤起 upload 直播扩展,直播扩展不能进行 UI 操作,也不适于做复杂的业务逻辑,因此通常宿主 App 负责鉴权及其它业务逻辑,直播扩展只负责进行屏幕的音画采集与推流发送,扩展就经常需要与宿主 App 之间进行数据传递与通信。

1. 发本地通知

扩展的状态需要反馈给用户,有时宿主 App 并未启动,此时可通过发送本地通知的方式进行状态反馈给用户与激活宿主 App 进行逻辑交互,如在直播扩展启动时通知宿主 App:

- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
    [self sendLocalNotificationToHostAppWithTitle:@"腾讯云录屏推流" msg:@"录屏已开始,请从这里单击回到Demo->录屏幕推流->设置推流URL与横竖屏和清晰度" userInfo:@{kReplayKit2UploadingKey: kReplayKit2Uploading}];
}

- (void)sendLocalNotificationToHostAppWithTitle:(NSString*)title msg:(NSString*)msg userInfo:(NSDictionary*)userInfo
{
    UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];

    UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
    content.title = [NSString localizedUserNotificationStringForKey:title arguments:nil];
    content.body = [NSString localizedUserNotificationStringForKey:msg  arguments:nil];
    content.sound = [UNNotificationSound defaultSound];
    content.userInfo = userInfo;

    // 在设定时间后推送本地推送
    UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger
                                                  triggerWithTimeInterval:0.1f repeats:NO];

    UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"ReplayKit2Demo"
                                                                          content:content trigger:trigger];

    //添加推送成功后的处理!
    [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {

    }];
}

通过此通知可以提示用户回到主 App 设置推流信息、启动推流等。

2. 进程间的通知 CFNotificationCenter

扩展与宿主 App 之间还经常需要实时的交互处理,本地通知需要用户点击横幅才能触发代码处理,因此不能通过本地通知的方式。而 NSNotificationCenter 不能跨进程,因此可以利用 CFNotificationCenter 在宿主 App 与扩展之前通知发送,但此通知不能通过其中的 userInfo 字段进行数据传递,需要通过配置 App Group 方式使用 NSUserDefault 进行数据传递(也可以使用剪贴板,但剪贴板有时不能实时在进程间获取数据,需要加些延迟规避),如主 App 在获取好推流 URL 等后,通知扩展可以进行推流时,可通过 CFNotificationCenter 进行通知发送直播扩展开始推流:

CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(),
                                    kDarvinNotificationNamePushStart,
                                    NULL,
                                    nil,
                                    YES);
扩展中可通过监听此开始推流通知,由于此通知是在 CF 层,需要通过 NSNotificationCenter 发送到 Cocoa 类层方便处理:

    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                    (__bridge const void *)(self),
                                    onDarwinReplayKit2PushStart,
                                    kDarvinNotificationNamePushStart,
                                    NULL,
                                    CFNotificationSuspensionBehaviorDeliverImmediately);

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleReplayKit2PushStartNotification:) name:@"Cocoa_ReplayKit2_Push_Start" object:nil];


static void onDarwinReplayKit2PushStart(CFNotificationCenterRef center,
                                        void *observer, CFStringRef name,
                                        const void *object, CFDictionaryRef
                                        userInfo)
{
//转到 cocoa 层框架处理
    [[NSNotificationCenter defaultCenter] postNotificationName:@"Cocoa_ReplayKit2_Push_Start" object:nil];
}

- (void)handleReplayKit2PushStartNotification:(NSNotification*)noti
{
//通过 NSUserDefault 或剪贴板拿到宿主要传递的数据
//    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kReplayKit2AppGroupId];

    UIPasteboard* pb = [UIPasteboard generalPasteboard];
    NSDictionary* defaults = [self jsonData2Dictionary:pb.string];

    s_rtmpUrl = [defaults objectForKey:kReplayKit2PushUrlKey];
    s_resolution = [defaults objectForKey:kReplayKit2ResolutionKey];
    if (s_resolution.length < 1) {
        s_resolution = kResolutionHD;
    }
    NSString* rotate = [defaults objectForKey:kReplayKit2RotateKey];
    if ([rotate isEqualToString:kReplayKit2Portrait]) {
        s_landScape = NO;
    }
    else {
        s_landScape = YES;
    }
    [self start];
}

常见问题

ReplayKit2 屏幕录制在 iOS 11 新推出功能,相关的官方文档比较少,且存在着一些问题,使得每个版本的系统都在不断修复完善中。以下是一些使用中的常见现象或问题:

  1. 屏幕录制何时自动会停止?
    系统在锁屏或有电话打入时,会自动停止屏幕录制,此时 SampleHandler 里的 broadcastFinished 函数会被调用,可在此函数发通知提示用户。

  2. 采集推流过程中有时屏幕录制会自动停止问题?
    通常是因为设置的推流分辨率过高时在做横竖屏切换过程中容易出现。ReplayKit2 的直播扩展目前是有50M的内存使用限制,超过此限制系统会直接杀死扩展进程,因此 ReplayKit2 上建议推流分辨率不高于720P。

  3. iPhoneX 手机的兼容性与画面变形问题?
    iPhoneX 手机因为有刘海,屏幕采集的画面分辨率不是 9:16。如果设了推流输出分辨率为 9:16 的比例,如高清里是为 960 × 540 的分辨率,这时因为源分辨率不是 9:16 的,推出去的画面就会稍有变形。建议设置分辨率时根据屏幕分辨率比例来设置,拉流端用 AspectFit 显示模式 iPhoneX 的屏幕采集推流会有黑边是正常现象,AspectFill 看画面会不全。

目录