移动端音视频从零到上手

640?wx_fmt=jpeg

黑客技术 点击右侧关注,了解黑客的世界! 640?wx_fmt=jpeg

640?wx_fmt=jpeg

Java开发进阶 点击右侧关注,掌握进阶之路! 640?wx_fmt=jpeg

640?wx_fmt=jpeg

Linux编程 点击右侧关注,免费入门到精通! 640?wx_fmt=jpeg


640?wx_fmt=jpeg


作者 | 小东邪,TVU Networks音视频开发 
https://juejin.im/post/5d29d884f265da1b971aa220

概述

随着整个互联网的崛起,数据传递的形式也在不断升级变化,总的流行趋势如下:

纯文本的短信,QQ -> 空间,微博,朋友圈的图片文字结合 -> 微信语音 -> 各大直播软件 -> 抖音短视频

音视频的发展正在向各个行业不断扩展,从教育的远程授课,交通的人脸识别,医疗的远程就医等等,音视频方向已经占据一个相当重要的位置,而音视频真正入门的文章又少之甚少,一个刚毕业小白可能很难切入理解,因为音视频中涉及大量理论知识,而代码的书写需要结合这些理论,所以搞懂音视频,编解码等理论知识至关重要.本人也是从实习开始接触音视频项目,看过很多人的文章,在这里总结一个通俗易懂的文章,让更多准备学习音视频的同学更快入门。

划重点

本文中理论知识来自于各种音视频文章的归纳音视频编码基本原理汇总,其中也会有一些我自己总结增加的部分.若有错误可评论,检查后会更正.

为了防止大家理解过于空洞,作者花了三个月时间将最常用,最重要的一些功能的理论知识及实战Demo亲自写出来,配合文章阅读效果更佳.每一部分的文章可以在下面每章章节开始的深入学习中点击链接查看, 链接中文章均有Github地址,每个Demo都亲测可以通过,可以下载Demo运行.

如果喜欢,请帮忙点赞并支持转载,转载请附原文链接.

原理

• 采集

无论是iOS平台,还是安卓平台,我们都是需要借助官方的API实现一系列相关功能.首先我们要明白我们想要什么,最开始我们需要一部手机,智能手机中摄像头是不可缺少的一部分,所以我们通过一些系统API获取就要可以获取物理摄像头将采集到的视频数据与麦克风采集到的音频数据.

• 处理

音频和视频原始数据本质都是一大段数据,系统将其包装进自定义的结构体中,通常都以回调函数形式提供给我们,拿到音视频数据后,可以根据各自项目需求做一系列特殊处理,如: 视频的旋转,缩放,滤镜,美颜,裁剪等等功能, 音频的单声道降噪,消除回声,静音等等功能.

• 编码

原始数据做完自定义处理后就可以进行传输,像直播这样的功能就是把采集好的视频数据发送给服务器,以在网页端供所有粉丝观看,而传输由于本身就是基于网络环境,庞大的原始数据就必须压缩后才能带走,可以理解为我们搬家要将物品都打包到行李箱这样理解.

• 传输

编码后的音视频数据通常以RTMP协议进行传输,这是一种专门用于传输音视频的协议,因为各种各样的视频数据格式无法统一,所以需要有一个标准作为传输的规则.协议就起到这样的作用.

• 解码

服务端接收到我们送过去的编码数据后,需要对其解码成原始数据,因为编码的数据直接送给物理硬件的设备是不能直接播放的,只有解码为原始数据才能使用.

• 音视频同步

解码后的每帧音视频中都含有最开始录制时候设置的时间戳,我们需要根据时间戳将它们正确的播放出来,但是在网络传输中可能会丢失一些数据,或者是延时获取,这时我们就需要一定的策略去实现音视频的同步,大体分为几种策略:缓存一定视频数据,视频追音频等等.

推流,拉流流程

• 推流: 将手机采集到的视频数据传给后台播放端进行展示,播放端可以是windows, linux, web端,即手机充当采集的功能,将手机摄像头采集到视频和麦克风采集到的音频合成编码后传给对应平台的播放端。

• 拉流: 将播放端传来的视频数据在手机上播放,推流的逆过程,即将windows, linux, web端传来的视频数据进行解码后传给对应音视频硬件,最终将视频渲染在手机界面上播放.

