dash.js中实现点播视频同步机制

背景

自HEVC等视频协议提出“基于块”(tile-based)的视频格式概念后,在360度视频中基于视点追踪及预测进行多码率分配的方法成为了现实。由于一个完整的360度视频可以轻松达到12K的视频分辨率,大多数视频播放器依赖视点预测算法来估计未来用户的头部运动趋势。如果能成功预测到视点即将经过的下一个位置,播放器就能优先满足该位置所在视频块的视频质量,从而降低了360度视频对网络带宽的需求。

基于tile的自适应VR视频结构示例,以tile为单位将画面拆分为3个质量区域

提出方法

在验证平台中,由于整体框架使用DASH协议进行流媒体传输,基于tile的播放模式容易引起画面断层。因此,我们使用多播放器联合的模式,并添加catchup机制来控制各tile同步播放。

【我用的是dash.js v3.2.1,作者只实现了低延迟直播模式下使用catchup机制来维持两个播放器播放同步,之前有发起issue和作者沟通,在确认了没有实现点播模式下多播放器时间同步之后,我决定直接加一个支持点播模式的catchup机制。】

以tile = 6的CMP格式360度视频传输为例,平台首先初始化六个基于dash.js的Mediaplayer,每个Mediaplayer对应一个tile空间,各Mediaplayer按照文件服务器的视频块索引文件进行流媒体数据请求,并通过A-Frame框架进行3D联合渲染执行播放。

CMP格式360度视频各面实例,通过联合渲染可以得到全方位沉浸式画面

以下为catchup机制:平台实时观测各Mediaplayer的播放时间进度,并始终选取播放进度最快的Mediaplayer作为标准播放进度,其他Mediaplayer根据偏移阈值判断是否执行“追赶”;若确认执行,则该Mediaplayer将立刻执行倍速播放,直到与标准播放进度的偏移量小于偏移阈值。通过catchup机制,目前平台上基于tile播放360度视频可以保证各tile进度偏移量在0.02s上下浮动,主观上基本不影响观看体验。

播放实时监测数据,当某一面timeline落后时catchup state会改为catching up

代码实现

以下代码是在dash.js v3.2.1版本的基础上修改的,只提供playbackController.js中的关键修改部分供大家参考,具体还要考虑怎么和平台适配以及各种数据怎么调用等等。

// Change: [PlaybackController.js]

PlaybackController.onPlaybackProgression() {  // Consider the situation whatever isDynamic is
 if (
   //isDynamic &&
   _isCatchupEnabled() &&
   settings.get().streaming.liveCatchup.playbackRate > 0 &&
   !isPaused() &&
   !isSeeking()
 ) {
   if (_needToCatchUp()) {  // Judge if it needs to catch up according to current live latency
     if ($scope !== undefined && $scope.playerCatchUp !== undefined) {
       $scope.playerCatchUp[settings.get().count] = true;  // $scope contains global variables, $scope.playerCatchUp is for caching catchup state, settings.get().count is for locating which tile is operating
     }
     startPlaybackCatchUp();  // Begin catching up
   } else {
     if ($scope !== undefined && $scope.playerCatchUp !== undefined) {
       $scope.playerCatchUp[settings.get().count] = false;
     }
     stopPlaybackCatchUp();  // Stop catching up when the divation is less than the threshold
   }
 }
};

// ...

PlaybackController.getCurrentLiveLatency() {  // Change totally with compatibility for non-isDynamic
 if (isNaN(availabilityStartTime)) {
   return NaN;
 }
 let currentTime = getNormalizedTime();
 if (isNaN(currentTime) || currentTime === 0) {
   return 0;
 }

 if (!isDynamic && $scope !== undefined && $scope.normalizedTime !== undefined) {  // Run this when non-isDynamic
   const now = $scope.normalizedTime * 1000 + timelineConverter.getClientTimeOffset() * 1000;
   return Math.max(((now - availabilityStartTime - currentTime * 1000) / 1000).toFixed(3), 0);  // Return the divation between current tile's timeline and the normalized timeline
 }

 const now = new Date().getTime() + timelineConverter.getClientTimeOffset() * 1000;  // Run this when isDynamic
 return Math.max(((now - availabilityStartTime - currentTime * 1000) / 1000).toFixed(3), 0);
};
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容