1、应用内分享 即只能分享当前 App 的画面,该特性需要 iOS 13 及以上版本的操作系统才能支持。由于无法分享当前 App 之外的屏幕内容,因此适用于对隐私保护要求高的场景。
2、跨应用分享 基于苹果的 Replaykit 方案,能够分享整个系统的屏幕内容,但需要当前 App 额外提供一个 Extension 扩展组件,因此对接步骤也相对应用内分享要多一点。
| iOS | Android | Mac OS | Windows | Electron | 微信小程序 | Chrome 浏览器 |
|---|---|---|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ | ✓ | × | ✓ |
应用内分享的方案非常简单,只需要调用 AVD SDK 提供的接口[self.mscreen publishScreen:screen_resolution_1080p fps:10] 并传入分辨率参数 即可。
由于屏幕分享的内容一般不会剧烈变动,所以设置较高的 FPS 并不经济,推荐10 FPS即可。
如果您要分享的屏幕内容包含大量文字,可以适当提高分辨率和码率设置。
最高码率(videoBitrate)是指画面在剧烈变化时的最高输出码率,如果屏幕内容变化较少,实际编码码率会比较低。
iOS 系统上的跨应用屏幕分享,需要增加 Broadcast Upload Extension 录屏进程以配合主 App 进程进行推流。Extension 录屏进程由系统在需要录屏的时候创建,并负责接收系统采集到屏幕图像
该指南主要通过demo集成屏幕共享相关的功能。 包括:建立屏幕共享、开启屏幕共享、关闭屏幕共享;ReplayKit 2仅支持iOS 12.0 以上共享系统屏幕。基本流程是添加ReplayKit扩展、SDK创建加入房间、使用Socket在宿主App(主工程,这里是baseVideo)和扩展ReplayKit程序之间进行视频数据(音频使用宿主APP中AVD SDK采集)和控制指令传输
1、 在您的工程中,新建一个 Broadcast Upload Extension 的 Target

2、设置Extension 中bitcode 为NO

3、将avd_sdk.framework添加到Extension中并添加系统库 libc++.tbd

4、确认是否添加成功

5、在宿主app中添加avd_sdk.framework

