除了之前讲的 avpacket 跟 avframe,FFMPEG 还有其它一些结构经常在流程中出现. FFMPEG 还有哪些常见的结构呢? 先来看一下这个截图:
这张图中的主角, 是 AVFormatContext.AVFormatContext 是 FFMPEG 的基本结构之一, 对应于封装格式(或容器格式).
围绕 FFMPEG 的 "格式场景", 本文介绍 FFMPEG 常见的数据结构. 按照上图, 小程依次介绍图中的几个结构体.
(一)AVCodec
AVCodec 是 FFMPEG 设计上的一个结构体, 用来保存编解码器的信息, 也就是说, AVCodec 是编码器或解码器.
还是以调试的办法, 具体看一下 AVCodec 变量中的内容吧.
(1)演示代码
演示代码的目录结构是这样的:
其中的 FFMPEG 静态库是事先编译好的(这里是 macos 版本, 因为我现在用的是 Mac 电脑), 编译的办法可以参考之前的文章.
moments.mp4 是试用的视频文件(mp4 封装格式).
makefile 是编译脚本, 用来编译演示代码, 当然也可以直接用 gcc 来编译.
show_avcodec.c 就是演示代码了, 内容如下:
- #include "libavcodec/avcodec.h"
- #include "libavformat/avformat.h"
- void show_avcodec(const char* filepath) {
- av_register_all();
- av_log_set_level(AV_LOG_DEBUG);
- AVFormatContext* formatContext = avformat_alloc_context();
- int status = 0;
- int success = 0;
- int videostreamidx = -1;
- AVCodecContext* codecContext = NULL;
- status = avformat_open_input(&formatContext, filepath, NULL, NULL);
- if (status == 0) {
- status = avformat_find_stream_info(formatContext, NULL);
- if (status>= 0) {
- for (int i = 0; i <formatContext->nb_streams; i ++) {
- if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
- videostreamidx = i;
- break;
- }
- }
- if (videostreamidx> -1) {
- codecContext = formatContext->streams[videostreamidx]->codec;
- AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
- if (codec) {
- status = avcodec_open2(codecContext, codec, NULL);
- if (status == 0) {
- success = 1;
- }
- }
- }
- }
- else {
- av_log(NULL, AV_LOG_DEBUG, "avformat_find_stream_info error\n");
- }
- avformat_close_input(&formatContext);
- }
- avformat_free_context(formatContext);
- }
- int main(int argc, char *argv[])
- {
- show_avcodec("moments.mp4");
- return 0;
- }
你已经看到一个常规的 FFMPEG 使用套路: open_input 得到 avformatcontext, 然后 find_stream_info 得到 avstream, 再从 avstream 中找到 avcodeccontext, 然后找到 avcodec, 最后 open 开 avcodec.
(2)编译与调试
makefile 的内容:
- exe=showavcodec
- srcs=show_avcodec.c
- $(exe):$(srcs)
- gcc -o $(exe) $(srcs) -Iffmpeg/include/ -Lffmpeg -lffmpeg -liconv -lz -g
- clean:
- rm -f $(exe) *.o
直接执行 make 来编译, 编译后会生成符号表文件即 showavcodec.dSYM.
这里只是简单看一下 AVCodec 的内容, 用 gdb 来调试即可:
- gdb showavcodec
- b 25
- r
在断点的地方, 看一下 AVCodec 变量中的值:
(3)AVCodec 结构内容
AVCodec 是编解码器的结构体, 在 libavcodec/avcodec.h 中定义.
在这个示例中, AVCodec 是一个解码器.
AVCodec 结构中的一些变量, 从它的名字或者 FFMPEG 详细的注释中, 可以知道是什么含义.
比如 name 是编解码的名称, 而 long_name 就是长的名称, 等等.
在设计上, AVCodec 是编解码器的抽象, 所以, 编解码器是有相应的具体实现的.
事实上, 每一个编解码器都有具体的实现.
比如 h264 的解码器(libavcodec/h264.c):
比如 mp3lame 的编码器(libavcodec/libmp3lame.c)
FFMPEG 使用这些具体的编解码器的实现, 以完成编解码等功能.
(二)AVCodecContext
AVCodecContext 可以简单理解为 AVCodec 的使用场景, 而实际上 AVCodecContext 包括的内容, 除了关联 AVCodec, 还有其它信息.
跟调试 AVCodec 变量一样, 直接使用上面的演示代码就可以调试 AVCodecContext, 部分代码如下 :
- if (videostreamidx> -1) {
- codecContext = formatContext->streams[videostreamidx]->codec;
- AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
- if (codec) {
- status = avcodec_open2(codecContext, codec, NULL);
- if (status == 0) {
- success = 1;
- }
- }
- }
同样用 gdb 来调试就可以了, 在拿到 codecContext 后下断点, 可以看到 AVCodecContext 的部分内容如下:
其中有一些变量应该引起注意, 比如:
width/height 视频的宽与高
codec_id 编解码器的 id, 根据它可以找到对应的编解码器
extradata 对于 h264 编码的视频, 保存了 pps 与 sps 的参数信息
profile 视频编码复杂等级
sample_rate 音频的采样率
channels 音频的声道数
sample_fmt 音频的采样格式
跟 AVCodec 一样, AVCodecContext 结构体在 libavcodec/avcodec.h 中定义.
(三)AVStream
上面介绍了 AVCodec,AVCodecContext, 现在介绍 AVStream.
这三者的大概关系是这样的:
AVStream 对应音频流, 视频流, 字幕等媒体流. FFmepg 以流的概念来封装不同的媒体.
调试 AVStream 的示例代码与编译, 可以查看上面 AVCodec 调试的介绍. 大概如下:
下断点, 可以看到 AVStream 中的内容, 比如:
AVStream 中的一些变量:
index, 流的索引
codec, 流对应的 avcodeccontext
time_base, 时间基准(比例)
duration, 流的时长
metadata, 流的元信息
nb_frames, 流中帧的数量
AVStream 结构, 在 libavformat/avformat.h 中定义.
(四)AVFormatContext
AVFormatContext 是主角, 表示为格式的场景, 对应于封装格式(或容器格式).
同样, 使用之前的示例代码, 在 avformat_open_input 函数后下断点:
可以查看 avformatcontext 结构中的变量值:
AVFormatContext 中的 metadata 记录了多媒体文件的一些信息(比如作者, 专辑之类), 可以这样取得里面的信息:
- if (formatCtx->metadata) {
- AVDictionaryEntry *item = NULL;
- while((item = av_dict_get(formatCtx->metadata, "", item, AV_DICT_IGNORE_SUFFIX))){
- printf("key:%s value:%s \n", item->key, item->value);
- }
- // 或者这样:
- AVDictionaryEntry *tag = NULL;
- tag = av_dict_get(formatCtx->metadata, "artist", NULL, 0);
- if (tag) {
- std::string artist = (char*)tag->value;
- }
- }
AVFormatContext 的一些变量说明:
iformat/oformat, 输入 / 输出格式, 在解复用 (解封装) 或复用 (封装) 时使用.
pb, 输入或输出场景, 提供数据操作接口(比如读写, seek 等).
nb_streams, 流的个数(以流的方式来复用).
streams, 流的数组.
filename, 文件名.
start_time, 流的起始时间, 以 AV_TIME_BASE 为单位(除以 AV_TIME_BASE 转为秒).
duration, 流的时长, 以 AV_TIME_BASE 为单位.
bit_rate, 比特率.
probesize, 在检测容器格式时, 最大的探测大小, 在 avformat_open_input 之前设置(或不设置使用默认值).
max_analyze_duration, 最大的分析数据的时长, 在检测编码格式时使用, 在 avformat_find_stream_info 前设置(或不设置), 越大越耗时.
metadata, 元信息.
AVFormatContext 结构, 在 libavformat/avformat.h 中定义.
(五)AVIOContext
AVIOContext 是输入输出信息的结构体, 它在 FFMPEG 结构体系中的位置是这样的:
可以看到, AVIOContext 是 AVFormatContext 的一个成员, 叫作 pb.
pb 是提供数据的变量, 既用于读 (解码) 也用于写(编码).
(1)解码时
在解码时, pb 提供解码的原始数据, 一般在调用 avio_alloc_context 创建 aviocontext 时, 指定 read 与 seek 函数 (自定义的实现, 提供读数据, 跳转位置的功能), 然后把创建的 aviocontext(即 pb) 直接设置给 AVFormatContext. 比如这样:
- pb = avio_alloc_context(readBuf, readBufLen, 0, this, myReadFunc, NULL, mySeekFunc);
- mFormatCtx->pb = pb;
或者, 在解码时, 这样使用 aviocontext:
- mIOContext.read_packet = myReadFunc;
- mIOContext.seek = mySeekFunc;
- const int MAX_PRO_SIZE = 32*1024;
- unsigned char* probuf = (unsigned char*)av_malloc(MAX_PRO_SIZE);
- mIOContext.buffer = probuf;
- mIOContext.buf_ptr = probuf;
- mIOContext.buffer_size = MAX_PRO_SIZE;
- mIOContext.buf_end = probuf + MAX_PRO_SIZE;
- mIOContext.max_packet_size = MAX_PRO_SIZE;
- formatContext->pb = &mIOContext;
其中, 函数 myReadFunc 与 mySeekFunc, 按照结构体 AVIOContext 中的格式说明 (参照头文件说明) 来定义即可.
解码时, pb 的设置, 要在 avformat_open_input 调用前完成.
(2)编码时
在编码写文件时, pb 提供写到文件的数据(编码后的数据, 对应 AVPacket), 比如可以直接用 avio_open2 来打开 pb:
- if (!(mFormatContext->flags & AVFMT_NOFILE)) {
- err = avio_open2(&mFormatContext->pb, url, AVIO_FLAG_WRITE, &mFormatContext->interrupt_callback, NULL);
- // ...
- }
写文件时, 可以用 av_write_frame 来写入一个 packet, 也可以用 avio_write 往 pb 中写入数据.
编码写文件时, pb 的设置, 要在 avformat_write_header 调用前完成.
(3)AVIOContext 的变量
这里只列表一部分:
buffer,AVIOContext 缓存数据的 buffer, 起始地址.
buffer_size,buffer 的大小.
buf_ptr, 操作 buffer 的当前位置.
buf_end, 数据的结束位置, 有可能未到 buffer 的未端.
opaque, 指向 URLContext, 提供读, 写, seek 等接口, 可以让它为空, 从而使用自定义的接口.
read_packet/write_packet/seek, 读写与 seek 的接口, 可以在 avio_alloc_context 时指定, 从而自定义.
AVIOContext 结构体在 libavformat/avio.h 中定义.
至此, FFMPEG 常见的几个结构体就介绍完毕了.
总结一下, 本文介绍了 FFMPEG 的常见结构体, 包括 AVFormatContext,AVIOContext,AVStream,AVCodecContext,AVCodec 等, 并且以调试的方式查看了结构体的一些变量值.
来源: https://www.cnblogs.com/freeself/p/11050845.html