当前位置: 首页 > Web前端 > JavaScript

音视频同步——你所不知道的SEI

时间:2023-03-27 01:36:18 JavaScript

摘要:使用SEI解决数据流录制和播放时音视频不同步的问题。文章|ZEGOWebSDK开发团队今年6月,ZEGO科技推出业界首款数据流录音PaaS解决方案,打破传统录音服务传统,实现100%录音还原效果(点击查看方案介绍文章)。在数据流的录制和回放过程中,我们需要将音视频画面和白板画面组合成一个播放画面,模拟播放器进行同步播放。在此过程中,有时由于网络抖动等原因,录制的音视频可能会卡顿。如果不及时处理,播放进度和录制过程、音视频图像等图像会不同步。那么,面对这种情况,我们应该如何应对呢?在本文中,我们将从SEI的基本概念出发,结合数据流录制和播放的需求和应用场景,带你了解ZEGOInstantTechnology如何使用SEI解决音频不同步的问题和视频,以及开发过程中的可能步骤。到坑里。1、什么是SEI1,SEI简介SEI,SupplementalEnhancementInformation,属于码流范畴,它提供了一种给视频码流添加附加信息的方法,就是H.264/H.265这些视频压缩标准功能之一。在H264/AVC编码格式的NALuint的header中,有一个type字段表示NALuint的类型。当“type=6”时,NALuint携带的信息是SupplementaryEnhancementInformation(SEI)。SEI信息可以在视频内容生成端的传输过程中插入。2.SEI的基本特征对于解码过程不是强制性的。也就是说,SEI对解码过程没有直接影响。它可能对解码过程(容错,纠错)有帮助,可以根据SEI中插入的信息在解码过程中编写逻辑。集成在视频码流中,从码流中读取。3、SEI应用利用SEI可以存储数据的特性,还可以实现以下功能:传输编码器参数、传输视频版权信息、传输摄像头参数、传输内容生成过程中的剪辑事件、传输自定义消息。企业可以使用SEI特性来实现业务功能。2、如何使用SEI实现业务逻辑接下来,我们将以web端为切入点,带大家了解SEI的阅读和应用过程。1.将SEI插入到视频码流中在读取SEI之前,必须将SEI插入到音视频码流中。可以了解一下SEI的插入方法和规则,具体的操作步骤可以上网搜索了解。2、在Web平台上阅读hjplayer.js是一个音视频插件,可以对FLV文件流和HLSTS文件流进行解码转码,转换成FragmentedMP4,然后通过MediaSourceExtensions将mp4片段进行转换APIPaddingtoHTML5,提供SEI信息的回调方法。插件初始化:constvideoElement=document.getElementById('videoElement');constplayer=newHJPlayer({type:'flv',url:'http://xxx.xxx.com/xxxx.flv',});player.on(HJPlayer.Events.GET_SEI_INFO,(e)=>{console.log(e);//SEI消息});该回调方法提供读取SEI返回的信息,但是SEI信息对应的不是当前视频播放进度,而是当前视频缓存读取的进度。也就是说,当前回调返回的不是当前播放帧的SEI,而是未来帧的SEI。这时候我们需要知道返回的SEI对应的是哪一帧。3、获取当前SEI返回的位置获取SEI返回的位置,需要根据hjplayer.js源码修改。改造前,我们需要先了解一下SEI阅读的原理:首先,hjplayer.js是基于flv.js封装的。其工作原理是:将FLV文件流转码复用成ISOBMFF(MP4片段)片段,然后通过MediaSourceExtensions将MP4片段设置到原始HTML5Video标签中进行播放;然后,对FLV文件流进行转码复用,在使用过程中,会解析MP4片段,解析NALU携带的信息可以得到SEI信息。因为分析是以片段为单位进行的,我们无法准确知道每一个SEI的具体位置,但是我们可以知道包含该SEI的片段的具体位置,计算片段的具体位置,进而得到大概的位置SEI。接下来我们修改hjplayer.js的源码,获取SEI片段的位置。话不多说,让我们一起来看看下面修改后的源码:*//HJPlayer/src/Codecs/FLVCodec/Demuxer/FLVDemuxer.ts*_parseAVCVideoData(arrayBuffer:ArrayBuffer,dataOffset:number,dataSize:number,tagTimestamp:number,tagPosition:number,frameType:number,cts:number){constle=this._littleEndian;constv=newDataView(arrayBuffer,dataOffset,dataSize);常量单位:Array=[];让长度=0;让偏移量=0;constlengthSize=this._naluLengthSize;constdts=this._timestampBase+tagTimestamp;让isKeyframe=frameType===1;*//来自FLV帧类型常量*while(offset=dataSize){Log.warn(this.Tag,`格式错误的Nalu接近时间戳${dts},offset=${offset},dataSize=${dataSize}`);休息;*//数据不足以用于下一个Nalu*}*//Naluwithlength-header*(*AVC1*)letnaluSize=v.getUint32(偏移量,!le);*//大端读取*if(lengthSize===3){naluSize>>>=8;}}if(naluSize>dataSize-lengthSize){TagLog.warn(,`MalformedNalusneartimestamp${dts},NaluSize>DataSize!`);返回;}constunitType=v.getUint8(offset+lengthSize)&0x1f;if(unitType===5){*//IDR*isKeyframe=true;}}constdata=newUint8Array(arrayBuffer,dataOffset+offset,lengthSize+naluSize);constunit:NALUnit={type:unitType,data};if(unit.type===6){//*获取*To*SEI*信息try{constunitArray:Uint8Array=data.subarray(lengthSize);*//*添加*tagPosition*回调参数,返回当前读取段的位置}catch(e){Log.log(this.Tag,'parsesei信息错误!');}}units.push(unit);长度+=数据.byteLength;offset+=lengthSize+naluSize;}if(units.length){consttrack=this._videoTrack;constavcSample:AvcSampleData={units,length,isKeyframe,dts,cts,pts:dts+cts};如果(isKeyframe){avcSample.fileposition=tagPosition;}track.samples.push(avcSample);track.length+=长度;}}在上面的源码中,在_parseAVCVideoData方法中解析SEI信息。tagPosition参数用于标识当前读取段的位置。该参数在触发Events.GET_SEI_INFO回调的位置暴露。将tagPosition除以视频资源总长度totalLength,得到读取位置的百分比,进而计算SEI对应的大概位置。如果想知道更准确的SEI位置,可以每次读取更小的片段,这样计算更准确,当然这也会增加一定的量。性能消耗。4.使用存储在SEI中的时间戳来校正视频进度。利用SEI可以存储数据的特性,将视频流播放位置的时间戳存储在SEI中,并以此数据作为播放时间基准。思路如下:第一步:计算当前SEI记录的位置,比如第10秒返回的SEI;步骤2:根据计算出的SEI位置,找出当前SEI位置对应的帧节点,将当前SEI记录的时间戳保存在帧节点数据中;第三步:根据时间戳和播放开始时间计算当前帧视频的基本进度,如果视频进度与基本进度的差值大于一定阈值,则修正回基本进度.下面举个例子来理解上面的思路:上图是播放器某个区域的时间轴。假设回放播放器开始播放的时间戳记录为T1:当回放播放器进行到第7s时,有视频流进来,此时从进度0的位置开始播放;当回放播放器播放到第10秒时,视频流当前播放到第3秒;而在第10个位置,此时帧节点中保存了SEI信息,记录时间戳为T2;根据T2-T1-7s,视频流的基本播放进度为C;如果C减去当前视频流进度3s(即c-3s),如果大于0.5s,则当前视频流进度调整为C,保证当前视频流画面与其他非视频流画面同步显示。以上就是利用存储在SEI中的时间戳来修正视频的进度,保证播放时音视频同步的过程。3、hjplayer.js步进和填充技巧在使用hjplayer.js插件获取SEI的同时,我们还会用它来进行一些基本的音视频操作,比如播放、快进快退等。下面在使用过程中会出现常见问题,下面针对具体情况进行说明。问题一:等待状态的处理当用户将视频进度调整到未缓存区域时,当前视频会出现等待状态,导致视频显示loading无法正常播放跳转。这时候就需要调用player实例的unload和load方法进行视频重载。示例代码如下:constvideoElement=document.getElementById('videoElement');constplayer=newHJPlayer({type:'flv',url:'http://xxx.xxx.com/xxxx.flv',},{...userconfig});player.attachMediaElement(videoElement);播放器.load();player.play();//...videoElement.addEventListener('waiting',()=>{player.unload();player.load();});问题二:跳转的处理到未缓存区当用户将视频进度调整到未缓存区时,视频画面会出现加载图标,并停在当前进度,不正常跳转播放,视频处于等待状态,如下图所示:我们可以通过以下操作来避免这个问题:第一步:设置lazyLoad属性constvideoElement=document.getElementById('videoElement');constplayer=newHJPlayer({type:'flv',url:'http://xxx.xxx.com/xxxx.flv',},{lazyload:false,...用户配置});将lazyLoad属性设置为false,表示当视频缓存时间足够长时,HTTP连接不会断开。但是如果加载比较长的视频,缓存会在一定进度后停止加载;第二步:监听缓存进度,挂载到播放器实例上.end(len-1);}});从缓冲进度的监听回调中,记录当前视频的缓冲进度。第三步:调整跳转进度方法functionseek(targetTime){if(player.task)return;player.task=setInterval(()=>{constprocess=player.process;if(targetTime>process){videoElement.currentTime=process-2;}else{videoElement.currentTime=targetTime;clearInterval(player.task);播放器.task=null;}},100);}通过定时器,轮询当前缓存进度,如果当前缓存进度小于目标进度,则调整当前播放进度到与缓存进度相近的位置.这时候可以主动触发缓存资源的请求,直到缓存达到目标进度。至此,跳转到未缓存区的问题就解决了。4.总结数据流录制是教育企业自研技术优化加入后形成的一套便捷、高效、即用型的标准化PaaS解决方案。打破传统录音服务,实现100%录音还原效果。以上就是本文对SupplementaryEnhancedInformation(SEI)的解读和应用,即Goutech利用FLV音视频携带的SEI携带一些验证信息,验证音视频的标准播放时间,使用SEI实现多回放画面实时同步,最大程度还原直播场景,提高录制回放质量。更详细的数据流记录可以查看至高科技官方文档,点击查看:https://doc-zh.zego.im/articl...