IVS 实时直播功能优化
为了确保您的用户在使用 IVS 实时流式传输时获得最佳的视频直播和观看体验,您可以使用我们目前提供的所有功能,通过多种方式对部分体验进行改进或优化。
简介
在优化用户的体验质量时,务必考虑用户想要的观看体验,这种体验可能会根据其正在观看的内容和网络状况而变化。
在本指南中,我们重点关注流的发布者或流的订阅用户,并考虑这些用户所需的操作和体验。
使用 IVS SDK 可以配置流的最大比特率、帧速率和分辨率。当发布者出现网络拥塞时,SDK 会自动适应并通过降低比特率、帧速率和分辨率来降低视频质量。在 Android 和 iOS 上,遇到拥塞时可以选择降级偏好。无论您是启用使用联播进行分层编码还是保留默认配置,行为都是相同的。
自适应流式传输:通过联播分层编码
此功能仅在下列客户端版本中受支持:
-
iOS 和 Android 1.18.0+
-
Web 1.12.0+
使用 IVS 实时广播 SDK 时,发布者可以对多层视频进行编码,而订阅用户会自动适应或根据其网络状况更改为最佳质量。我们将之称为通过联播分层编码。
Android 和 iOS 以及 Chrome 和 Edge 桌面浏览器(适用于 Windows 和 macOS)均支持通过联播分层编码。但其他浏览器不支持分层编码。
在下图中,主机发送了三种质量(高、中和低)的视频。IVS 根据可用带宽向每位观众发送最高质量的视频;每位观众可以获得最佳视频体验。如果观众 1 的网络连接从良好变为不良,IVS 会自动开始向观众 1 发送较低质量的视频,因而观众 1 可以继续以其所能获得的最佳质量观赏流。
默认分层、质量和帧率
为移动平台和 Web 用户提供默认质量和分层,如下所示:
| 移动平台(Android、iOS) | Web(Chrome) |
|---|---|
高级层(或自定义):
|
高级层(或自定义):
|
中间层:无(不需要,因为移动平台上高级层和低级层的比特率之间的差异很小) |
中间层:
|
低级层:
|
低级层:
|
图层分辨率
中层和低层的分辨率会自动从高图层缩减,以保持相同的宽高比。
如果中层和低层的分辨率与上层过于接近,则将其排除在外。例如,如果配置的分辨率为 320x180,则 SDK 不会同时发送较低分辨率的图层。
下表显示不同配置分辨率生成的图层的分辨率。列出的值是横向的,但可以反向应用于纵向内容。
| 输入分辨率 | 输出图层分辨率:移动 | 输出层分辨率:Web |
|---|---|---|
|
720p (1280x720) |
高(1280x720) 低(320x180) |
高(1280x720) 中(640x360) 低(320x180) |
|
540p (960x540) |
高(960x540) 低(320x180) |
高(960x540) 低(320x180) |
|
360p (640x360) |
高(640x360) 低(360x180) |
高(640x360) 低(360x180) |
|
270p (480x270) |
高(480x270) |
高(480x270) |
|
180p (320x180) |
高(320x180) |
高(320x180) |
对于上面未映射的自定义输入分辨率,您可以使用以下工具
配置联播分层编码(发布者)
要在联播中使用分层编码,您必须在客户端上启用该功能。如果已启用,您将看到发布者的上传带宽使用量增加,观众的视频冻结可能会减少。
Android:
// Enable Simulcast StageVideoConfiguration config = new StageVideoConfiguration(); config.simulcast.setEnabled(true); ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config); // Other Stage implementation code
iOS:
// Enable Simulcast let config = IVSLocalStageStreamVideoConfiguration() config.simulcast.enabled = true let cameraStream = IVSLocalStageStream(device: camera, configuration: config) // Other Stage implementation code
Web
// Enable Simulcast let cameraStream = new LocalStageStream(cameraDevice, { simulcast: { enabled: true } }) // Other Stage implementation code
有关配置各个层的详细信息,请参阅每个广播 SDK 指南中的“配置分层编码(发布者)”:Android、iOS 和 Web。
配置联播分层编码(订阅用户)
要配置订阅用户接收的层,请参阅相应实时流媒体 SDK 指南中的“联播分层编码”部分:
可以通过订阅用户配置来定义 InitialLayerPreference。这会决定最初交付的视频质量以及 preferredLayerForStream,后者又会决定在视频播放期间选择哪个层。如果层发生了变化、自适应发生了改变或选择了层,有一些事件和流方法可用于发出通知。
流式传输配置
本节介绍了可对视频和音频流进行的其他配置。
更改视频流比特率
要更改视频流的比特率,请使用以下配置示例。
Android:
StageVideoConfiguration config = new StageVideoConfiguration(); // Update Max Bitrate to 1.5mbps config.setMaxBitrate(1500000); ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config); // Other Stage implementation code
iOS:
let config = IVSLocalStageStreamVideoConfiguration(); // Update Max Bitrate to 1.5mbps try! config.setMaxBitrate(1500000); let cameraStream = IVSLocalStageStream(device: camera, configuration: config); // Other Stage implementation code
Web
let cameraStream = new LocalStageStream(camera.getVideoTracks()[0], { // Update Max Bitrate to 1.5mbps or 1500kbps maxBitrate: 1500 }) // Other Stage implementation code
更改视频流帧率
要更改视频流的帧率,请使用以下配置示例。
Android:
StageVideoConfiguration config = new StageVideoConfiguration(); // Update target framerate to 10fps config.targetFramerate(10); ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config); // Other Stage implementation code
iOS:
let config = IVSLocalStageStreamVideoConfiguration(); // Update target framerate to 10fps try! config.targetFramerate(10); let cameraStream = IVSLocalStageStream(device: camera, configuration: config); // Other Stage implementation code
Web
// Note: On web it is also recommended to configure the framerate of your device from userMedia const camera = await navigator.mediaDevices.getUserMedia({ video: { frameRate: { ideal: 10, max: 10, }, }, }); let cameraStream = new LocalStageStream(camera.getVideoTracks()[0], { // Update Max Framerate to 10fps maxFramerate: 10 }) // Other Stage implementation code
优化音频比特率和立体声支持
要更改音频流的比特率和立体声设置,请使用以下配置示例。
Web
// Note: Disable autoGainControl, echoCancellation, and noiseSuppression when enabling stereo. const camera = await navigator.mediaDevices.getUserMedia({ audio: { autoGainControl: false, echoCancellation: false, noiseSuppression: false }, }); let audioStream = new LocalStageStream(camera.getAudioTracks()[0], { // Optional: Update Max Audio Bitrate to 96Kbps. Default is 64Kbps maxAudioBitrateKbps: 96, // Signal stereo support. Note requires dual channel input source. stereo: true }) // Other Stage implementation code
Android:
StageAudioConfiguration config = new StageAudioConfiguration(); // Update Max Bitrate to 96Kbps. Default is 64Kbps. config.setMaxBitrate(96000); AudioLocalStageStream microphoneStream = new AudioLocalStageStream(microphone, config); // Other Stage implementation code
iOS:
let config = IVSLocalStageStreamConfiguration(); // Update Max Bitrate to 96Kbps. Default is 64Kbps. try! config.audio.setMaxBitrate(96000); let microphoneStream = IVSLocalStageStream(device: microphone, config: config); // Other Stage implementation code
更改订阅用户抖动缓冲区最小延迟
要更改正在订阅的参与者的抖动缓冲区最小延迟,可以使用自定义 subscribeConfiguration。抖动缓冲区决定在开始播放之前存储多少数据包。最小延迟表示应存储的最小数据量目标。当面临丢包/连接问题时,更改最小延迟可帮助提高播放弹性。
增加抖动缓冲区大小的代价是也会增加播放开始前的延迟。增加最小延迟可以提高弹性,但代价是影响视频播放时间。请注意,在播放期间增加最小延迟会产生类似的效果:播放将短暂停顿,等待抖动缓冲区填满。
如果需要更高的弹性,则建议最小延迟预设从 MEDIUM 开始,并在播放开始之前设置订阅配置。
请注意,最小延迟仅适用于仅订阅的参与者。如果参与者自行发布,则最小延迟不适用。这样做是为了确保多个发布者能够互相交流而不会产生额外的延迟。
以下示例使用的最小延迟预设为 MEDIUM。请参阅 SDK 参考文档以了解所有可能的值。
Web
const strategy = { subscribeConfiguration: (participant) => { return { jitterBuffer: { minDelay: JitterBufferMinDelay.MEDIUM } } // ... other strategy functions }
Android:
@Override public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { SubscribeConfiguration config = new SubscribeConfiguration(); config.jitterBuffer.setMinDelay(JitterBufferConfiguration.JitterBufferDelay.MEDIUM()); return config; }
iOS:
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration { let config = IVSSubscribeConfiguration() try! config.jitterBuffer.setMinDelay(.medium()) return config }
在暂存区之间切换时缩短视频加载时间
应用程序可能允许用户浏览实时流源。用户加入一个暂存区,滑动到另一个流,离开当前暂存区,然后快速加入下一个暂存区。每次暂存区切换都需要创建新连接并建立新的网络连接,这可能会延长视频可用前的等待时间。
使用 RealTimeConnection 在用户浏览流时保持一个网络连接处于打开状态。创建一次连接,可选择在第一个暂存区加入之前建立连接,然后跨暂存区重复使用。每个暂存区仍使用自己的参与者令牌,并维护自己的参与者、流和生命周期。
在浏览会话期间,在存在时间超过单个暂存区的对象(例如应用程序协调器、导航控制器或视图模型)中创建并保留 RealTimeConnection。不要为每次暂存区切换创建和断开连接。
Android:
以下示例将 RealTimeConnection 生命周期添加到 Android 示例中使用的 MainViewModel 中。视图模型实现了 Stage.Strategy 和 StageRenderer。
private var realTimeConnection: RealTimeConnection? = null private var stage: Stage? = null private fun setupConnection(connectionToken: String) { try { val connection = RealTimeConnection(getApplication(), connectionToken) // Establish the shared transport before the first Stage join. connection.connect() realTimeConnection = connection } catch (error: BroadcastException) { Log.e("BasicRealTime", "Unable to create the RealTimeConnection", error) } } private fun joinStage(participantToken: String) { val realTimeConnection = realTimeConnection ?: return try { stage?.leave() stage?.release() val stage = Stage( getApplication(), participantToken, realTimeConnection, this ) stage.addRenderer(this) stage.join() this.stage = stage } catch (error: BroadcastException) { Log.e("BasicRealTime", "Unable to join the Stage", error) } } override fun onCleared() { stage?.leave() stage?.release() stage = null realTimeConnection?.disconnect() realTimeConnection?.release() realTimeConnection = null super.onCleared() }
调用 connect() 是可选的。如果您加入暂存区时连接未打开,SDK 会自动将其打开。在下一个暂存区加入之前调用 connect(),以提前建立连接并缩短视频加载时间。尽管该示例一次只连接到一个暂存区,但 RealTimeConnection 可以同时连接到多个暂存区。
当应用程序不再需要 RealTimeConnection 时,调用 release()。释放后,它将无法重复使用。
iOS:
以下示例假设应用程序在用户开始多暂存区流程时获取连接令牌,并为用户加入的每个暂存区获取参与者令牌。
private var realTimeConnection: IVSRealTimeConnection? private var stage: IVSStage? private func setupConnection(connectionToken: String) throws { let connection = try IVSRealTimeConnection(token: connectionToken) // Establish the shared transport before the first Stage join. try connection.connect() realTimeConnection = connection } private func joinStage(participantToken: String) throws { guard let realTimeConnection else { return } stage?.leave() let stage = try IVSStage( token: participantToken, on: realTimeConnection, strategy: self ) stage.addRenderer(self) try stage.join() self.stage = stage } private func endStageTransitionFlow() { stage?.leave() stage = nil realTimeConnection?.disconnect() realTimeConnection = nil }
调用 connect() 是可选的。如果您加入暂存区时连接未打开,SDK 会自动将其打开。在下一个暂存区加入之前调用 connect(),以提前建立连接并缩短视频加载时间。尽管该示例一次只连接到一个暂存区,但 IVSRealTimeConnection 可以同时连接到多个暂存区。
在暂存区使用 IVSRealTimeConnection 期间,保持对其的强引用。调用 disconnect() 会退出所有使用该连接的暂存区。
推荐优化
| 场景 | 建议 |
|---|---|
| 包含文字或移动缓慢内容的流,例如演示文稿或幻灯片 | 使用通过联播分层编码或配置帧速率较低的流。 |
| 包含动作或大量移动的流 | 使用通过联播分层编码。 |
| 包含对话或少量移动的流 | 通过联播分层编码或选择纯音频(请参阅《实时流式广播 SDK 指南:Web、Android 和 iOS》中的“订阅参与者”)。 |
| 数据有限的用户流式传输 | 使用通过联播分层编码,或者,如果您想降低所有人的数据使用量,可以配置较低的帧速率并手动降低比特率。 |
| 用户浏览实时流源,并快速从一个暂存区切换到下一个暂存区 | 在浏览会话中重复使用 RealTimeConnection。在第一个暂存区加入之前调用 connect(),以提前建立连接。 |