随着技术的发展,现在的网站视频,特别是那些长视频网站,都采用了一系列的优化手段去实现视频的稳定播放和视频的快速播放,即使是在拖拽快进的情况下也可以实现近乎秒播,本文将进行深入分析相关原理与实践
前置知识
现在的网站视频都是采用了流媒体传输协议来进行视频资源的请求与播放,即将视频切成一小段一小段的单独问题,播放器根据播放进度请求对应或提前请求响应的视频片段,进而实现视频的流畅播放和快速播放;当然了这样也能实现降本的目的,点播的降本设计范围较多,如CDN、转码、存储等;
包括直播场景中的断流检测就是为了排查和解决直播卡顿的关键方案;直播是实时流播放,任何一个环节出问题了,都会导致直播卡顿甚至黑屏等现象,这大多是因为实时拉取的流断开了,我们称之为断流,但是为了知道什么时候流断开了,我们可以通过第三方库进行实现,进而提醒主播或观看端「网络拥堵」等提示
多媒体优化方案
直播行业的质量衡量通用标准:开播失败率、卡顿率、秒开率
点播行业的质量衡量通用标准:卡顿率、错误率、失败率
- 多媒体行业指标参考
- 短视频直播:开播失败率、首帧时间、百秒卡顿 sum、百秒卡顿 avg、百秒卡 顿次数、百秒重试次数
- 游戏直播:秒开率、卡顿次数及程度、黑屏比、平均延时等
- 短视频点播:下载失败率、下载速度、卡顿/率次数/时长等
直播质量优化方案
直播和点播业务均是建立在复杂的技术链路架构上,因此要想对质量进行优化,需要先拆解链路架构、再针对每一环链路进行优化,常见的直播架构分为:边缘推流架构、触发拉流架构、拉流架构;
整体直播业务架构分为三层:上行推流层、中间分发层和下行拉流层,因此相关优化也是针对上述结构进行展开的;
播放器播放视频的过程实际上是播放器代码逻辑读取缓冲区数据,进行解封装、音视频解码、音视频同步处理、音视频渲染的过程;
为了抗网络、渲染、解码等各种抖动,播放器播放视频一般会设置缓冲区来进行抗抖动,缓冲区分为帧缓冲区和显示缓冲区:帧缓冲区,主要是抗网络抖动、抗解码抖动、避免丢帧。显示缓冲区,主要是抗渲染抖动、实现音画同步。缓冲区越大,抗抖动效果越好,但内存占用就更大,延时效果也更大;
因此在不同需求场景下的Buffer缓冲时长设置是不同的,教育类直播场景需要低延时直播,而终端播放器缓冲时长越长,延迟越高,一般对延迟要求较高的需要下Buffer缓冲时长为0-1秒,要求不高的需求下一般设置3-5秒缓冲区;
- 下行播放优化
多码率及窄带高清的本质就是在弱网/低网速环境下使用低码率视频播放以较少客户播放视频质差现象,网络状况好/高网速情况下使用高码率视频播放,提升观看体验;多码率自适应可以将指定的音视频文件流统一打包生成一个自适应码流文件,让播放器可以根据终端网络带宽环境,自行选择对应码率的播放URL,播放协议一般为HLS;
现在一般都在进行窄带高清技术
的相关探索与优化,窄带高清技术大多是对编码算法进行优化,可以实现在同等画质下更省流,在同等带宽下更高清的观看体验;- 终端选择适当合适清晰度视频进行播放的方案
终端用户的网络带宽需要不小于视频码率才可以保障视频的清晰观看,不同分辨率的视频一般码率区间相对固定,一般有如下两种方式进行选择
- 根据不同的终端画质需要选择不同的码率的视频作为默认码率
- 多码率自适应技术
- 该技术需要终端播放器配合检测终端客户网络情况+集成码率自适应算法+播流视频支持多码率来实施
- 终端播放器Buffer缓冲区优化
播放器播放视频的过程本质上是播放器代码逻辑读取缓冲区数据,进行解封装、音视频解码、音视频同步处理、音视频渲染的过程。为了抗网络、渲染、解码等各种抖动,播放器播放视频一般会设置缓冲区来进行抗抖动,缓冲区分为帧缓冲区和显示缓冲区:帧缓冲区,主要是抗网络抖动、抗解码抖动、避免丢帧。显示缓冲区,主要是抗渲染抖动、实现音画同步。缓冲区越大,抗抖动效果越好,但内存占用就更大,延时效果也更大
被忽略的视频格式
HLS
HLS是基于
HTTP
的流媒体传输协议,可以实现流媒体的额点播和直播,其原理就是将整个视频流分成一个个基于HTTP的文件,每次只下载一部分;
#EXTM3U // 表明该文件是一个 m3u8 文件,必须在文件的第一行
#EXT-X-VERSION:3 // HLS 的协议版本号
#EXT-X-MEDIA-SEQUENCE:0 // M3U8直播时的直播切换序列,当播放打开M3U8时,以这个标签的值作为参考,播放对应的序列号的切片
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:12 // 该标签指定了媒体文件持续时间的最大值,播放文件列表中的媒体文件在EXTINF标签中定义的持续时间必须小于或者等于该标签指定的持续时间。
//该标签在播放列表文件中必须出现一次。
#EXTINF:10.760000, // EXTINF为M3U8列表中每一个分片的duration,描述信息
1701831447981_1682703046062\1701831447981_1682703046062-0000.ts
#EXTINF:10.720000, // EXTINF为M3U8列表中每一个分片的duration,描述信息
1701831447981_1682703046062\1701831447981_1682703046062-0001.ts
#EXTINF:10.800000,
1701831447981_1682703046062\1701831447981_1682703046062-0002.ts
#EXTINF:8.080000,
1701831447981_1682703046062\1701831447981_1682703046062-0003.ts
#EXTINF:10.240000,
1701831447981_1682703046062\1701831447981_1682703046062-0004.ts
#EXTINF:11.680000,
1701831447981_1682703046062\1701831447981_1682703046062-0005.ts
#EXTINF:8.920000,
1701831447981_1682703046062\1701831447981_1682703046062-0006.ts
#EXTINF:9.600000,
1701831447981_1682703046062\1701831447981_1682703046062-0007.ts
#EXTINF:0.240000,
1701831447981_1682703046062\1701831447981_1682703046062-0008.ts
#EXT-X-ENDLIST
- HLS包含三部分
HTTP
:传输协议m3u8
:索引文件- 是一种
UTF-8
编码的索引纯文本文件,可以理解为一个播放列表,里面记录着一段段的视频流ts文件非视频流内容的信息 Content-Type:application/octet-stream
- 是一种
ts文件
:音视频媒体信息Content-Type:application/octet-stream
- 浏览器解析播放HLS媒体文件流程
- 注解:一般可以通过专业的转码工具将常见的视频格式如mp4转换为指定格式的视频文件如HLS文件,较常用的工具有
FFmpeg
FFmpeg
转换MP4到HLS的步骤:先将MP4转换为一整块ts文件,然后将转换后的ts文件切成一段一段的指定大小(通常指时长大小)的ts文件,并将对应的ts文件信息存放到一个m3u8格式的文件中,相对前端来说一般将此过程放到Node的子进程中进行实现
ffmpeg -y -i './video.mp4' -vcodec copy -acodec copy -bsf:v h264_mp4toannexb "./index.ts" // `-bsf:v h264_mp4toannexb`设置比特流过滤器 ffmpeg -i "./index.ts" -c copy -map 0 -f segment -segment_list "./index.m3u8" -segment_time 10 "./index-%04d.ts"
FFmpeg
也常用于媒体信息的拉取与分析,如播放过程中的卡顿、模糊、音视频不同步等情况的媒体信息拉取分析,是相当牛皮的一个工具
- 首先解析m3u8文件,然后通过
HTTP请求
分片内容即ts文件 - 然后再用
MSE
的appendBuffer
进行Buffer
的封装,完成合流的工作- 包括下文的视频
快播
方案也是会操作Buffer进而实现相关的逻辑
- 包括下文的视频
- 最后交由播放器进行播放
- 注解:一般可以通过专业的转码工具将常见的视频格式如mp4转换为指定格式的视频文件如HLS文件,较常用的工具有
m4s
M4S文件是使用MPEG-DASH(动态自适应流传输)视频流技术流式传输的视频片段,m4s是一种视频文件格式,通常用于存储流媒体数据,这种文件格式是iOS基础媒体文件格式(iOS BMFF)的一部分,包含了视频和音频的分段数据;
MPEG-DASH是一种自适应比特率流技术,允许用户使用HTTP在互联网上流式传输高质量的视频。使用MPEG-DASH流式传输的视频分为多个段,这些段以各种不同的比特率提供。通过将视频划分为多个片段,并使这些片段以不同的比特率可用,Web 客户端可以流畅地流式传输长视频,而不会停顿或重新缓冲;
M4S文件通常用于HTTP Live Streaming(HLS)协议中,这是一种用于在互联网上分发流媒体的协议。HLS协议将视频和音频分成多个小段,每个小段都被存储为M4S文件。这种分段的方式可以提高视频的加载速度和播放体验,因为它允许视频在下载和播放过程中同时进行
M4S文件的结构通常包括几个部分:moof和mdat。moof部分包含了媒体片段的元数据,例如时间戳和媒体类型。mdat部分包含了媒体数据本身,例如视频和音频内容。
Range请求首部
Range是一个请求首部,用于告知服务器返回文件的哪一部分,一个首部可以对应有多个请求的部分,服务器会以
multipart
文件的形式将其返回,一般情况下会返回206 Partial Content状态码
(206 Partial Content (from disk cache)
),请求返回不合适会返回416 Range Not Satisfiable
状态码,其可以用在普通的HTTP请求和其他视频流格式文件请求上,同时会在相应头中返回Content-Range:0-5/20
(当前资源范围/视频总长度)字段表示下载的是整个资源中的哪一部分;
在进行快进时,有可能会命中同一块片段的数据块,此时的
Content-Range
是一样的,即视频分成多少段时提前确定的,当点击进度条的时候会计算出在哪个Range,然后下载对应的Range的视频片段来播放
- 常规的HTTP Range设置
... headers: { Range: 'bytes=0-4' } ...
- Range数据获取到之后进行播放的原理
拿到对应Range的视频片段后需要结合
SourceBuffer
API来实现对应片段的播放和快进;在快进的时候需要先将之前部分的数据删除SourceBuffer.remove(start,end)
,然后再append新的数据进行续播
大厂中的Web视频播放方案
共用逻辑
都是边播边加载缓存,从而减少直接加载大文件的成本,且支持了分片传输,同时支持动态控制视频码率(清晰度),做到根据网速加载不同码率的分片文件,做到动态码率适应
bilibili
B站是采用m4s配合Range来实现视频稳定快速播放的,其内部逻辑和如下知乎的MP4格式的类似,m4s和HLS中的ts类似,都是视频分块的技术,m4s地址是相同的,配合Range可以实现分块的加载渲染,而ts是不同的地址块进行分块的加载,一般不会涉及到Range的逻辑;加载时是采用分块加载的,再加上Range的加持,使得该格式的视频播放更流畅快速;
知乎
知乎视频时通过Range来请求部分视频片段,通过SourceBuffer动态播放这个片段,来实现快速播放的目的,视频格式还是用的MP4格式,而具体的分段是提前确定好的,会根据进度条来计算出下载哪个Range的视频
爱奇艺
爱奇艺是请求f4v格式的视频,也是分片加载的技术,播放一个视频请求多个f4v文件,当然也是配合了Range技术实现分片加载逻辑,但是相比于其他厂商,爱奇艺的Range是写在f4v的get请求体body中的,而非请求头中
抖音
抖音和知乎的类似,都是直接采用的MP4的格式,结合Range技术可以实现边播放边缓冲的效果,区别是视频地址中没有.mp4,只是在相应头的
Content-Type:
设置了video/mp4
小红书
和知乎类似,通知MP4格式的文件结合Range实现视频的边播边缓存,但是不分片,同地址MP4不断请求缓冲;
拓展
浏览器DOM侦听浅析 → MutationObserver
MutationObserver会检测DOM变化然后反馈给检测端,其类似于事件监听,不同点在于事件监听是同步的,MutationObserver是异步的,只有当当前侦听的DOM变化结束后才会进行反馈;只要是需要检测DOM变化的,都可以使用该API,如水印需求,当用户更改水印逻辑时,就会通过该API进行监测与反馈
- MutationObserver特点
- 它等待所有脚本任务完成后,才会运行,即采用异步方式。
- 它把DOM变动记录封装成一个数组进行处理,而不是一条条地个别处理DOM变动。
- 它既可以观察发生在DOM的所有类型变动,也可以观察某一类变动。
- 兼容性检测
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var observeMutationSupport = !!MutationObserver;
- 实例化
var observer = new MutationObserver(callback)
- 实例监测
var options = {
'childList': true, // 子节点的变动
'attributes':true, // 属性变动
'characterData': true, //节点内容或节点文本的变动
'subtree':true, // 所有后代节点的变动 不可单独检测 需要配合上面之一
} ;
observer.observe(document.queryselect('body'), options);
- disconnect(),takeRecords()
- disconnect方法用来停止观察。再发生相应变动,就不再调用回调函数。
- takeRecords方法用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。
observer.disconnect(); //
//返回的是变动记录的数组
observer.takeRecords(); //
一文读懂 MutationObserver 的基本原理和应用场景
JavaScript核心之变动观察器(Mutation Observer)
浏览器中的ETag浅析
一般设置在协商缓存(ETag)和强缓存(Expires/Chache-control)中;
- ETag设置步骤
- 浏览器第一次和服务器请求一个资源,服务器返回资源的同时,在
response header
上加ETag字段- ETag是服务器根据当前请求的资源生成的一个唯一标识
- 该唯一标识是一个字符串,只要资源变化了这个串就不同,和最后的修改时间没有关系
- 浏览器再次请求这个资源时,在Request header上添加
If-Modified-Since:Tue, 05 Dec 2023 11:13:32 GMT
字段和If-None-Match:ETag
字段,后者的值就是上一次请求返回的ETag的值 - 服务器再次收到请求时,根据浏览器返回的
If-None-Match:ETag
和根据对应资源新生成的ETag进行对比- 没有变化时:返回
304 Not Modified
,不会返回资源内容,但是会返回根据对应内容新生成的ETag字段 - 有变化时,返回正常的资源内容
- 没有变化时:返回
- 浏览器第一次和服务器请求一个资源,服务器返回资源的同时,在
视频video的src上的blob:http....
浅析
此地址是通过
URL。createObjectURL(file/Blob/stream)
来生成的,然后将生成的地址设置为video的src属性即可
视频第一帧获取
function getVideoBase64(url) {
return new Promise(function (resolve, reject) {
let dataURL = ''
let video = document.createElement("video");
video.setAttribute('crossOrigin', 'anonymous');//处理跨域
video.setAttribute('src', url);
video.setAttribute('width', 400);
video.setAttribute('height', 240);
video.addEventListener('loadeddata', function () {
let canvas = document.createElement("canvas")
let width = video.width //canvas的尺寸和图片一样
let height = video.height
canvas.width = width
canvas.height = height
canvas.getContext("2d").drawImage(video, 0, 0, width, height); //绘制canvas
dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
resolve(dataURL)
})
})
}
推荐文献
实现自适应码率Web视频加密播放:前后端的挑战与解决方案
H5视频化调研浅析
原文链接:https://juejin.cn/post/7311602485885599782 作者:FE杂志社