推流如下:

640?wx_fmt=png

拉流如下:

640?wx_fmt=png

具体剖析

推流,拉流实际为互逆过程,这里按照从采集开始介绍.

1. 采集

采集是推流的第一个环节,是原始的音视频数据的来源.采集的原始数据类型为音频数据PCM,视频数据YUV,RGB...。

1.1. 音频采集

• 深入研究

 
 

• 采集来源

 
 

• 音频主要参数

 
 

• 音频帧

音频与视频不同,视频每一帧就是一张图片,音频是流式,本身没有明确的帧的概念,实际中为了方便,取2.5ms~60ms为单位的数据为一帧音频.

• 计算

数据量(字节 / 秒)=(采样频率(Hz)* 采样位数(bit)* 声道数)/ 8

单声道的声道数为1,立体声的声道数为2. 字节B,1MB=1024KB = 1024*1024B

1.2. 视频采集

• 深入研究

 
 

• 采集来源

 
 

注意: 像一些外置摄像头,如像利用摄像机的摄像头采集,然后用手机将数据处理编码并发出,也是可以的,但是数据的流向需要我们解析,即从摄像头的HDMI线转成网线口,网线口再转USB,USB转苹果Lighting接口,利用FFmpeg可以获取其中的数据.

• 视频主要参数

 
 

• 计算 (RGB)

1帧数据量 = 分辨率(width * height) * 每个像素的所占字节数(一般是3个字节)

注意上面计算的方法并不是唯一的,因为视频的数据格式有很多种,如YUV420计算方式为分辨率(width * height) * 3/2

1.3. 综上所述

我们假设要上传的视频是1080P 30fps(分辨率:1920*1080), 声音是48kHz,那么每秒钟数据量如下:

 
 

由此我们可得,如果直接将原始采集的数据进行传输,那么一部电影就需要1000多G的视频,如果是这样将多么恐怖,所以就涉及到我们后面的编码环节。

2. 处理

• 深入研究 (待添加)

 
 

从上一步中,我们可以得到采集到的音频原始数据和视频原始数据,在移动端,一般是通过各自手机平台官方API中拿到, 前文链接中皆有实现的方法.
之后,我们可以对原始数据加以处理,对原始操作处理只能在编码之前,因为编码后的数据只能用于传输. 比如可以对图像处理

• 美颜
• 水印
• 滤镜
• 裁剪
• 旋转
• ...

对音频处理

• 混音
• 消除回声
• 降噪
• ...

目前流行的有很多大型框架专门用来处理视频,音频,如OpenGL, OpenAL, GPUImage...以上的各种处理网上均有开源的库可以实现,基本原理就是,我们拿到原始的音视频帧数据,将其送给开源库,处理完后再拿到处理好的音视频继续我们自己的流程.当然很多开源库仍需要根据项目需求略微更改并封装.

3.编码

• 深入研究

 
 

3.1. 为什么要编码

在第1.步采集最后已经讲到,原始的视频每秒钟就产生200多MB,如果直接拿原始数据传输,网络带宽即内存消耗是巨大的,所以视频在传输中是必须经过编码的.

类似的例子就像我们平常搬家,如果直接搬家,东西很零散,需要跑很多趟拿,如果将衣服,物品打包,我们仅仅需要几个行李箱就可以一次搞定.等我们到达新家,再将东西取出来,重新布置,编解码的原理就是如此.

3.2. 有损压缩 VS 无损压缩

• 有损压缩

 
 

有损压缩适用于重构信号不一定非要和原始信号完全相同的场合。

• 无损压缩

 
 

正因为有着上面的压缩方法,视频数据量可以极大的压缩,有利于传输和存储.

3.3. 视频编码

• 原理:编码是如何做到将很大的数据量变小的呢? 主要原理如下

 
 

• 压缩编码的方法

 
 

a. 运动估计技术

将当前的输入图像分割成若干彼此不相重叠的小图像子块,例如一帧图像为1280720,首先将其以网格状形式分成4045个尺寸为16*16彼此没有重叠的图像块,然后在前一图像或者后一图像某个搜索窗口的范围内为每一个图像块寻找一个与之最为相似的图像块,这个搜寻的过程叫做运动估计。

b. 运动补偿

