共计 3631 个字符,预计需要花费 10 分钟才能阅读完成。
前言
流媒体协定多种多样,音视频编码格局更是繁多,要想在浏览器中失常浏览并非不容易。除开 WebRTC 这种浏览器曾经反对的协定,HLS、FLV、RTSP、RTMP、DASH 等协定都须要预处理,不过流程大抵都是:
- 通过 HTTP、WebSocket 等形式获取数据;
- 解决数据,解协定、组帧等失去媒体信息及数据;
- 封装成媒体片段,或解码成一帧画面;
- 通过 video 或 canvas(WebGL)等进行播放。
目前市面上也有一些前端解码的计划,如借助 WASM
的高性能调用 c 解码库,或者间接应用浏览器的 WebCodecs API
进行编解码 …… 但都存在局限性,WebCodecs
仍是试验性功能;而 WASM
计划尽管冲破浏览器沙盒限度(能播放浏览器不反对的编码格局如 H265 等),但解码和浏览器原始解码之间仍有差距,并且因为只能走软解导致多路性能也吃不消。所以,市面上更多的是采纳另一种形式,解协定 + 封装 + 这篇文章的配角 Media Source Extensions(以下简称 MSE)。
开始
HTML5 标准容许咱们间接在网页中嵌入视频,
<video src="demo.mp4"></video>
但 src 指定的资源地址必须是一个残缺的媒体文件,如何在 Web 做到流式的媒体资源播放?MSE
提供了这样的可能性,先看下 MDN 对它对形容:
媒体源扩大 API(MSE)提供了实现无插件且基于 Web 的流媒体的性能。应用 MSE,媒体串流可能通过 创立,并且能通过应用
<audio>
和<video>
元素进行播放。
正如下面所说,MSE
让咱们能够通过 JS 创立媒体资源,应用起来也非常不便:
const mediaSource = new MediaSource();
const video = document.querySelector('video');
video.src = URL.createObjectURL(mediaSource);
媒体资源对象创立结束,接下来就是喂给它视频数据(片段),代码看上去就像是:
mediaSource.addEventListener('sourceopen', () => {
const mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
const sourceBuffer = mediaSource.addSourceBuffer(mime);
const data = new ArrayBuffer([...]); // 视频数据
sourceBuffer.appendBuffer(data);
});
此时,视频就能够失常播放了。要想做到流式播放,只须要不停的调用 appendBuffer
喂音视频数据就行了 …… 但不禁有疑难,'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
这段字符串什么意思?音视频数据又要从哪来的?🤔
MIME TYPE
// webm MIME-type
'video/webm;codecs="vp8,vorbis"'// fmp4 MIME-type'video/mp4;codecs="avc1.42E01E,mp4a.40.2"'
这段字符串形容了视频的相干参数,如封装格局、音 / 视频编码格局以及其余重要信息。以下面 mp4 这段为例,以 ;
分为两局部:
- 前半部分的
video/mp4
示意这是 mp4 格局的视频; -
后半局部的
codecs
形容了视频的编码信息,它是由一个或多个由,
分隔的值组成,其中每个值又由一个或多个由.
宰割的元素组成:avc1
示意视频是AVC
(即 H264)编码;-
42E01E
由(16 进制示意的)三个字节形成,形容了视频的相干信息:0x42
(AVCProfileIndication
)示意视频的 Profile,常见的有 Baseline/Extended/Main/High profile 等;0xE0
(profile_compatibility
)示意编码级别的约束条件;0x1E
(AVCLevlIndication
)示意 H264 的 level,示意最大反对的分辨率、帧率、码率等;
mp4a
示意某种MPEG-4
音频;40
是由 MP4 注册机构指定的 ObjectTypeIndication(OTI),0x40
对应Audio ISO/IEC 14496-3 (d)
规范;2
示意某种音频 OTI,mp4a.40.2
示意AAC LC
。
但音视频格局多种多样,前端有什么办法间接取到视频的 MIME TYPE
呢?
对于 mp4 格局的能够应用:🌟🌟🌟 mp4box 🌟🌟🌟,获取形式如下:
// utils.ts
// 增加库
// yarn add mp4box
import MP4Box from 'mp4box';
export function getMimeType (buffer: ArrayBuffer) {return new Promise<string>((resolve, reject) => {const mp4boxfile = MP4Box.createFile();
mp4boxfile.onReady = (info: any) => resolve(info.mime);
mp4boxfile.onError = () => reject();
(buffer as any).fileStart = 0;
mp4boxfile.appendBuffer(buffer);
});
}
MIME TYPE
获取到后,能够通过 MSE 的静态方法 MediaSource.isTypeSupported() 检测以后浏览器是否反对该媒体格式。
import {getMimeType} from './utils';
...
const mime = await getMimeType(buffer);
if (!MediaSource.isTypeSupported(mime)) {throw new Error('mimetype not supported');
}
Media Segment
SourceBuffer.appendBuffer(source)
旨在将媒体片段数据 source
增加到 SourceBuffer 对象中,看 MDN 上对 source
的形容:
一个 BufferSource (en-US) 对象(ArrayBufferView 或 ArrayBuffer),存储了你要增加到 SourceBuffer 中去的媒体片段数据。
所以 source
就是一串二进制数据,当然也不是轻易一串就行,那该 媒体片段 须要满足怎么的条件呢?
- 满足 MSE Byte Stream Format Registry 规定的 MIME 类型
- 属于 Initialization Segment 或 Media Segment 中的一种
对于第一个条件,MSE 反对的媒体格式和音视频格局较少,常见的个别为 fmp4(h264+aac)
和 webm(vp8/vorbis)
等。什么是 fmp4?什么是 webm?能够点开理解下,本篇文章不展开讨论。
对于第二个条件,Initialization Segment
意为初始化片段,蕴含了 Media Segment
解码所需的初始化信息,如媒体的分辨率、时长、比特率等信息;Media Segment
则是带有工夫戳的音视频片段,并与最近增加的 Initialization Segment
相关联。个别都是 append 一个初始化片段后 append 多个媒体片段。
对于 fmp4
来说,初始化片段和媒体片段实际上都是 MP4 box
,只是类型不一样(理解更多);而对于 webm
来说,初始化片段是 EBML Header
以及 Cluster
元素前的内容(包含一些媒体、track 等信息),媒体片段则是一个 Cluster
元素(理解更多)。
实践都理解了,实操呢?如何从已有的媒体文件生成上述的 媒体片段 呢?
这里咱们用到的是🌟🌟🌟 FFmpeg 🌟🌟🌟,用起来很不便,只须要一行命令:
ffmpeg -i xxx -c copy -f dash index.mpd
xxx
是你本地的媒体文件,我这边别离用 lol.mp4
和 big-buck-bunny.webm
两个文件进行测试:
👉 ffmpeg -i lol.mp4 -c copy -f dash index.mpd
👇
👉 ffmpeg -i big-buck-bunny.webm -c copy -f dash index.mpd
👇
从测试后果能够看出,都是生成了 init-xxx.xx
、chunk-xxx-xxx.xx
的文件,
很显著 init-xxx.xx
示意初始化片段,chunk-xxx-xxx.xx
示意媒体片段,而其中的 stream0
和 stream1
别离代表了视频和音频通道。
借助在线的 mp4 box 解析工具,看下 fmp4
的初始化片段和媒体片段的外部结构:
跟 ISO BMFF 形容统一,初始化分片由 ftyp box
+ moov box
组成;媒体分片 styp box
、sidx box
、moof box
、mdat box
组成,想要理解各种盒子的含意能够返回 学好 MP4,让直播更给力 学习。
EXAMPLE
👇
🖥 在线 Demo 🌰
🔗 github 🌟