- av_write_trailer() 写入输出文件的媒体尾部信息
对于AVFormatContext的使用,主要就是读视频和写视频,下面是基本的流程:
读视频流程:
- 1.创建avformat上下文
AVFormatContext *ifmt_ctx = avformat_alloc_context()
- 2.打开视频文件
avformat_open_input(&ifmt_ctx, in_filename, 0, 0)
- 3.持续读取视频帧
while(…) { av_read_frame(ifmt_ctx, &pkt) }
- 4.关闭avformat上下文
avformat_close_input(&ifmt_ctx)
写视频流程:
- 1.创建输出上下文
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename)
- 2.写格式头部
avformat_write_header(ofmt_ctx, NULL)
- 3.持续输出帧
while(…) { av_interleaved_write_frame(ofmt_ctx, &pkt) }
- 4.写格式尾部
av_write_trailer(ofmt_ctx)
- 5.关闭上下文
avformat_free_context(ofmt_ctx)
AVInputFormat
解封装器Demuxer,正式的结构体是AVInputFormat,其实是一个接口,功能是对封装后的格式容器解开获得编码后的音视频的工具。简单说,就是拆包工具。
我们所知道的各种多媒体格式,例如MP4、MP3、FLV等格式的读取,都有AVInputFormat的具体实现。
demuxer的种类很多,而且是可配置的,demuxer有多少,可以看一下demuxer_list.c文件,太多了,不一一列举了,我们举一个mp4 demuxer的例子。
下面是mp4视频格式的解封装器ff_mov_demuxer,在mov.c中:
AVInputFormat ff_mov_demuxer = {
.name = “mov,mp4,m4a,3gp,3g2,mj2”,
.long_name = NULL_IF_CONFIG_SMALL(“QuickTime / MOV”),
.priv_class = &mov_class,
.priv_data_size = sizeof(MOVContext),
.extensions = “mov,mp4,m4a,3gp,3g2,mj2”,
.read_probe = mov_probe,
.read_header = mov_read_header,
.read_packet = mov_read_packet,
.read_close = mov_read_close,
.read_seek = mov_read_seek,
.flags = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS,
};
看到了有几个函数指针:
- read_probe
探测一下什么封装格式
- read_header
读取格式头部数据
- read_packet
读取解封装之后的数据包
- read_close
关闭对象
- read_seek
格式的seek读取控制
你可以看到AVInputFormat提供的是类似接口一样的功能,而ff_mov_demuxer是其的一个具体实现。FFmpeg其实本身的逻辑并不复杂,只是由于支持的格式特别丰富,所以代码才如此多。如果我们先把大部分格式忽略掉,重点关注FFmpeg对其中几个格式的实现,可以更好理解FFmpeg。
AVOutputFormat
封装器 Muxer,对应的结构体是AVOutputFormat,也是一个接口,功能是对编码后的音视频封装进格式容器的工具。简单说,就是打包工具。
跟_解封装器 Demuxer_类似,也是MP4、MP3、FLV等格式的实现,差别是_封装器 Muxer_用于输出。
与demuxer类似,muxer的种类很多,可以看一下muxer_list.c文件。 下面看一下mp3的muxer,在mp3enc.c中:
AVOutputFormat ff_mp3_muxer = {
.name = “mp3”,
.long_name = NULL_IF_CONFIG_SMALL(“MP3 (MPEG audio layer 3)”),
.mime_type = “audio/mpeg”,
.extensions = “mp3”,
.priv_data_size = sizeof(MP3Context),
.audio_codec = AV_CODEC_ID_MP3,
.video_codec = AV_CODEC_ID_PNG,
.write_header = mp3_write_header,
.write_packet = mp3_write_packet,
.write_trailer = mp3_write_trailer,
.query_codec = query_codec,
.flags = AVFMT_NOTIMESTAMPS,
.priv_class = &mp3_muxer_class,
};
上面也有对应的指针函数,是demuxer的反过程。
AVCodecContext
跟AVFormatContext类似,我们也是通过AVCodecContext对_编码器Encoder_和_解码器Decoder_操作,一般也不直接操作编解码器。所以需要实现编解码,一般都要跟AVCodecContext打交道。
和demuxer与muxer一样,codec也有decode和encode之分,具体可以参考codec_list.c文件: 查看ff_libx264_encoder,在libx264.c中:
AVCodec ff_libx264_encoder = {
.name = “libx264”,
.long_name = NULL_IF_CONFIG_SMALL(“libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10”),
.type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_H264,
.priv_data_size = sizeof(X264Context),
.init = X264_init,
.encode2 = X264_frame,
.close = X264_close,
.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS |
AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
.priv_class = &x264_class,
.defaults = x264_defaults,
.init_static_data = X264_init_static,
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
.wrapper_name = “libx264”,
};
其中核心的函数就是encode2,对应X264_frame函数
FFmpeg中的Parser
解析器 Parser,将输入流转换为帧的数据包 由于解码器的输入是一个完整的帧数据包,而无论是网络传输还是文件读取,一般都是固定的buffer来读取的,而不是安装格式的帧大小来读取,所以我们需要解析器Parser将流整理成一个一个的Frame数据包。
parser的全局声明在parsers.c,具体的定义在list_parser.c 看一下h264_parser.c中的ff_h264_parser例子:
AVCodecParser ff_h264_parser = {
.codec_ids = { AV_CODEC_ID_H264 },
.priv_data_size = sizeof(H264ParseContext),
.parser_init = init,
.parser_parse = h264_parse,
.parser_close = h264_close,
.split = h264_split,
};
H264ParseContext结构中是H264格式的帧数据定义。
typedef struct H264ParseContext {
ParseContext pc;
H264ParamSets ps;
H264DSPContext h264dsp;
H264POCContext poc;
H264SEIContext sei;
int is_avc;
int nal_length_size;
int got_first;
int picture_structure;
uint8_t parse_history[6];
int parse_history_count;
int parse_last_mb;
int64_t reference_dts;
int last_frame_num, last_picture_structure;
} H264ParseContext;
这儿大家简单看下,其中H264ParamSets很重要,H264关键的参数都在这儿定义:我们熟知的sps、pps都在这儿定义,有了这两个定义,我们方便在宏块中快速找到当前帧的属性。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
感谢您的阅读,在文末给大家准备一个福利。本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
[外链图片转存中…(img-9CFxwv6J-1712259176696)]
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。