屏幕共享
更新时间: 2024/08/05 15:02:55
在大型会议或在线教育等场景中,为了满足提升沟通效率的需求,主讲人或老师需要将本端的屏幕内容分享给远端参会者或在线学生观看。NERTC 支持屏幕共享功能,帮助您实时分享本端设备的屏幕内容。
功能介绍
通过 NERTC SDK 可以在视频通话或互动直播过程中实现屏幕共享,主播或连麦者可以将自己的屏幕内容,以视频的方式分享给远端参会者或在线观众观看,从而提升沟通效率,一般适用于多人视频聊天、在线会议以及在线教育场景。
-
视频会议场景中,参会者可以在会议中将本地的文件、数据、网页、PPT 等画面分享给其他与会者,让其他与会者更加直观的了解讨论的内容和主题。
-
在线课堂场景中,老师可以通过屏幕共享将课件、笔记、教学内容等画面展示给远端的其他学生观看,降低传统教学模式下的沟通成本,提升教育场景的用户体验。
NERTC SDK 以辅流的形式实现屏幕共享,即单独为屏幕共享开启一路上行的视频流,摄像头的视频流作为主流,屏幕共享的视频流作为辅流,两路视频流并行,主播同时上行摄像头画面和屏幕画面两路画面。
此外 NERTC SDK 还支持在共享屏幕的同时,也共享本地播放的系统背景音。具体请参考音频共享。
示例项目
网易云信提供 ScreenShare 示例项目源码,您可以参考该源码实现屏幕共享。
注意事项
-
在实现屏幕共享功能前,请先确保以下开发环境:
- Visual Studio 2013 及以上版本。
- Windows7、Windows8、Windows10 或以上版本。
-
NERTC Android、iOS、Windows 和 macOS SDK V3.9.0 及以上版本,Web SDK V4.1.0 及以上版本支持通过辅流实现屏幕共享。如果使用辅流的屏幕共享方案,请保证房间内所有成员均升级到支持版本以上,否则互相通信时会因同时发送主流和辅流造成通话异常等问题。
-
如果您的 App 无法针对所有端进行强制升级,屏幕共享场景中仅部分端使用 V3.9.0 及以上版本,为避免上述通话异常问题,必须保证通话过程中单人同时只有一路上行视频流。当需要将视频流切换为屏幕共享流时,请先通过
enableLocalVideo
关闭视频流,再通过startScreenCaptureByScreenRect
或其他屏幕共享相关接口启动屏幕共享流。反向切换同理。 -
对于 V5.3.0 及之后版本,如果当前正在使用本地视频辅流通道进行本地摄像头采集或者外部自定义视频输入,调用
startScreenCaptureByScreenRect
或startScreenCaptureByWindowId
开启屏幕共享时,需要先调用enableLocalVideo
停止辅流。如果当前正在屏幕共享,调用enableLocalVideo
开启辅流时,需要调用stopScreenCapture
先停止屏幕共享。
本端共享屏幕
API 调用时序
实现方法
-
获取指定的待共享区域在整个显示器屏幕中的 Rectangle 坐标。
示例代码如下:
bool NRTCEngine::startMonitorShare(const uint32_t &screenIndex) { #ifdef Q_OS_WIN32 auto screens = QGuiApplication::screens(); auto screen = screens.at(screenIndex); nertc::NERtcRectangle sourceRectangle; sourceRectangle.x = screen->geometry().x(); sourceRectangle.y = screen->geometry().y(); sourceRectangle.width = screen->geometry().width() * screen->devicePixelRatio(); sourceRectangle.height = screen->geometry().height() * screen->devicePixelRatio(); nertc::NERtcRectangle regionRectangle = { 0, 0, 0, 0 }; nertc::NERtcScreenCaptureParameters params; params.bitrate = 0; params.frame_rate = 5; params.profile = nertc::kNERtcScreenProfileMAX; params.capture_mouse_cursor = true; params.dimensions.width = sourceRectangle.width - sourceRectangle.x; params.dimensions.height = sourceRectangle.height - sourceRectangle.y; }
-
设置屏幕共享辅流画布。
- 初始化后通过
setupLocalSubStreamVideoCanvas
设置本端的辅流视频回放画布。 - (可选)通过
setLocalSubStreamRenderMode
修改本端的辅流渲染缩放模式。
- 初始化后通过
-
开启屏幕共享。 加入房间后,根据需求开启屏幕共享并设置屏幕共享的方式,屏幕共享内容以辅流形式发送。
startScreenCaptureByScreenRect
:开启屏幕共享,共享范围为指定屏幕的指定区域。startScreenCaptureByWindowId
:开启屏幕共享,共享范围为指定窗口的指定区域。
对于 V5.3.0 及之后版本:
- 如果当前正在使用本地视频辅流通道进行本地摄像头采集或者外部自定义视频输入,调用
startScreenCaptureByScreenRect
或startScreenCaptureByWindowId
开启屏幕共享时,需要先调用enableLocalVideo
停止辅流。 - 如果当前正在屏幕共享,调用
enableLocalVideo
开启辅流时,需要调用stopScreenCapture
先停止屏幕共享。
您需要设置
capture_params
配置屏幕共享的编码参数,其NERtcScreenCaptureParameters
结构体的参数说明如下表所示。参数 参数说明 prefer 屏幕共享编码策略倾向: kNERtcSubStreamContentPreferMotion
(默认):内容类型为动画。当用户共享的内容是视频、电影或游戏等动态画面时,推荐选择此枚举值;此时frame_rate
参数完全按照您的设置处理。kNERtcSubStreamContentPreferDetails
:内容类型为细节。当用户共享的内容是图片、文字或 PPT 等静态画面时,推荐选择此枚举值;此时frame_rate
参数最高可设置为 10 帧。
profile 视频编码的分辨率。
具体请参考NERtcScreenProfileType
。- 建议直接设置为
kNERtcScreenProfileCustom
,以确保所有设置符合您的真实业务场景。 - 若您需要使用自定义的尺寸(
dimensions
)和帧率(frame_rate
),请务必设置此参数为kNERtcScreenProfileCustom
。 - 若设置
profile
为kNERtcScreenProfileCustom
之外的值,尺寸dimensions
会自动对应为profile
指定的大小,帧率frame_rate
固定为 5 fps。
dimensions 视频编码的最大像素值,可以设置视频尺寸的宽、高。 此参数仅当 profile
参数设置为kNERtcScreenProfileCustom
有效。frame_rate 视频编码的帧率。
单位为 fps,默认值为 5,建议此参数的值不要超过 15。min_framerate 视频编码的最小帧率。
默认值为 0,表示使用默认的最小帧率。bitrate 视频编码的码率,单位为 Kbps。
若设置的码率为 0 或超出合理范围,SDK 会自行计算出合理区间处理码率,具体请参考分辨率、帧率、码率参照表。min_bitrate 视频编码的最小码率,单位为 Kbps。 excluded_window_count 待屏蔽窗口的数量。 excluded_window_list 待屏蔽窗口的列表。 capture_mouse_cursor 是否采集鼠标用于屏幕共享。 此外,网易云信提供一些常见场景下的参数推荐搭配值,供您参考,具体如下表所示。
参数名称 共享视频 共享 PPT prefer kNERtcSubStreamContentPreferMotion kNERtcSubStreamContentPreferDetails profile kNERtcScreenProfileCustom kNERtcScreenProfileCustom dimensions {1920, 1080} {1920, 1080} frame_rate 15 5 region_rect
参数的坐标值请通过步骤一获取,具体如何设置请参考设置屏幕共享的窗口范围和枚举屏幕列表或窗口列表。- 开启屏幕共享之后,本地会触发
onScreenCaptureStatus
回调,远端会触发onUserSubStreamVideoStart
回调。
-
管理屏幕共享任务。
updateScreenCaptureParameters
:更新屏幕共享参数。pauseScreenCapture
:暂停屏幕共享。resumeScreenCapture
:恢复屏幕共享。updateScreenCaptureRegion
:更新屏幕共享区域。setExcludeWindowList
:设置共享指定屏幕区域时,需要屏蔽的窗口列表。setScreenCaptureMouseCursor
:设置屏幕共享时是否显示鼠标。
-
关闭屏幕共享。 通过
stopScreenCapture
关闭辅流形式的屏幕共享。此时本地会触发onScreenCaptureStatus
回调,远端会触发onUserSubStreamVideoStop
回调。
若您在屏幕共享过程中,SDK 触发 kScreenCaptureStatusAbort
回调,这是由于当前共享的窗口被关闭、进程崩溃等原因导致目标窗口无效。您需要在此事件的响应函数中调用 stopScreenCapture
结束屏幕共享进程。
示例代码
cpp// 设置本地辅流画布
nertc::NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = window;
canvas.scaling_mode = nertc::kNERtcVideoScaleFit;
rtc_engine_->setupLocalSubStreamVideoCanvas(&canvas);
// 更新辅流画布缩放模式
rtc_engine_->setLocalSubStreamRenderMode(nertc::kNERtcVideoScaleCropFill);
// 获取本地辅流启动参数,下面举了两种类型的例子:动画模式和细节模式
// case 1: 动画模式 (流畅度优先),共享视频等高帧率场景
nertc::NERtcScreenCaptureParameters capture_params;
capture_params.profile = nertc::kNERtcScreenProfileCustom; // 建议全部使用custom模式,不要使用预置的profile。
capture_params.dimensions = {1920, 1080};
capture_params.frame_rate = 15; // 动画模式最高支持30fps,具体设置多少帧,可参考实际业务和测试效果
capture_params.bitrate = 0;
capture_params.capture_mouse_cursor = true;
capture_params.window_focus = true;
// 设置过滤窗口,这里假设exclude_wnd_list_中保存了客户代码中设置的过滤窗口列表
HWND* wnd_list = nullptr;
int index = 0;
if (!exclude_wnd_list_.empty()) {
// 记得在调用了 startScreenCaptureBy*** 系列函数之后,释放内存 wnd_list
wnd_list = new HWND[exclude_wnd_list_.size()];
for (auto e : exclude_wnd_list_) {
*(wnd_list + index++) = e;
}
}
capture_params.excluded_window_list = (nertc::source_id_t*)wnd_list;
capture_params.excluded_window_count = exclude_wnd_list_.size(); //指定排除窗口的个数
capture_params.prefer = nertc::kNERtcSubStreamContentPreferMotion; // 设置动画模式
// case 2: 细节模式 (清晰度优先)共享文档/ppt推荐
nertc::NERtcScreenCaptureParameters capture_params;
capture_params.profile = nertc::kNERtcScreenProfileCustom;
capture_params.dimensions = {1920, 1080};
capture_params.frame_rate = 5; // 细节模式最高支持10fps
capture_params.bitrate = 0;
capture_params.capture_mouse_cursor = true;
capture_params.window_focus = true;
// 设置过滤窗口,这里假设exclude_wnd_list_中保存了客户代码中设置的过滤窗口列表
HWND* wnd_list = nullptr;
int index = 0;
if (!exclude_wnd_list_.empty()) {
// 记得在调用了 startScreenCaptureBy*** 系列函数之后,释放内存 wnd_list
wnd_list = new HWND[exclude_wnd_list_.size()];
for (auto e : exclude_wnd_list_) {
*(wnd_list + index++) = e;
}
}
capture_params.excluded_window_list = (nertc::source_id_t*)wnd_list;
capture_params.excluded_window_count = exclude_wnd_list_.size(); //指定排除窗口的个数
capture_params.prefer = nertc::kNERtcSubStreamContentPreferDetails;// 设置细节模式
// 启动辅流 case 1:根据指定区域选择某一个屏幕的分享
// 比如当前系统接入了 2 台显示器,坐标分别是 display1: {x=0, y=0, width=1920, height=1080}, display2: {x=1920, y=0, width=1920, height=1080}
// 当 screen_rect 设置为 {0,0,0,0} 是表示选择整个虚拟桌面,即采集范围是 {x=0,y=0,width=3840,height=1080}
// 当 screen_rect 设置为 {0,0,1920,1080} ,则表示选择display1;同理,设置为 {1920,0,1920,1080}就表示采集display2
// 当 region_rect 设置为 {0,0,0,0} 时,表示采集范围时整个指定的 screen_rect 大小;如果设置了某一个范围,比如 {x=200,y=30, width=800, height=600},则表示在 screen_rect 内,采集 左下角坐标为 {x=200, y=30},大小为 {width=800, height=600} 的范围。
// 以共享 display1 的整个区域为例
nrtc_engine_->startScreenCaptureByScreenRect({ 0,0,1920,1080 }, { 0,0,0,0 }, capture_params);
// 启动辅流 case 2:根据特定窗口句柄,选择应用窗口分享
// 当 region_rect 设置为 {0,0,0,0} 时,表示采集整个窗口大小。当设置了一个范围,则表示仅分享设定范围内的窗口画面
nrtc_engine_->startScreenCaptureByWindowId(hwnd, { 0,0,0,0 }, capture_params);
// 如果设置了过滤窗口,那么这里可能是一个比较好的释放内存的地方
if (wnd_list) {
delete[] wnd_list;
wnd_list = nullptr;
}
//更新屏幕共享参数
nrtc_engine_->updateScreenCaptureParameters(captureParams);
// 暂停屏幕共享
nrtc_engine_->pauseScreenCapture();
// 恢复屏幕共享
nrtc_engine_->resumeScreenCapture();
// 更新取屏区域
nrtc_engine_->updateScreenCaptureRegion({ 0,0,640,480 });
// 更新屏幕共享时的鼠标显示状态
nrtc_engine_->setScreenCaptureMouseCursor(true);
// 停止屏幕共享
nrtc_engine_->stopScreenCapture();
观看远端屏幕共享
API 调用时序
实现方法
-
设置远端辅流画布。
- 远端用户加入房间时,可以通过
onUserJoined
事件获取远端用户 ID,并通过setupRemoteSubStreamVideoCanvas
设置指定远端用户的的辅流视频画布。 - (可选)通过
setRemoteSubSteamRenderMode
设置远端的屏幕共享辅流视频渲染缩放模式。
- 远端用户加入房间时,可以通过
-
订阅远端用户的屏幕共享流。
- 收到
onUserSubStreamVideoStart
远端用户开启屏幕共享辅流通道的回调。 - 通过
subscribeRemoteVideoSubStream
订阅远端的屏幕共享辅流视频,订阅之后才能接收远端的辅流视频数据。
- 收到
-
结束屏幕共享。
收到
onUserSubStreamVideoStop
其他用户关闭辅流的回调,结束屏幕共享。
示例代码
cpp// 远端辅流处理-------------------------------------------
// 设置远端辅流画布
nertc::NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = window;
canvas.scaling_mode = nertc::kNERtcVideoScaleFit;
rtc_engine_->setupRemoteSubStreamVideoCanvas(uid, &canvas);
// 更新远端辅流画布缩放模式
rtc_engine_->setRemoteSubSteamRenderMode(uid, nertc::kNERtcVideoScaleCropFill);
// 监听远端辅流开启
void onUserSubStreamVideoStart(nertc::uid_t uid, nertc::NERtcVideoProfileType max_profile) override{
//订阅远端辅流
rtc_engine_->subscribeRemoteVideoSubStream(uid, true);
//取消订阅远端辅流
rtc_engine_->subscribeRemoteVideoSubStream(uid, false);
}
//监听远端辅流停止
void onUserSubStreamVideoStop(uid_t uid) override{
// 取消远端辅流画布
rtc_engine_->setupRemoteSubStreamVideoCanvas(uid, nullptr);
}
设置屏幕共享的窗口范围
屏幕共享时,您可能需要限制共享屏幕的窗口范围,例如部分涉及敏感信息的窗口区域不进行屏幕共享。NERTC SDK 支持设置共享屏幕的区域范围,目前可通过以下方式实现:
-
开启屏幕共享时,仅共享指定窗口的部分区域。
通过
startScreenCaptureByWindowId
开启窗口维度的屏幕共享,并通过参数region_rect
指定共享的窗口范围。 -
开启屏幕共享时,仅共享指定屏幕的部分区域,并排除部分窗口。
通过
startScreenCaptureByScreenRect
开启屏幕维度的屏幕共享,并通过参数region_rect
指定共享的屏幕范围、通过NERtcScreenCaptureParameters
设置需要过滤的窗口列表。 -
屏幕共享过程中,动态调整需要屏蔽的窗口列表。
共享整个屏幕时,您可以通过
setExcludeWindowList
设置窗口过滤,将一个或多个需要过滤的窗口或应用排除出共享范围,其他观看端只能看到整个屏幕中指定窗口以外的其他内容。
枚举屏幕列表或窗口列表
NERTC SDK 暂不支持枚举屏幕列表或窗口列表,您需要在应用层自行实现枚举屏幕的方法。 如果您基于 Qt 来开发音视频能力,请注意 Qt 中通过 QWidget::winId() 得到的 WId 类型的值在不同平台上有差异:
- Qt 在 Windows 平台上的实现返回的是窗口的句柄 HWND,即窗口 ID,可以直接用于通过 SDK 调用
startScreenCaptureByWindowId
等方法时设置 window_id 参数。 - Qt 在 macOS 平台上的实现返回的是 NSView 对象指针,而非窗口 ID,调用 SDK 方法需要的是 NSWindow 窗口的 ID,即成员 windowNumber。若您需要通过 WId 类型的值获取 macOS 平台窗口的 ID,可以参考以下代码。
/////// file: macx_helper.h
#ifndef HIDETITLEBAR_H
#define HIDETITLEBAR_H
#include <QQuickWindow>
#include <QScreen>
#include <QGuiApplication>
class MacXHelpers : public QObject
{
Q_OBJECT
public:
MacXHelpers() {}
public slots:
int getWindowId(WId wid);
};
#endif // HIDETITLEBAR_H
/////// file: macx_helper.mm
#include "macx_helpers.h"
#import <AppKit/AppKit.h>
int MacXHelpers::getWindowId(WId wid)
{
NSView *nativeView = reinterpret_cast<NSView *>(wid);
NSWindow* nativeWindow = nativeView.window;
if (nativeWindow)
{
return nativeWindow.windowNumber;
}
return 0;
}