6、在宿主App和Extension之间建立Socket连接,用于进程间发送视频帧数据,并且注册相同进程间通知用于处理宿主和extension直接相互传递消息
7、在宿主app初始化引擎或者初始化引擎成功回调中调用初始化Socket
xxxxxxxxxx- (void)onInitResult:(AVDResult)result { if (AVD_Success == result) {//初始化成功并设置相关参数 具体定义参考AVDEngine.h [[AVDClientBufferSocketManager sharedManager] setupSocket]; }else { }}8、注册Extension事件通知
xxxxxxxxxx- (void)addUploaderEventMonitor { //屏幕共享开始 [self registerForNotificationsWithIdentifier:@"broadcastStartedWithSetupInfo"]; //屏幕共享停止 [self registerForNotificationsWithIdentifier:@"broadcastFinished"]; //这里同时注册了纷发消息的通知,在宿主App中使用 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(broadcastInfo:) name:ScreenHoleNotificationName object:nil];}
//注册进程间通知- (void)registerForNotificationsWithIdentifier:(nullable NSString *)identifier { CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter(); CFStringRef str = (__bridge CFStringRef)identifier; CFNotificationCenterAddObserver(center, (__bridge const void *)(self), MyHoleNotificationCallback, str, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);}9、在Extension中SampleBuffer.m文件中调用接口初始化Socket和发送屏幕流
xxxxxxxxxx//// SampleHandler.m// uploadExtenion//// Created by 张启金 on 2020/3/31.// Copyright © 2020年 Elliot. All rights reserved.//
@interface SampleHandler ()
@property(nonatomic, assign)double nextFrameTime;
@end
@implementation SampleHandler
//屏幕开始直播时会回调这个方法,因此在此回调中初始化Socket并注册宿主App发送notification的监听- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo { // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter (), (__bridge const void *)(self), broadcastStopCallback,CFSTR("broadcastStop"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fishionBroadNotification:) name:@"FISHIONBROADCASTNOTIFICATION" object:nil]; [self sendNotificationForMessageWithIdentifier:@"broadcastStartedWithSetupInfo" userInfo:nil]; [[AVDSampleHandlerSocketManager sharedManager] setUpSocket]; self.nextFrameTime = 0; }
void broadcastStopCallback (CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo){ [[NSNotificationCenter defaultCenter] postNotificationName:@"FISHIONBROADCASTNOTIFICATION" object:nil userInfo:nil]; }
- (void)broadcastPaused { // User has requested to pause the broadcast. Samples will stop being delivered. [self sendNotificationForMessageWithIdentifier:@"broadcastPaused" userInfo:nil];
}
- (void)broadcastResumed { // User has requested to resume the broadcast. Samples delivery will resume. [self sendNotificationForMessageWithIdentifier:@"broadcastResumed" userInfo:nil];}
- (void)broadcastFinished { // User has requested to finish the broadcast. [self sendNotificationForMessageWithIdentifier:@"broadcastFinished" userInfo:nil]; CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), CFSTR("broadcastUnpublish"), NULL); [[NSNotificationCenter defaultCenter] removeObserver:self name:@"FISHIONBROADCASTNOTIFICATION" object:nil];}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { switch (sampleBufferType) { case RPSampleBufferTypeVideo:{ double currentTime = [[NSDate date] timeIntervalSince1970]*1000; if (currentTime < self.nextFrameTime) { return; } self.nextFrameTime = currentTime + frameTime; // Handle video sample buffer [[AVDSampleHandlerSocketManager sharedManager] sendVideoBufferToHostApp2:sampleBuffer]; } break; case RPSampleBufferTypeAudioApp: // Handle audio sample buffer for app audio break; case RPSampleBufferTypeAudioMic: // Handle audio sample buffer for mic audio break; default: break; }}
- (void)sendNotificationForMessageWithIdentifier:(nullable NSString *)identifier userInfo:(NSDictionary *)info { CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter(); CFDictionaryRef userInfo = (__bridge CFDictionaryRef)info; BOOL const deliverImmediately = YES; CFStringRef identifierRef = (__bridge CFStringRef)identifier; CFNotificationCenterPostNotification(center, identifierRef, NULL, userInfo, deliverImmediately);}
- (void)fishionBroadNotification:(NSNotification *)noti{ NSError *error = [NSError errorWithDomain:@"直播已经停止" code:-1088 userInfo:@{NSLocalizedDescriptionKey:@"您主动停止了直播"}]; [self finishBroadcastWithError:error];}
@end
10、ReplayKit 采集到的屏幕视频数据通过 processSampleBuffer:withType:给用户,忽略音频数据回调(我们使用SDK音频采集),将视频数据通过Socket发送到宿主App,然后再通过SDK自定义视频数据进行发送,因为横竖屏切换时屏幕采集帧较多会超过Extension内存限制50m的要求,所以需要在发生帧时做一个限制逻辑
xxxxxxxxxx- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { switch (sampleBufferType) { case RPSampleBufferTypeVideo:{ double currentTime = [[NSDate date] timeIntervalSince1970]*1000; if (currentTime < self.nextFrameTime) { return; } self.nextFrameTime = currentTime + frameTime; // Handle video sample buffer [[AVDSampleHandlerSocketManager sharedManager] sendVideoBufferToHostApp2:sampleBuffer]; } break; case RPSampleBufferTypeAudioApp: // Handle audio sample buffer for app audio break; case RPSampleBufferTypeAudioMic: // Handle audio sample buffer for mic audio break; default: break; }}11、应用层开启屏幕共享
xxxxxxxxxx//系统级别的屏幕共享点击界面上共享按钮是调用- (IBAction)startRecord:(UIButton *)sender { if (@available(iOS 12, *)) {//ios12 系统级别录制 if (!self.broadPickerView) { self.broadPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(-100,-100, 100, 100)]; self.broadPickerView.preferredExtension = @"cn.tee3.baseVideo.baseVideoExtension";//这里的id必须和Extension中的Bundle identiter一致 self.broadPickerView.showsMicrophoneButton = NO; [self.view addSubview:self.broadPickerView]; } for (UIView *subView in self.broadPickerView.subviews) { if ([subView isKindOfClass:[UIButton class]]) { [(UIButton *)subView sendActionsForControlEvents:UIControlEventAllEvents]; break; } } }else{ NSLog(@"ios12以下暂不支持系统级别的录制"); }}
Caution
苹果在 iOS 12.0 中增加了 RPSystemBroadcastPickerView 可以从应用中弹出启动器供用户确认启动屏幕分享,到目前为止, RPSystemBroadcastPickerView 尚不支持自定义界面,也没有官方的唤起方法。
TRTCBroadcastExtensionLauncher 的原理就是遍历 RPSystemBroadcastPickerView 的子 View 寻找 UIButton 并触发了其点击事件。
但该方案不被苹果官方推荐,并可能在新一轮的系统更新中失效
12、停止屏幕分享
xxxxxxxxxx- (IBAction)stopRecord:(UIButton *)sender {//这个是为了通知extension结束屏幕共享采集 CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter (); CFNotificationCenterPostNotification(notification, CFSTR("broadcastStop"), NULL,NULL, YES); [self.mscreen unpublishScreen];}13、用户主动关闭房间时需要通知Extension停止屏幕直播
xxxxxxxxxxCFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter (); CFNotificationCenterPostNotification(notification, CFSTR("broadcastStop"), NULL,NULL, YES);14、异常处理,比如用户主动杀掉应用进程需要监听应用程序被杀死然后通知Extension停止屏幕直播
xxxxxxxxxx [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
-(void)applicationWillTerminate{ // CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter (); CFNotificationCenterPostNotification(notification, CFSTR("broadcastStop"), NULL,NULL, YES); NSLog(@"程序被杀死"); }
Caution
注意:进程间通讯文档中只是示列代码,可以替换其他进程通讯的方法都行