从Chrome源码看audio/video流媒体实现一(转)
现在绝大多数的网站已经从flash播放器转向了浏览器原生的audio/video播放器,浏览器是如何加载和解析多媒体资源的,这对于web开发者来说是一个黑盒,所以很有必要看一下浏览器是怎么实现的,Chromium文档介绍了整体的过程是这样的:
大体来说,由video标签创建一个DOM对象,它会实例化一个WebMediaPlayer,这个player是控制中枢,player驱使Buffer去请求多媒体数据,然后交由FFmpeg进行多路解复用和音视频解码(FFmpeg是一个开源的第三方音视频解码库),再把解码后的数据传给相应的渲染器对象进行渲染绘制,最后让video标签去显示或者声卡进行播放。
这里面有两个问题需要重点关注:
(1)编解码的过程是怎么样的?
(2)流媒体数据的传输是怎么控制的?
1. 编解码基础
摄像机或者相机通过镜头里的感光元件,把收到的光转换为电子像素,一个像素是由rgba 4通道组成,在视频里面通常只有rgb 3通道,共8b * 3 = 24b = 3B即3个字节,一个1080p(1920 * 1080),帧率为30fps(一秒钟有30张图片)时长为1分钟的视频有多大呢?计算如下:
3B * 1920 * 1080 * 30 * 60 = 12441600000B = 10GB
约有10GB,一个100分钟的电影就需要占用1TB的硬盘空间,所以如果没有压缩的话这个体积是非常巨大的。但是我们看到一个1080p的mp4文件(h264,普通码率)平均1分钟的体积只有100MB不到,如下图所示:
在这个例子里面,h264编码的mp4文件压缩比达到了100:1。
因此编码的目的就是压缩,而解码的目的就是解压缩。压缩又分为有损压缩和无损压缩,无损压缩如gzip/flac,是可以还原成原本未压缩的完整数据,而有损压缩如h264/mp3等一旦压完之后就无法再还原成更高清晰度的数据了。
视频编码通常都是有损压缩,目标是降低一点清晰度的同时让体积得到大大的减少,降低的清晰度能达到对人眼不可察觉或者几乎无法分辨的水平。主要是方法是去除视频里面的冗余信息,对于很多不是剧烈变化的场面,相邻帧里面有很多重复信息,通过帧间预测等方法分析和去除,而帧内预测可去掉同个帧里的重复信息,还有对画面观众比较关注的前景部分高码率编码,而对背景部分做低码率编码,等等,这些取决于不同的压缩算法。
视频的编码方式从MPEG-1(VCD)到MPEG-2(DVD)再到MPEG-4(数码相机),再到现在主流的网络视频用的h264和比较新的h265,同等压缩质量下,压缩率不断地提到提升,它们可能都是.mp4的文件后缀,mp4格式是一个容器, 可以支持流式传输,不用把整个文件下载下来才播放。另外还有谷歌主导的VP8、VP9和AV1编码(主要在WebRTC里面使用)。编码的压缩率越好,它的算法通常会更复杂,所以一些低端设备可能会扛不住进而使用较老的编码格式。
解码的目的就是解压缩,把数据还原成原始的像素。FFmpeg是一个很出名的开源的编解码C库,Chrome也使用了它做为它的解码器之一,并且有人把它转成了WASM,可以在网页上跑。
视频是用rga像素表示相对比较好理解,声音又是怎么表达呢?
屏幕图像是用rgb即红绿蓝三原色混合组成各种各样的颜色,而声音是物体振动挤压空气形成的声波,它主要有3个感观属性:音量(振幅)、音调(频率)、音色,不同材料的音色不一样,但是一个几块钱的耳机为什么能够发出各种各样的音色的声音呢?
要形成声音需要振幅的周期性变化,如果只是振幅不断单向变大是形成不了声音的,下图表示一个声音的振幅随时间变化的波形:
纯音的波形是一个正弦曲线,但是在现实世界里面一个物体主体部分在振动的同时,它上面的不同部位也会在做自己的振动,主体振动发出的声音叫基音,子部位发出的声音叫谐音,谐音往往有成百上千个,也就是说有上千个正弦曲线叠加:
y = 1/2 sin(x) + 3/7 sin(x) + …
就形成了上面不规则的周期性曲线。这条曲线就决定了声音的所有属性包括音量(最大振幅和最小振幅的差距)、音调(周期性变化的快慢)以及音色,一个确定的波形就确定了音色。也就是说只要把喇叭按照上面的振幅移动就能够还原记录的声音。这就是声音播放的原理,没错就是这么简单。那么这些帧幅是怎么记录的呢?
视频是每秒25或者30帧图像形成连续的动画,用离散来表示连续,声音也是同样道理,它有一个采样率的概念,麦克风把声音转化为不同强度的电流信号,就像开了一个水龙头源源不断地流水,每一点的水温都是不一样的,如果采样率为1k Hz即1s 1000次,1s测1000次流过的水的温度,CD唱片音质的采样率44.1k Hz,所以为了高保真,1s需要测44k次。
温度大小表示也是连续的,所以有一个位深的概念,位深为16位则表示有16位二进制数来表示水温的水平,从低到高共65536个层次。
所以采样率和位深就决定了音质的好坏,我们知道CD是超高音质的代表了,它的采样率是44.1kHz和位深是16位,1分钟的双声道(立体音)声音的CD格式存储需要占用的硬盘空间大小为:
44100 * 16b * 60 / 8 * 2 = 10MB
所以一个没有压缩的声音1分钟需要10MB的空间,记录为PCM格式,而FLAC/APE等编码是无损压缩,相对zip/gzip,它的特点是能够实时播放。
而mp3格式是有损压缩,它有一个码率的概率,标准规定码率最高为320kbps,常见的还有128kbps/192kbps等,如果码率为192kbps,它是意思是每秒有192kb大小的内容:192kb = 192kB/ 8 = 24kB,每分钟有24kB * 60 = 1.44MB,相对于PCM的10MB,压缩比为7 : 1,所以这个压缩率也是挺高的,对于192kbps的音质和无损音质,一般的音响或者普通人的耳朵来说已经无法分辨。
MP3压缩的原理主要是人耳的听觉掩蔽效应,对低频声音会比高频的声音感知度更高,压缩时进行频谱分析,把一些高频的声音降低分配的位数,从而达到极小失真但大大小减少体积的目的。
h264的音轨主要使用AAC编码格式,相对于mp3同等音质AAC的压缩率更高。
所以声音解码的目的就是把mp3/aac等格式还原成原始的PCM格式。
2. Chrome的buffer控制加载机制
在流媒体传输里面有一个重要的概念就是buffer缓冲空间大小,如果buffer太大,那么一次性下载的数据太多,用户还没播到那里不划算,相反如果buffer太小不够播放可能会经常卡住加载。在实时传输领域,实时流媒体通信的双方如果buffer太大的话会导致延迟太大,如果buffer太小那么能做的事情就比较少如拥塞控制、丢包重传等。
我们可以来看一下Chrome播放音视频的时候buffer是怎么控制的,它的实现是在src/media/blink/multibuffer_data_source.cc这个文件的UpdateBufferSizes函数,简单来说就是每次都往后预加载10s的播放长度,并且最大不超过50MB,最小不小于2MB,往前是保留2s播放长度。
详细来说,首先要获取码率,即1s的音视频需要占用的空间,如下代码所示:
1
2
3
4
5
6
7
8
9
10
|
// If bitrate is not known, use this.
const int64_t kDefaultBitrate = 200 * 8 << 10; // 200 Kbps.
// Maximum bitrate for buffer calculations.
const int64_t kMaxBitrate = 20 * 8 << 20; // 20 Mbps.
// Use a default bit rate if unknown and clamp to prevent overflow.
int64_t bitrate = clamp<int64_t>(bitrate_, 0, kMaxBitrate);
if (bitrate == 0)
bitrate = kDefaultBitrate;
|
有一个默认码率是200Kbps,最大码率不超过20Mbps,如果还不知道码率的情况下就使用默认码率。在使用一个mp3文件做demo研究的时候发现preload了3次之后才拿到码率,如下图所示:
码率为128kbps,知道码率之后再获取播放速率通常为默认的1倍速,进而知道10s应该是多少空间:
// 这里的播放速率playback_rate为1
1
2
3
4
5
6
7
|
// Increase buffering slowly at a rate of 10% of data downloaded so
// far, maxing out at the preload size.
int64_t extra_buffer = std::min(
preload, url_data_->BytesReadFromCache() * kSlowPreloadPercentage / 100);
// Add extra buffer to preload.
preload += extra_buffer;
|
对一个60Mb的视频,播放到中间的时候,这个extra_buffer的值大概就是3MB,如下打印输出:
../../media/blink/multibuffer_data_source.cc, 733: extra_buffer = 3107155
然后把preload值传给BufferReader,由它去触发请求相应的数据。这个是使用http range功能请求相应范围的字节数,如下检查Chrome的请求:
在某个请求里面Chrome请求的范围起点是从3604480开始,没有终点,但是这里并不是说Chrome只是发一个请求然后一次性把整个文件下载下来了,它应该是通过拥塞窗口之类的方法控制接收的速率,具体没有深入去研究,把视频暂停了你会发现加载停止了,再播放原先的http返回数据量又开始变大。
对于很多视频网站我们发现他们的range都是指定明确范围的:
相比之下原生播放器的策略只是使用一个http连接加载不同字节范围的视频数据,而视频网站是每需要一个range的数据的时候就发一个请求。这些请求的响应状态码都是206,表示返回部分内容。如果连接断了或者服务端只返回部分数据就关闭连接,那么chrome会重新发个请求。另外打开页面的时候chrome不会提前预加载数据,只有点击播放了才加载音视步内容。并且preload的buffer size是在加载过程中周期性更新的。
综上,本篇主要介绍了编解码的基础,chrome的buffer加载机制,以及chrome加载和解析流媒体的基本模型,我们还没有讨论具体的解码过程,下一篇将会继续研究。
从Chrome源码看audio/video流媒体实现一(转)的更多相关文章
- 从Chrome源码看audio/video流媒体实现二(转)
第一篇主要介绍了Chrome加载音视频的缓冲控制机制和编解码基础,本篇将比较深入地介绍解码播放的过程.以Chromium 69版本做研究. 由于Chromium默认不能播放Mp4,所以需要需要改一下源 ...
- 从Chrome源码看浏览器的事件机制
.aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...
- 从Chrome源码看JS Array的实现
.aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...
- 从Chrome源码看浏览器如何构建DOM树
.aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } p { font-size: 1 ...
- 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计
使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...
- 从微信小程序开发者工具源码看实现原理(四)- - 自适应布局
从前面从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计可以知道,小程序大部分是通过web技术进行渲染的,也就是最终通过浏览器的dom tree + cssom来生成渲染树:既然最终是通 ...
- 从源码看Azkaban作业流下发过程
上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...
- Google Chrome 源码下载地址 (Google Chrome Source Code Download)
1. Google Chrome 源码 SVN 地址:http://src.chromium.org/svn.包含有 Chrome.Gears.Webkit.GCC 等源码以及编译依赖工具.Chrom ...
- 解密随机数生成器(二)——从java源码看线性同余算法
Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...
随机推荐
- Java多线程编程模式实战指南(一):Active Object模式--转载
本文由黄文海首次发布在infoq中文站上:http://www.infoq.com/cn/articles/Java-multithreaded-programming-mode-active-obj ...
- Quartz介绍和使用
Quartz介绍和使用 什么是Quartz,干什么的? Quartz框架是一个全功能.开源的任务调度服务,可以集成几乎任何的java应用程序—从小的单片机系统到大型的电子商务系统.Quartz可以执行 ...
- (WC2016模拟十一)【BZOJ4695】最假女选手
ps:好久没更博啦……这几天连着有模拟赛,等初赛前后休息的时候来疯狂补坑吧……顺便补一下前面的数论啥的? 题解: mdzz我场上写了个15分暴力长度跟标算差不多... 线段树大法好啊!这题听说很多人做 ...
- 2013-11-02 【webrebuild广州站】分享会纪要
为了不让自己沉浸个人的技术研究当中,也为了多去接触业界新技术新思想,今天去参加了webrebuild广州站的一个分享交流会,效果不错,有一些获益.听了四个主题,依据个人获取信息的情况来做个纪要(比较粗 ...
- [codevs1048]石子归并&[codevs2102][洛谷P1880]石子归并加强版
codevs1048: 题目大意:有n堆石子排成一列,每次可合并相邻两堆,代价为两堆的重量之和,求把他们合并成一堆的最小代价. 解题思路:经典区间dp.设$f[i][j]$表示合并i~j的石子需要的最 ...
- 【Tool】Linux下的Spark安装及使用
1. 确保自己的电脑安装了JAVA Development Kit JDK, 用来编译Java应用, 如 Apache Ant, Apache Maven, Eclipse. 这里是我们安装Spark ...
- 红黑树(RBTREE)之上-------构造红黑树
该怎么说呢,现在写代码的速度还是很快的,很高兴,o(^▽^)o. 光棍节到了,早上没忍住,手贱了一般,看到*D的优惠,买了个机械键盘,晚上就到了,敲着还是很舒服的,和老婆炫耀了一把哈哈. 光棍节再去* ...
- win10 64位下VirtualBox安装CentOS6.9
第一步:安装VritualBox 百度“VritualBox”下载安装即可: 第二步:下载Linux镜像系统并安装 这里写出我参照的博客,很详细,我就不累赘了! 原文地址:http://blog.cs ...
- 洛谷 P1045 麦森数 (快速幂+高精度+算位数骚操作)
这道题太精彩了! 我一开始想直接一波暴力算,然后叫上去只有50分,50分超时 然后我改成万位制提高运算效率,还是只有50分 然后我丧心病狂开long long用10的10次方作为一位,也就是100亿进 ...
- 紫书 习题11-11 UVa 1644 (并查集)
这道题感觉思路非常巧妙, 我是看了别人的博客才想明白的. 这里用到了并查集, 以根节点为中心城市, 然后把边从大到小排序, 每次的当前的边即为容量, 因为是目前的最小值, 然后去算总的容量, 每次选容 ...