从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的编程艺术 ...
随机推荐
- Linux就该这么学 20181003(第四章Vim/shell/测试条件)
参考链接https://www.linuxprobe.com/ vim文本编辑器 命令模式:控制光标移动,可对文本进行复制,黏贴,删除和查找工作 输入模式:正常的文本录入 末行模式:保存或退出文档,以 ...
- 字符串转换整数 (atoi) C++实现 java实现 leetcode系列(八)
字符串转换整数 (atoi) java实现 C++实现 请你来实现一个 atoi 函数,使其能将字符串转换成整数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止. 当 ...
- POJ 2190 模拟
按照题意模拟就好- 注意"X"只能出现在最后一位... // by SiriusRen #include <cstdio> using namespace std; c ...
- node,koa 图片批量添加水印,可手动配置水印位置
公司设计在处理京东上架商品图片的时候,需要给设计好的图片添加京东的“logo”,并且logo位置得根据图片来摆放,需要通过计算得出logo位置.那样太麻烦了,于是就用node,koa写了批量给图片添加 ...
- python 3.x 学习笔记11 (静态、类、属性、特殊成员方法)
1.静态方法通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法.静态方法是不可以访问实例变量或类变量的即没有self,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什 ...
- Android 去掉TabLayout下的阴影,AppBarLayout下的阴影
开始还以为是TabLayout在高版本系统上的特殊表现呢,没有在意,UI提出说感觉不好看就查了一下,原来是在TabLayout放在AppBarLayout里面才有这样的效果,只需要对AppBarLay ...
- 51nod 1785 数据流中的算法 (方差计算公式)
1785 数据流中的算法 基准时间限制:1.5 秒 空间限制:131072 KB 分值: 20 难度:3级算法题 51nod近日上线了用户满意度检测工具,使用高级人工智能算法,通过用户访问时间.鼠 ...
- 阿里云ecs : Couldn't connect to host, port: smtp.aliyun.com, 25; timeout -1;
上传到服务器后javamail发邮件异常 链接 原来是ECS基于安全考虑,禁用了端口25. 改成465就可以发邮件了. p.setProperty("mail.smtp.socketFact ...
- 3Ds Max制作克劳族少女教程
作者:Andrius Balciunas 使用软件:3ds Max, ZBrush 3ds Max下载:http://www.xy3dsmax.com/xiazai.html ZBrush下载:htt ...
- php7 memcache和memcached.so扩展
php7安装memcache和memcached扩展 https://github.com/websupport-sk/pecl-memcache https://github.com/php-mem ...