封装层:理论与实战
好文章,来自【福优学苑@音视频+流媒体】
封装格式简介
封装格式(container format)可以看作是编码流(音频流、视频流等)数据的一层外壳,将编码后的数据存储于此封装格式的文件之内。
封装又称容器,容器的称法更为形象,所谓容器,就是存放内容的器具,饮料是内容,那么装饮料的瓶子就是容器。
不同封装格式适用于不同的场合,支持的编码格式不一样,几个常用的封装格式如下:
FFmpeg中的封装格式
FFmpeg关于封装格式的处理涉及打开输入文件、打开输出文件、从输入文件读取编码帧、往输出文件写入编码帧这几个步骤,这些都不涉及编码解码层面。
在FFmpeg中,mux指复用,是multiplex的缩写,表示将多路流(视频、音频、字幕等)混入一路输出中(普通文件、流等)。demux指解复用,是mux的反操作,表示从一路输入中分离出多路流(视频、音频、字幕等)。
mux处理的是输入格式,demux处理的输出格式。输入/输出媒体格式涉及文件格式和封装格式两个概念【封装格式】。
文件格式由文件扩展名标识【文件格式】,主要起提示作用,通过扩展名提示文件类型(或封装格式)信息。封装格式则是存储媒体内容的实际容器格式,不同的封装格式对应不同的文件扩展名,很多时候也用文件格式代指封装格式,例如常用ts格式(文件格式)代指mpegts格式(封装格式)。
例如,我们把test.ts改名为test.mkv,mkv扩展名提示了此文件封装格式为Matroska,但文件内容并无任何变化,使用ffprobe工具仍能正确探测出封装格式为mpegts。
查看FFmpeg支持的封装格式
使用ffmpeg -formats命令可以查看FFmpeg支持的封装格式。FFmpeg支持的封装非常多,下面仅列出最常用的几种:
h264/aac裸流封装格式
h264裸流封装格式和aac裸流封装格式在后面的解复用和复用例程中会用到,这里先讨论一下。
h264本来是编码格式,当作封装格式时表示的是H.264裸流格式,所谓裸流就是不含封装信息的流,也就是没穿衣服的流。aac等封装格式类似。
我们看一下FFmpeg工程源码中h264编码格式以及h264封装格式的定义:
FFmpeg工程包含h264解码器,而不包含h264编码器(一般使用第三方libx264编码器用作h264编码),所以只有解码器定义:
AVCodec ff_h264_decoder = {
.name = "h264",
.long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
.type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_H264,
......
};
h264封装格式定义如下
AVOutputFormat ff_h264_muxer = {
.name = "h264",
.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),
.extensions = "h264,264",
.audio_codec = AV_CODEC_ID_NONE,
.video_codec = AV_CODEC_ID_H264,
.write_header = force_one_stream,
.write_packet = ff_raw_write_packet,
.check_bitstream = h264_check_bitstream,
.flags = AVFMT_NOTIMESTAMPS,
};
AVOutputFormat ff_h264_muxer = {
.name = "h264",
.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),
.extensions = "h264,264",
.audio_codec = AV_CODEC_ID_NONE,
.video_codec = AV_CODEC_ID_H264,
.write_header = force_one_stream,
.write_packet = ff_raw_write_packet,
.check_bitstream = h264_check_bitstream,
.flags = AVFMT_NOTIMESTAMPS,
};
文件扩展名与封装格式
在FFmpeg命令行中,输入文件扩展名是错的也没有关系,因为FFmpeg会读取一小段文件来探测出真正的封装格式;但是如果未显式的指定输出封装格式,就只能通过输出文件扩展名来确定封装格式,就必须确保扩展名是正确的。
API介绍
最主要的API有如下几个。
FFmpeg中将编码帧及未编码帧均称作frame,本文为方便,将编码帧称作packet,未编码帧称作frame。
原始图片(yuv,rgb)或声音()pcm流):未压缩的,未编码的,
H264/265, aac/ac3/mp3:压缩的,编码的
2.1 avformat_open_input()
这个函数会打开输入媒体文件,读取文件头,将文件格式信息存储在第一个参数AVFormatContext中。
2.2 avformat_find_stream_info()
这个函数会读取一段视频文件数据并尝试解码,将取到的流信息填入AVFormatContext.streams中。
AVFormatContext.streams是一个指针数组,数组大小是AVFormatContext.nb_streams
2.3 av_read_frame()
本函数用于解复用过程。
本函数将存储在输入文件中的数据分割为多个packet,每次调用将得到一个packet。packet可能是视频帧、音频帧或其他数据,解码器只会解码视频帧或音频帧,非音视频数据并不会被扔掉、从而能向解码器提供尽可能多的信息。
对于视频来说,一个packet只包含一个视频帧;
对于音频来说,若是帧长固定的格式则一个packet可包含整数个音频帧,若是帧长可变的格式则一个packet只包含一个音频帧。
读取到的packet每次使用完之后应调用av_packet_unref(AVPacket *pkt)清空packet。否则会造成内存泄露。
2.4 av_write_frame()
本函数用于复用过程,将packet写入输出媒体。
packet交织是指:不同流的packet在输出媒体文件中应严格按照packet中dts递增的顺序交错存放。
本函数直接将packet写入复用器(muxer),不会缓存或记录任何packet。本函数不负责不同流的packet交织问题。由调用者负责。
如果调用者不愿处理packet交织问题,应调用av_interleaved_write_frame()替代本函数。
2.5 av_interleaved_write_frame()
本函数用于复用过程,将packet写入输出媒体。
本函数将按需在内部缓存packet,从而确保输出媒体中不同流的packet能按照dts增长的顺序正确交织。
2.6 avio_open()
创建并初始化一个AVIOContext,用于访问输出媒体文件。
2.7 avformat_write_header()
向输出文件写入文件头信息。
2.8 av_write_trailer()
向输出文件写入文件尾信息。
解封装
代码:demuxer_test1.cpp
1.分配解复用器上下文avformat_alloc_context
2.根据url打开本地文件或网络流avformat_open_input
3.读取媒体的部分数据包以获取码流信息avformat_find_stream_info
4.读取码流信息:循环处理
4.1 从文件中读取数据包av_read_frame
4.2 定位文件avformat_seek_file或av_seek_frame
5.关闭解复用器avformat_close_input
本例子实现的是将音视频分离,例如将封装格式为 FLV、MKV、MP4、AVI 等封装格式的文件,将音频、视频读取出来并打印。
实现的过程,可以大致用如下图表示:
转封装
代码:remuxer_test1.c
从一种视频容器转成另一种视频容器
所谓的封装格式转换,就是在AVI,FLV,MKV,MP4这些格式之间转换(对应.avi,.flv,.mkv,.mp4文件)。需要注意的是,本程序并不进行视音频的编码和解码工作。而是直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。
传统的转码程序工作原理如下图所示:
上图例举了一个举例:FLV(视频:H.264,音频:AAC)转码为AVI(视频:MPEG2,音频MP3)的例子。可见视频转码的过程通俗地讲相当于把视频和音频重新“录”了一遍。
本程序的工作原理如下图所示:
由图可见,本程序并不进行视频和音频的编解码工作,因此本程序和普通的转码软件相比,有以下两个特点:
处理速度极快。视音频编解码算法十分复杂,占据了转码的绝大部分时间。因为不需要进行视音频的编码和解码,所以节约了大量的时间。
视音频质量无损。
因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。
***【在线视频教程】***