通过计算最相似的图像块与该图像块之间的位置信息,可以得到一个运动矢量。这样在编码的过程中就可以将当前图像中的块与参考图像运动矢量所指向的最相似的图像块相减,得到一个残差图像块,由于每个残差图像块中的每个像素值都很小,所以在压缩编码中可以获得更高的压缩比。

• 压缩数据类型

正因为运动估计与运动补偿,所以编码器将输入的每一帧图像根据参考图像分成了三种类型:I帧,P帧,B帧。

 
 

实际应用中使用混合编码(变换编码+运动估计,运动补偿+熵编码)

640?wx_fmt=png

• 编码器

经过数十年的发展,编码器的功能已经十分强大,种类繁多,下面介绍最主流的一些编码器。

 
 

3.4. 音频编码

• 原理

数字音频压缩编码在保证信号在听觉方面不产生失真的前提下,对音频数据信号进行尽可能的压缩。数字音频压缩编码采取去除声音中冗余成分的方法实现。冗余成分指的是音频中不能被人耳朵察觉的信号,它们对声音的音色,音调等信息没有任何帮助。

冗余信号包含人耳听觉范围外的音频信号以及被掩蔽掉的音频信号灯。例如,人耳能察觉的声音频率为20Hz~20kHz,出此之外的其他频率人耳无法察觉,都为冗余信号。此外,根据人耳听觉的生理和心理学现象。当一个强音信号与一个弱音信号同时存在时,弱音信号将被强音信号所掩蔽而听不见,这样弱音信号就可以视为冗余信号不用传送。这就是人耳听觉的掩蔽效应。

• 压缩编码方法

 
 

640?wx_fmt=png

4. 封装编码数据

• 深入研究

 
 

4.1 定义

封装就是把编码器生成的音频,视频同步以生成我们肉眼可见,耳朵可听并且看到的与听到的是同步的视频文件.即封装后生成一个容器,来存放音频和视频流以及一些其他信息(比如字幕, metadata等).

4.2 格式

• AVI(.AVI):

 
 

• MOV(.MOV): 美国Apple公司开发的一种视频格式,默认的播放器是苹果的QuickTime。

• MPEG(.MPG,.MPEG,MPE,.DAT,.VOB,.ASF,.3GP,.MP4):

• WMV(.WMV,.ASF)

• Real Video(.RM,.RMBV): 根据不同的网络传输速率制定出不同的压缩比率,从而实现在低速率的网络上进行影像数据实时传送和播放

• Flash Video(.FLV):由 Adobe Flash 延伸出来的的一种流行网络视频封装格式。随着视频网站的丰富,这个格式已经非常普及。

4.3 将编码数据合成流

在移动端我们需要借助FFmpeg框架,正如上面介绍的,FFmpeg不仅可以做编解码,还可以合成视频流,像常用的.flv流,.asf流.

最后, 合成好的数据即可用于写文件或者在网络上传播

补充: FFmpeg (必学框架)

FFmpeg 是一个开源框架,可以运行音频和视频多种格式的录影、转换、流功能,包含了 libavcodec: 这是一个用于多个项目中音频和视频的解码器库,以及 libavformat 一个音频与视频格式转换库。

目前支持 Linux ,Mac OS,Windows 三个主流的平台,也可以自己编译到 Android 或者 iOS 平台。

如果是 Mac OS ,可以通过 brew 安装 brew install ffmpeg --with-libvpx --with-libvorbis --with-ffplay

4.4. FLV流简介

• 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结构。

640?wx_fmt=png

每个Tag前面还包含了Previous Tag Size字段,表示前面一个Tag的大小。Tag的类型可以是视频、音频和Script,每个Tag只能包含以上三种类型的数据中的一种。图2展示了FLV文件的详细结构。

640?wx_fmt=png

5. 将数据通过RTMP协议传输

• 优点

 
 

• 缺点

 
 

我们推送出去的流媒体需要传输到观众,整个链路就是传输网络.

5.1. Overview

RTMP协议是一个互联网TCP/IP五层体系结构中应用层的协议。RTMP协议中基本的数据单元称为消息(Message)。当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块(Chunk)。

5.2. 消息

消息是RTMP协议中基本的数据单元。不同种类的消息包含不同的Message Type ID,代表不同的功能。RTMP协议中一共规定了十多种消息类型,分别发挥着不同的作用。

