随着整个互联网的崛起,数据传递的形式也在不断升级变化,总的流行趋势以下:linux
纯文本的短信,QQ -> 空间,微博,朋友圈的图片文字结合 -> 微信语音 -> 各大直播软件 -> 抖音短视频
复制代码
音视频的发展正在向各个行业不断扩展,从教育的远程授课,交通的人脸识别,医疗的远程就医等等,音视频方向已经占据一个至关重要的位置,而音视频真正入门的文章又少之甚少,一个刚毕业小白可能很难切入理解,由于音视频中涉及大量理论知识,而代码的书写须要结合这些理论,因此搞懂音视频,编解码等理论知识相当重要.本人也是从实习开始接触音视频项目,看过不少人的文章,在这里总结一个通俗易懂的文章,让更多准备学习音视频的同窗更快入门。web
本文中理论知识来自于各类音视频文章的概括音视频编码基本原理汇总,其中也会有一些我本身总结增长的部分.如有错误可评论,检查后会更正.windows
为了防止你们理解过于空洞,做者花了三个月时间将最经常使用,最重要的一些功能的理论知识及实战Demo亲自写出来,配合文章阅读效果更佳.每一部分的文章能够在下面每章章节开始的深刻学习中点击连接查看, 连接中文章均有Github地址,每一个Demo都亲测能够经过,能够下载Demo运行.浏览器
若是喜欢,请帮忙点赞并支持转载,转载请附原文连接.缓存
采集bash
不管是iOS平台,仍是安卓平台,咱们都是须要借助官方的API实现一系列相关功能.首先咱们要明白咱们想要什么,最开始咱们须要一部手机,智能手机中摄像头是不可缺乏的一部分,因此咱们经过一些系统API获取就要能够获取物理摄像头将采集到的视频数据与麦克风采集到的音频数据.服务器
处理微信
音频和视频原始数据本质都是一大段数据,系统将其包装进自定义的结构体中,一般都以回调函数形式提供给咱们,拿到音视频数据后,能够根据各自项目需求作一系列特殊处理,如: 视频的旋转,缩放,滤镜,美颜,裁剪等等功能, 音频的单声道降噪,消除回声,静音等等功能.网络
编码数据结构
原始数据作完自定义处理后就能够进行传输,像直播这样的功能就是把采集好的视频数据发送给服务器,以在网页端供全部粉丝观看,而传输因为自己就是基于网络环境,庞大的原始数据就必须压缩后才能带走,能够理解为咱们搬家要将物品都打包到行李箱这样理解.
传输
编码后的音视频数据一般以RTMP协议进行传输,这是一种专门用于传输音视频的协议,由于各类各样的视频数据格式没法统一,因此须要有一个标准做为传输的规则.协议就起到这样的做用.
解码
服务端接收到咱们送过去的编码数据后,须要对其解码成原始数据,由于编码的数据直接送给物理硬件的设备是不能直接播放的,只有解码为原始数据才能使用.
音视频同步
解码后的每帧音视频中都含有最开始录制时候设置的时间戳,咱们须要根据时间戳将它们正确的播放出来,可是在网络传输中可能会丢失一些数据,或者是延时获取,这时咱们就须要必定的策略去实现音视频的同步,大致分为几种策略:缓存必定视频数据,视频追音频等等.
推流: 将手机采集到的视频数据传给后台播放端进行展现,播放端能够是windows, linux, web端,即手机充当采集的功能,将手机摄像头采集到视频和麦克风采集到的音频合成编码后传给对应平台的播放端。
拉流: 将播放端传来的视频数据在手机上播放,推流的逆过程,即将windows, linux, web端传来的视频数据进行解码后传给对应音视频硬件,最终将视频渲染在手机界面上播放.
推流,拉流实际为互逆过程,这里按照从采集开始介绍.
采集是推流的第一个环节,是原始的音视频数据的来源.采集的原始数据类型为音频数据PCM,视频数据YUV,RGB...。
深刻研究
采集来源
音频主要参数
音频帧
音频与视频不一样,视频每一帧就是一张图片,音频是流式,自己没有明确的帧的概念,实际中为了方便,取2.5ms~60ms为单位的数据为一帧音频.
计算
数据量(字节 / 秒)=(采样频率(Hz)* 采样位数(bit)* 声道数)/ 8
单声道的声道数为1,立体声的声道数为2. 字节B,1MB=1024KB = 1024*1024B
注意: 像一些外置摄像头,如像利用摄像机的摄像头采集,而后用手机将数据处理编码并发出,也是能够的,可是数据的流向须要咱们解析,即从摄像头的HDMI线转成网线口,网线口再转USB,USB转苹果Lighting接口,利用FFmpeg能够获取其中的数据.
视频主要参数
计算 (RGB)
1帧数据量 = 分辨率(width * height) * 每一个像素的所占字节数(通常是3个字节)
注意上面计算的方法并非惟一的,由于视频的数据格式有不少种,如YUV420计算方式为
分辨率(width * height) * 3/2
咱们假设要上传的视频是1080P 30fps(分辨率:1920*1080), 声音是48kHz,那么每秒钟数据量以下:
video = 1920 * 1080 * 30 * 3 = 186624000B = 186.624 MB
audio = (48000 * 16 * 2) / 8 = 192000B = 0.192 MB
复制代码
由此咱们可得,若是直接将原始采集的数据进行传输,那么一部电影就须要1000多G的视频,若是是这样将多么恐怖,因此就涉及到咱们后面的编码环节。
从上一步中,咱们能够获得采集到的音频原始数据和视频原始数据,在移动端,通常是经过各自手机平台官方API中拿到, 前文连接中皆有实现的方法.
以后,咱们能够对原始数据加以处理,对原始操做处理只能在编码以前,由于编码后的数据只能用于传输. 好比可 以对图像处理
对音频处理
目前流行的有不少大型框架专门用来处理视频,音频,如OpenGL, OpenAL, GPUImage...以上的各类处理网上均有开源的库能够实现,基本原理就是,咱们拿到原始的音视频帧数据,将其送给开源库,处理完后再拿处处理好的音视频继续咱们本身的流程.固然不少开源库仍须要根据项目需求略微更改并封装.
在第1.
步采集最后已经讲到,原始的视频每秒钟就产生200多MB,若是直接拿原始数据传输,网络带宽即内存消耗是巨大的,因此视频在传输中是必须通过编码的.
相似的例子就像咱们日常搬家,若是直接搬家,东西很零散,须要跑不少趟拿,若是将衣服,物品打包,咱们仅仅须要几个行李箱就能够一次搞定.等咱们到达新家,再将东西取出来,从新布置,编解码的原理就是如此.
有损压缩
视频利用人眼的视觉特性, 以必定的客观失真换取数据的压缩,好比人眼对亮度识别的阈值,视觉阈值,对亮度和色度的敏感度不一样,以致于能够在编码时引入适量偏差,不会被察觉出来.
音频利用了人类对图像或声波中的某些频率成分不敏感的特性,容许压缩过程当中损失必定的信息;去除声音中冗余成分的方法实现。冗余成分指的是音频中不能被人耳朵察觉的信号,它们对声音的音色,音调等信息没有任何帮助。重构后的数据与原来的数据有所不一样,但不影响人对原始资料表达的信息形成误解。
有损压缩适用于重构信号不必定非要和原始信号彻底相同的场合。
无损压缩
正由于有着上面的压缩方法,视频数据量能够极大的压缩,有利于传输和存储.
原理:编码是如何作到将很大的数据量变小的呢? 主要原理以下
压缩编码的方法
变换编码 (了解便可,具体请谷歌)
将空间域描述的图像信号变换成频率域,而后对变换后的系数进行编码处理。通常来讲,图像在空间上具备较强的相关性,变换频率域能够实现去除相关与能量集中。经常使用的正交变换有离散傅里叶变换,离散余弦变换等等。
熵编码 (了解便可,具体请谷歌)
熵编码是因编码后的平均码长接近信源熵值而得名。熵编码多用可变字长编码(VLC,Variable Length Coding)实现。其基本原理是对信源中出现几率大的符号赋予短码,对于出现几率小的符号赋予长码,从而在统计上得到较短的平均码长。可变字长编码一般有霍夫曼编码、算术编码、游程编码等。
运动估计和运动补偿 (重要)
运动估计和运动补偿是消除图像序列时间方向相关性的有效手段。上面介绍的变换编码,熵编码都是在以一帧图像的基础上进行的,经过这些方法能够消除图像内部各像素在空间上的相关性。实际上图像信号除了空间上的相关性外,还有时间上的相关性。例如对于像新闻联播这种背景静止,画面主体运动较小的数字视频,每一幅画面之间的区别很小,画面之间的相关性很大。对于这种状况咱们没有必要对每一帧图像单独进行编码,而是能够只对相邻视频帧中变化的部分进行编码,从而进一步减少数据量,这方面的工做是由运动估计和运动补偿来实现的。
将当前的输入图像分割成若干彼此不相重叠的小图像子块,例如一帧图像为1280*720,首先将其以网格状形式分红40*45个尺寸为16*16彼此没有重叠的图像块,而后在前一图像或者后一图像某个搜索窗口的范围内为每个图像块寻找一个与之最为类似的图像块,这个搜寻的过程叫作运动估计。
经过计算最类似的图像块与该图像块之间的位置信息,能够获得一个运动矢量。这样在编码的过程当中就能够将当前图像中的块与参考图像运动矢量所指向的最类似的图像块相减,获得一个残差图像块,因为每一个残差图像块中的每一个像素值都很小,因此在压缩编码中能够得到更高的压缩比。
压缩数据类型
正由于运动估计与运动补偿,因此编码器将输入的每一帧图像根据参考图像分红了三种类型:I帧,P帧,B帧。
I帧: 只使用本帧内的数据进行编码,在编码过程当中不须要进行运动估计和运动补偿。
P帧: 在编码过程当中使用前面的I帧或P帧做为参考图像的运动补偿,实际是对当前图像与参考图像的差值进行编码。
B帧: 在编码过程当中使用前面的I帧或P帧和后面的I帧或P帧进行预测。因而可知,每一个P帧利用一帧图像为参考图像。而B帧须要两帧图像做为参考。
实际应用中使用混合编码(变换编码+运动估计,运动补偿+熵编码)
编码器
通过数十年的发展,编码器的功能已经十分强大,种类繁多,下面介绍最主流的一些编码器。
与旧标准相比,它可以在更低带宽下提供优质视频(换言之,只有 MPEG-2,H.263 或 MPEG-4 第 2 部分的一半带宽或更少),也不增长太多设计复杂度使得没法实现或实现成本太高。
高效率视频编码(High Efficiency Video Coding,简称HEVC)是一种视频压缩标准,被视为是 ITU-T H.264/MPEG-4 AVC 标准的继任者。HEVC 被认为不只提高视频质量,同时也能达到 H.264/MPEG-4 AVC 两倍之压缩率(等同于一样画面质量下比特率减小了 50%).
VP8 是一个开放的视频压缩格式,最先由 On2 Technologies 开发,随后由 Google 发布。
VP9 的开发从 2011 年第三季开始,目标是在同画质下,比 VP8 编码减小 50%的文件大小,另外一个目标则是要在编码效率上超越 HEVC 编码。
原理
数字音频压缩编码在保证信号在听觉方面不产生失真的前提下,对音频数据信号进行尽量的压缩。数字音频压缩编码采起去除声音中冗余成分的方法实现。冗余成分指的是音频中不能被人耳朵察觉的信号,它们对声音的音色,音调等信息没有任何帮助。
冗余信号包含人耳听觉范围外的音频信号以及被掩蔽掉的音频信号灯。例如,人耳能察觉的声音频率为20Hz~20kHz,出此以外的其余频率人耳没法察觉,都为冗余信号。此外,根据人耳听觉的生理和心理学现象。当一个强音信号与一个弱音信号同时存在时,弱音信号将被强音信号所掩蔽而听不见,这样弱音信号就能够视为冗余信号不用传送。这就是人耳听觉的掩蔽效应。
压缩编码方法
一个频率的声音能量小于某个阈值以后,人耳就会听不到,这个阈值称为最小可闻阈。当有另外能量较大的声音出现的时候,该声音频率附近的阈值会提升不少,即所谓的掩蔽效应
人耳对2KHz~5KHz的声音最敏感,而对频率过低或过高的声音信号都很迟钝,当有一个频率为0.2KHz、强度为60dB的声音出现时,其附近的阈值提升了不少。
当强音信号和弱音信号同时出现时,还存在时域掩蔽效应,前掩蔽,同时掩蔽,后掩蔽。前掩蔽是指人耳在听到强信号以前的短暂时间内,已经存在的弱信号会被掩蔽而听不到。
- 前掩蔽是指人耳在听到强信号以前的短暂时间内,已经存在的弱信号会被掩蔽而听不到
- 同时掩蔽是指当强信号与弱信号同时存在时,弱信号会被强信号所掩蔽而听不到。
- 后掩蔽是指当强信号消失后,需通过较长的一段时间才能从新听见弱信号,称为后掩蔽。这些被掩蔽的弱信号便可视为冗余信号。
复制代码
封装就是把编码器生成的音频,视频同步以生成咱们肉眼可见,耳朵可听而且看到的与听到的是同步的视频文件.即封装后生成一个容器,来存放音频和视频流以及一些其余信息(好比字幕, metadata等).
在移动端咱们须要借助FFmpeg框架,正如上面介绍的,FFmpeg不只能够作编解码,还能够合成视频流,像经常使用的.flv流,.asf流.
最后, 合成好的数据便可用于写文件或者在网络上传播
FFmpeg 是一个开源框架,能够运行音频和视频多种格式的录影、转换、流功能,包含了 libavcodec: 这是一个用于多个项目中音频和视频的解码器库,以及 libavformat 一个音频与视频格式转换库。
目前支持 Linux ,Mac OS,Windows 三个主流的平台,也能够本身编译到 Android 或者 iOS 平台。 若是是 Mac OS ,能够经过 brew 安装 brew install ffmpeg --with-libvpx --with-libvorbis --with-ffplay
Overview
FLV封装格式分析器。FLV全称是Flash Video,是互联网上使用极为普遍的视频封装格式。像Youtube,优酷这类视频网站,都使用FLV封装视频
FLV(Flash Video)是Adobe公司设计开发的一种流行的流媒体格式,因为其视频文件体积轻巧、封装简单等特色,使其很适合在互联网上进行应用。此外,FLV可使用Flash Player进行播放,而Flash Player插件已经安装在全世界绝大部分浏览器上,这使得经过网页播放FLV视频十分容易。目前主流的视频网站如优酷网,土豆网,乐视网等网站无一例外地使用了FLV格式。FLV封装格式的文件后缀一般为“.flv”。
结构
FLV包括文件头(File Header)和文件体(File Body)两部分,其中文件体由一系列的Tag组成。所以一个FLV文件是如图1结构。
每一个Tag前面还包含了Previous Tag Size字段,表示前面一个Tag的大小。Tag的类型能够是视频、音频和Script,每一个Tag只能包含以上三种类型的数据中的一种。图2展现了FLV文件的详细结构。
优势
缺点
咱们推送出去的流媒体须要传输到观众,整个链路就是传输网络.
RTMP协议是一个互联网TCP/IP五层体系结构中应用层的协议。RTMP协议中基本的数据单元称为消息(Message)。当RTMP协议在互联网中传输数据的时候,消息会被拆分红更小的单元,称为消息块(Chunk)。
消息是RTMP协议中基本的数据单元。不一样种类的消息包含不一样的Message Type ID,表明不一样的功能。RTMP协议中一共规定了十多种消息类型,分别发挥着不一样的做用。
在网络上传输数据时,消息须要被拆分红较小的数据块,才适合在相应的网络环境上传输。RTMP协议中规定,消息在网络上传输时被拆分红消息块(Chunk)。
消息块首部(Chunk Header)有三部分组成:
在消息被分割成几个消息块的过程当中,消息负载部分(Message Body)被分割成大小固定的数据块(默认是128字节,最后一个数据块能够小于该固定长度),并在其首部加上消息块首部(Chunk Header),就组成了相应的消息块。消息分块过程如图5所示,一个大小为307字节的消息被分割成128字节的消息块(除了最后一个)。
RTMP传输媒体数据的过程当中,发送端首先把媒体数据封装成消息,而后把消息分割成消息块,最后将分割后的消息块经过TCP协议发送出去。接收端在经过TCP协议收到数据后,首先把消息块从新组合成消息,而后经过对消息进行解封装处理就能够恢复出媒体数据。
RTMP协议规定,播放一个流媒体有两个前提步骤
其中,网络链接表明服务器端应用程序和客户端之间基础的连通关系。网络流表明了发送多媒体数据的通道。服务器和客户端之间只能创建一个网络链接,可是基于该链接能够建立不少网络流。他们的关系如图所示:
播放一个RTMP协议的流媒体须要通过如下几个步骤:
RTMP链接都是以握手做为开始的。创建链接阶段用于创建客户端与服务器之间的“网络链接”;创建流阶段用于创建客户端与服务器之间的“网络流”;播放阶段用于传输视音频数据。
到这里为止,完整的推流过程已经介绍完成,下面的过程即为逆向过程-拉流.
由于接收端拿到编码的视频流最终仍是想将视频渲染到屏幕上, 将音频经过扬声器等输出设备播出,因此接着上面的步骤,接收端能够经过RTMP协议拿到视频流数据,而后须要利用FFmpeg parse数据,由于咱们须要将数据中的音频跟视频分开,分离出音视频数据后须要分别对它们作解码操做.解码的视频即为YUV/RGB等格式,解码后的音频即为线性PCM数据.
须要注意的是,咱们解码出来的数据并不可以直接使用,由于,手机端若是想要播放解码出来的数据是须要将其放入特定的数据结构中,在iOS中,视频数据须要放入CMSampleBufferRef中,而该数据结构又由CMTime,CMVideoFormatDes,CMBlockBuffer组成,因此咱们须要提供它所须要的信息才能组成系统可以播放的格式.
当咱们拿到解码后的音视频帧时,首先要考虑的问题就是如何同步音视频,在网络正常的状况下是不须要作音视频同步操做,由于咱们parse到的音视频数据里自己带着它们在采集时的时间戳,只要咱们在合理时间内拿到音视频帧,将它们分别送给屏幕与扬声器便可实现同步播放.可是考虑到网络波动,因此可能丢失一些帧或延迟后才能获取,当这种状况出现时就会形成声音视频不一样步,所以须要对音视频作同步处理.
咱们能够这样理解: 有一把尺子 一只蚂蚁(视频)跟着一个标杆(音频)走, 标杆是匀速的 蚂蚁或快或慢,慢了你就抽它 让它跑起来,快了就拽它。这样音视频就能同步了。 这里最大的问题就是音频是匀速的,视频是非线性的。
分别得到音视频的PTS后,咱们有三个选择:视频同步音频(计算音视频PTS之差,来断定视频是否有延迟)、音频同步视频(根据音视频PTS差值调整音频取的样值,即改变音频缓冲区的大小)和音频视频同步外部时钟(同前一个),由于调整音频范围过大,会形成令用户不适的尖锐声,因此一般咱们选择第一种。
咱们的策略是经过比较前一个 PTS 和当前的 PTS 来预测下一帧的 PTS。与此同时,咱们须要同步视频到音频。咱们将建立一个 audio clock 做为内部变量来跟踪音频如今播放的时间点,video thread 将用这个值来计算和判断视频是播快了仍是播慢了。
如今假设咱们有一个 get_audio_clock 函数来返回咱们 audio clock,那当咱们拿到这个值,咱们怎么去处理音视频不一样步的状况呢?若是只是简单的尝试跳到正确的 packet 来解决并非一个很好的方案。咱们要作的是调整下一次刷新的时机:若是视频播慢了咱们就加快刷新,若是视频播快了咱们就减慢刷新。既然咱们调整好了刷新时间,接下来用 frame_timer 跟设备的时钟作一下比较。frame_timer 会一直累加在播放过程当中咱们计算的延时。换而言之,这个 frame_timer 就是播放下一帧的应该对上的时间点。咱们简单的在 frame_timer 上累加新计算的 delay,而后和系统时间比较,并用获得的值来做为时间间隔去刷新。