(强烈推荐)移动端音视频从零到上手(下)
本文已获得作者授权,原文作者:小东邪
4.1 定义
封装就是把编码器生成的音频,视频同步以生成我们肉眼可见,耳朵可听并且看到的与听到的是同步的视频文件.即封装后生成一个容器,来存放音频和视频流以及一些其他信息(比如字幕, metadata等).
4.2 格式
AVI(.AVI):
MOV(.MOV):
MPEG(.MPG,.MPEG,MPE,.DAT,.VOB,.ASF,.3GP,.MP4):
WMV(.WMV,.ASF)
Real Video(.RM,.RMBV):
Flash Video(.FLV):
4.3 将编码数据合成流
在移动端我们需要借助FFmpeg框架,正如上面介绍的,FFmpeg不仅可以做编解码,还可以合成视频流,像常用的.flv流,.asf流.
最后, 合成好的数据即可用于写文件或者在网络上传播
补充: FFmpeg (必学框架)
FFmpeg 是一个开源框架,可以运行音频和视频多种格式的录影、转换、流功能,包含了 libavcodec: 这是一个用于多个项目中音频和视频的解码器库,以及 libavformat 一个音频与视频格式转换库。
目前支持 Linux ,Mac OS,Windows 三个主流的平台,也可以自己编译到 Android 或者 iOS 平台。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”。
结构
每个Tag前面还包含了Previous Tag Size字段,表示前面一个Tag的大小。Tag的类型可以是视频、音频和Script,每个Tag只能包含以上三种类型的数据中的一种。图2展示了FLV文件的详细结构。
5. 将数据通过RTMP协议传输
优点
CDN 支持良好,主流的 CDN 厂商都支持
协议简单,在各平台上实现容易
缺点
基于 TCP ,传输成本高,在弱网环境丢包率高的情况下问题显著
不支持浏览器推送
Adobe 私有协议,Adobe 已经不再更新
我们推送出去的流媒体需要传输到观众,整个链路就是传输网络.
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
2.消息块
在网络上传输数据时,消息需要被拆分成较小的数据块,才适合在相应的网络环境上传输。
RTMP协议中规定,消息在网络上传输时被拆分成消息块(Chunk)。
消息块首部(Chunk Header)有三部分组成:
用于标识本块的Chunk Basic Header
用于标识本块负载所属消息的Chunk Message Header
以及当时间戳溢出时才出现的Extended Timestamp
3.消息分块
在消息被分割成几个消息块的过程中,消息负载部分(Message Body)被分割成大小固定的数据块(默认是128字节,最后一个数据块可以小于该固定长度),并在其首部加上消息块首部(Chunk Header),就组成了相应的消息块。消息分块过程如图5所示,一个大小为307字节的消息被分割成128字节的消息块(除了最后一个)。
RTMP传输媒体数据的过程中,发送端首先把媒体数据封装成消息,然后把消息分割成消息块,最后将分割后的消息块通过TCP协议发送出去。接收端在通过TCP协议收到数据后,首先把消息块重新组合成消息,然后通过对消息进行解封装处理就可以恢复出媒体数据。
4.RTMP中的逻辑结构
RTMP协议规定,播放一个流媒体有两个前提步骤
第一步,建立一个网络连接(NetConnection)
第二步,建立一个网络流(NetStream)
其中,网络连接代表服务器端应用程序和客户端之间基础的连通关系。网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。他们的关系如图所示:
5. 连接流程
播放一个RTMP协议的流媒体需要经过以下几个步骤:
握手
建立连接
建立流
播放
RTMP连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的网络连接;建立流阶段用于建立客户端与服务器之间的网络流;播放阶段用于传输视音频数据。
6. 解析并解码视频流
深入研究
iOS完整文件拉流解析解码同步渲染音视频流
FFmpeg解析视频数据
iOS利用FFmpeg实现视频硬解码
iOS利用VideoToolbox实现视频硬解码
iOS利用FFmpeg实现音频硬解码
iOS利用原生音频解码
到这里为止,完整的推流过程已经介绍完成,下面的过程即为逆向过程-拉流.
因为接收端拿到编码的视频流最终还是想将视频渲染到屏幕上, 将音频通过扬声器等输出设备播出,所以接着上面的步骤,接收端可以通过RTMP协议拿到视频流数据,然后需要利用FFmpeg parse数据,因为我们需要将数据中的音频跟视频分开,分离出音视频数据后需要分别对它们做解码操作.解码的视频即为YUV/RGB等格式,解码后的音频即为线性PCM数据.
需要注意的是,我们解码出来的数据并不能够直接使用,因为,手机端如果想要播放解码出来的数据是需要将其放入特定的数据结构中,在iOS中,视频数据需要放入CMSampleBufferRef中,而该数据结构又由CMTime,CMVideoFormatDes,CMBlockBuffer组成,所以我们需要提供它所需要的信息才能组成系统能够播放的格式.
7. 音视频同步并播放
深入研究
iOS视频渲染
AudioQueue实现音频流实时播放实战
当我们拿到解码后的音视频帧时,首先要考虑的问题就是如何同步音视频,在网络正常的情况下是不需要做音视频同步操作,因为我们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,然后和系统时间比较,并用得到的值来作为时间间隔去刷新。
关注微信公众号【纸上浅谈】, Android 开发、Camera、OpenGL、FFmpeg 等音视频和图形图像开发文章~~