• 1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需操作其中的数据

• Message Type ID为8,9的消息分别用于传输音频和视频数据

• Message Type ID为15-20的消息用于发送AMF编码的命令,负责用户与服务器之间的交互,比如播放,暂停等等

消息首部(Message Header)有四部分组成:标志消息类型的Message Type ID,标志消息长度的Payload Length,标识时间戳的Timestamp,标识消息所属媒体流的Stream ID

640?wx_fmt=png

5.3. 消息块

在网络上传输数据时,消息需要被拆分成较小的数据块,才适合在相应的网络环境上传输。RTMP协议中规定,消息在网络上传输时被拆分成消息块(Chunk)。

消息块首部(Chunk Header)有三部分组成:

• 用于标识本块的Chunk Basic Header

• 用于标识本块负载所属消息的Chunk Message Header

• 以及当时间戳溢出时才出现的Extended Timestamp

640?wx_fmt=png

5.4.消息分块

在消息被分割成几个消息块的过程中,消息负载部分(Message Body)被分割成大小固定的数据块(默认是128字节,最后一个数据块可以小于该固定长度),并在其首部加上消息块首部(Chunk Header),就组成了相应的消息块。消息分块过程如图5所示,一个大小为307字节的消息被分割成128字节的消息块(除了最后一个)。

RTMP传输媒体数据的过程中,发送端首先把媒体数据封装成消息,然后把消息分割成消息块,最后将分割后的消息块通过TCP协议发送出去。接收端在通过TCP协议收到数据后,首先把消息块重新组合成消息,然后通过对消息进行解封装处理就可以恢复出媒体数据。

640?wx_fmt=png

5.5.RTMP中的逻辑结构

RTMP协议规定,播放一个流媒体有两个前提步骤

• 第一步,建立一个网络连接(NetConnection)

• 第二步,建立一个网络流(NetStream)。

其中,网络连接代表服务器端应用程序和客户端之间基础的连通关系。网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。他们的关系如图所示:

640?wx_fmt=png

5.6. 连接流程

播放一个RTMP协议的流媒体需要经过以下几个步骤:

• 握手
• 建立连接
• 建立流
• 播放

RTMP连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的“网络连接”;建立流阶段用于建立客户端与服务器之间的“网络流”;播放阶段用于传输视音频数据。

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

6. 解析并解码视频流

• 深入研究

 
 

到这里为止,完整的推流过程已经介绍完成,下面的过程即为逆向过程-拉流.

因为接收端拿到编码的视频流最终还是想将视频渲染到屏幕上, 将音频通过扬声器等输出设备播出,所以接着上面的步骤,接收端可以通过RTMP协议拿到视频流数据,然后需要利用FFmpeg parse数据,因为我们需要将数据中的音频跟视频分开,分离出音视频数据后需要分别对它们做解码操作.解码的视频即为YUV/RGB等格式,解码后的音频即为线性PCM数据.

需要注意的是,我们解码出来的数据并不能够直接使用,因为,手机端如果想要播放解码出来的数据是需要将其放入特定的数据结构中,在iOS中,视频数据需要放入CMSampleBufferRef中,而该数据结构又由CMTime,CMVideoFormatDes,CMBlockBuffer组成,所以我们需要提供它所需要的信息才能组成系统能够播放的格式.

640?wx_fmt=png

7. 音视频同步并播放

• 深入研究

当我们拿到解码后的音视频帧时,首先要考虑的问题就是如何同步音视频,在网络正常的情况下是不需要做音视频同步操作,因为我们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,然后和系统时间比较,并用得到的值来作为时间间隔去刷新。

参考

[1]https://blog.csdn.net/leixiaohua1020/article/details/18893769
[2]https://www.zhihu.com/org/qi-niu-yun/posts?page=6


 推荐↓↓↓ 

640?wx_fmt=jpeg

?16个技术公众号】都在这里!

涵盖:程序员大咖、源码共读、程序员共读、数据结构与算法、黑客技术和网络安全、大数据科技、编程前端、Java、Python、Web编程开发、Android、iOS开发、Linux、数据库研发、幽默程序员等。

640?wx_fmt=png万水千山总是情,点个 “ 在看” 行不行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值