FFmpeg API 之 libavformat库
libavformat库,是FFmpeg中用于处理各种媒体容器格式(media container format)的库。它的两个最主要的功能是 :demuxing:解封装,将一个媒体文件分割为多个多媒体流muxing:封装,将多个多媒体数据流写入到指定媒体容器格式的文件中这两个过程所做的事情正好相反,是互逆的。我们注意到,在 mux 和 demux 的过程中,均少不了要从 input 读取 和 写
libavformat库,是FFmpeg中用于处理各种媒体容器格式(media container format)的库。它的两个最主要的功能是 :
-
demuxing:解封装,将一个媒体文件分割为多个多媒体流
-
muxing:封装,将多个多媒体数据流写入到指定媒体容器格式的文件中
这两个过程所做的事情正好相反,是互逆的。
我们注意到,在 mux 和 demux 的过程中,均少不了要从 input 读取 和 写入 output 的过程,因此 libavformat 单独分出了一个子模块,用于封装相关的 I/O 过程,称之为 I/O 模块。
I/O 模块支持访问多个协议的数据,例如 file,tcp,http等。在一般的情况下,我们不需要自己来处理这些 I/O 操作,仅需交给 libavformat 的实现处理。当然,如果我们想要更加精细地控制 I/O 的过程,或者想要提供自定义的 I/O 操作,也是可以的,我们后面会有描述。
1、基本概念
在使用 libavformat 库之前,我们需要调用 av_register_all()
来注册所有编译后的 muxer、demuxer 和协议。我们还需要调用 avformat_network_init()
来初始化 libavformat 中的网络功能(如果我们不使用网络,也不可能不调用)。同样的,如果我们要使用输入输出设备,也需要调用 avdevice_register_all()
来初始化所有的输入输出设备。
在FFmpeg中,一个可用的输入格式可以用结构体 AVInputFormat
来描述,一个可用的输出格式可以用结构体AVOutputFormat
来描述。
我们可以使用 av_iformat_next()
遍历所有的已注册的输入格式,用 av_oformat_next()
函数遍历所有的已注册输出格式。可以通过 avio_enum_protocols()
函数获得可用的 protocols 的名称。
libavformat 库中用于 muxing 和 demuxing 的最主要的结构体是 AVFormatContext
, 通过它可以获取要被读取或者写入文件的全部的信息。 如果想要创建一个 AVFormatContext
,最直接的方法是使用 avformat_alloc_context()
函数,它可以创建一个空白的 AVFormatContext
。当然也可以通过其他函数来创建,比如 avformat_open_input()
也可以返回一个AVFormatContext
的实例。
AVFormatContext
中最重要的字段如下:
-
AVFormatContext.iformat(输入格式)或 AVFormatContext.oformat (输出格式)。输入时,它可以自动检测,也可以由用户设置;输出时,总是由用户设置。
-
AVFormatContext.streams,它表示 AVStreams ”数组”,描述了所有存储在文件中的基本流。AVStreams通常使用相关的索引来引用这个数据中的流。
-
AVFormatContext.pb,它表示一个 “I/O context”。在输入时,它要么由 libavformat 打开,要么由用户设置;输出时,总是由用户设置(除非你使用了AVFMT_NOFILE 格式,可以不设置它)。
2、Urls
url 用于指定输入和输出的媒体,它可以是文件,设备,网络上的流,管道等等。
libavformat 中的使用的 URL 字符串结构为:
scheme/protocol + ':' + 一个 scheme 相关的字符串
没有 scheme 和 ':' 的 url 表示一个本地文件,这种写法虽然仍被支持但不赞成使用。我们应该使用 "file:" 来表示一个本地文件。
注意:scheme 字符串非常重要,使用时,必须做相关的检测。
注意:一些 schemes/protocols 非常强大,允许访问本地和远程文件, 甚至是一个文件的一部分,多个文件的串联读取,本地音频和视频设备等。
3、解封装
Demuxers(解封装器) 用于读取媒体文件的数据,并将其分割成一个个数据块(也就是 packets)。FFmpeg 中我们使用 AVPacket
结构体来表示一个 packet,一个 AVPacket
中包含一个或多个已编码的帧数据,这些帧都属于同一个基本流。
基本流:element stream,即类型不变,参数不变的一个多媒体的数据流。
解封装的基本流程为:由 avformat_open_input()
函数用于打开一个文件, av_read_frame()
函数用于读取单个数据 packet ,最后执行 avformat_close_input()
完成清理操作。
3.1 打开一个媒体文件
想要打开一个文件,我们必须知道这个文件的 URL ,然后将它传入 avformat_open_input()
函数中,代码如下所示:
const char *url = "file:in.mp3";
AVFormatContext *s = NULL;
int ret = avformat_open_input(&s, url, NULL, NULL);
if (ret < 0)
abort();
上述代码意图分配一个新的 AVFormatContext ,并打开 url 指定的文件(自动检测格式)读取其 header,然后将该文件的信息加载到新分配的 AVFormatContext 中,最后将其保存到参数 s 中。
有些 format 没有 header 或者在 header 中没有存储足够的信息,因此应该在avformat_open_input()
之后,立刻调用 avformat_find_stream_info() 函数,这个函数会尝试去读取并解码少许的帧以便获取丢失的信息。
在某些情况下,你可能希望自己来分配 AVFormatContext (使用avformat_alloc_context() 函数来分配), 并在将其传递给 avformat_open_input() 函数之前做一些设置。
其中一种使用场景是希望使用自定义 I/O 来读取输入数据,而不使用 lavf 内部 I/O 的默认实现。为此,我们需要将自己的 I/O 函数作为回调传递给 avio_alloc_context() 创建自己的AVIOContext,然后设置 AVFormatContext.pb 字段为这个新创建的 AVIOContext 。ffmpeg的官方示例 avio_reading.c 展示了这个场景。
因为要等到 avformat_open_input() 函数返回以后,我们才会知道打开文件的具体 format, 因此在 demuxing 时,我们不可能预先设置 AVFormatContext 中的私有 option。相反的,我们应该通过 AVDictionary 包裹私有 option,并将其作为参数传入 avformat_open_input() 。
代码如下:
AVDictionary *options = NULL;
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "pixel_format", "rgb24", 0);
if (avformat_open_input(&s, url, NULL, &options) < 0)
abort();
av_dict_free(&options);
上述代码将私有选项 “video_size” 和 “pixel_format” 成功传递给了 demuxer 。这种用法在某些情况是必要的,例如 rawvideo demuxer,因为它不知道如何解释原始视频数据。如果输入文件的 format不是 raw video ,这些 options 将不会被 demuxer 识别,也就不会被采用。这些不被识别的 option 仍会在变量中保存,而被识别的 option 将会消失。
调用程序可以按照自己的意愿任意处理诸如此类的无法识别的选项:
AVDictionaryEntry *e;
if (e = av_dict_get(options, "", NULL, AV_DICT_IGNORE_SUFFIX)) {
fprintf(stderr, "Option %s not recognized by the demuxer.\n", e->key);
abort();
}
当我们完成对文件操作之后,我们必须使用 avformat_close_input() 来关闭它。这个函数将会释放和这个文件相关联的任何东西。
3.2、读取打开后的文件
打开 AVFormatContext
之后,下一步就是从它读取数据,我们可以通过重复调 av_read_frame()
函数来完成读取。每一次的成功调用,都会返回一个AVPacket,其中包含了某一个 AVStream 的已编码数据,具体是那一个 stream ,我们可以查看 AVPacket.stream_index 来获取。如果我们希望解码这个 packet 中的数据,可以将这个 packet 直接传递给 libavcodec 库中的解码函数 avcodec_send_packet()
或 avcodec_decode_subtitle2()
去解码数据。
如果能成功获取数据,AVPacket.pts,AVPacket.dts 和AVPacket.duration 这三个时间信息将会自动设置好。如果 stream 中没有提供相关信息,它们也可能是 unset (即pts/dts 设置为 AV_NOPTS_VALUE ,duration 设置为 0 )。这三个时间信息都是以 AVStream.time_base 为单位的,也就是说, time_base 是一个以秒为单位的时间长度值(例如可以是 2s,2.5s等),而上述三个值都以time_base为单位,也就是time_base的整数倍。
如果返回的 AVPacket 中 ,其字段 buf 被设置了,那么说明该 packet 的缓冲区是动态分配的,用户可以无限期保存它。相反,如果 AVPacket.buf 为空,说明 packet 中的相关数据是存储在 demuxer 中的某个静态内存中的,该 packet 的有效期限直到下一个 av_read_frame()
被调用或关闭文件为止。我们如果需要保存 packet ,那么可以调用 av_dup_packet()
,它将生成一个 av_malloc()ed 拷贝副本。
无论是上述那种情况下,当 packet 不再使用时,都必须调用 av_packet_unref() 释放包。
4、封装
Muxers (封装器)以 AVPacket 的形式获取编码数据并将其写入到指定容器格式的文件或输出字节流中。
muxing过程中最重要的API函数有:
-
avformat_write_header()
用于写入文件header; -
av_write_frame()
/av_interleaved_write_frame()
用于写入packets; -
av_write_trailer()
用于结束文件输出。
muxing 流程的第一步:调用者必须调用 avformat_alloc_context()
来创建一个 muxing context。然后去设置 context 的各个字段,以便对输出做相关设置:
-
AVFormatContext.oformat 必须被设置,它指定我们要使用的封装格式。
-
除非指定的 format 是 AVFMT_NOFILE 类型,否则AVFormatContext.pb也必须设置,它是一个打开的 I/O context,可以由 avio_open2() 打开或者由用户自定义。
-
除非指定的 format 是 AVFMT_NOSTREAMS 类型,否则至少要设置一个 stream。stream 由函数
avformat_new_stream()
创建。调用者应该填充 AVStream.codecpar(stream codec parameters)字段信息,用于指定 codec 相关的信息:AVCodecParameters.codec_type 指定编解码器类别,AVCodecParameters.codec_id 指定编解码器类型,以及其他一些信息,如 宽/高,像素格式,采样格式等。 尤其是 AVStream.time_base,应设置为调用者想要为这个流使用的时基。(注意,muxer实际使用的时基可能和设置的有所不同,这将会在后续进行描述)。 -
在 muxing 过程中,建议用户只通过手动设置的方式来初始化AVCodecParameters中的相关字段,而不是使用
avcodec_parameters_copy()
函数从别处进行拷贝。这是因为即使同一格式的 format context ,其在input 和 output 时具体采用的详细字段是不同的。 -
调用者也可以设置其他额外的信息,如设置全局的元数据:AVFormatContext.metadata,或者为单个流设置元数据:AVStream.metadata,以及AVFormatContext.chapters,AVFormatContext.programs 等字段。这些字段的详细信息在 AVFormatContext 文档中均有详细描述。 注意:这些信息是否真的会存储在输出中,这还要取决于容器的格式和muxer是否支持。
当用于 muxing 的 context 完全设置好之后,调用者必须调用 avformat_write_header()
来初始化 muxer 的内部并将 header 写入文件中。这一步中具体有那些数据可以被写入 IO context,完全取决于muxer,但是这个函数必须被调用。如果想要传递 muxer 的私有选项,那么必须将其通过 options 参数传递给这个函数。
然后通过反复调用av_write_frame()
或 av_interleaved_write_frame()
函数将数据发送给muxer(参考这两个函数的文档,以便讨论两者的区别)。但请注意:一个 muxing context 只能配合使用其中一个函数,而不能混合使用。
注意发送给 muxer 的packet中的时间信息必须是对应AVStream.time_base这个时间基的。该时间基由muxer 设置(在 avformat_write_header()步骤)。
等到将全部数据都写入之后,调用者必须调用 av_write_trailer()
函数将所有缓存的 packet 和 结尾数据 flush 到输出文件中,然后就可以关闭 I/O context,以及释放 muxing context,这通过函数avformat_free_context(
) 完成。
5、使用option
在上面 “解封装” 一节中,我们在打开一个文件时,使用了 option 。在 FFmpeg 中,可以对 muxer,codec,device 设置相关的 option,以便对相关细节做详细的控制,这是非常重要的。其中,option 可以分为通用的和私有的两种。
在这里,我们讲述如何使用 avoptions 机制来配置 muxers 和 demuxers 中的 options 。
通用(可用于所有 format)的 libavformat options 由 AVFormatContext 结构体提供,用户程序可以通过一定的方法检测这些通用options,方法是对 AVFormatContext 或者是对其(调用 avformat_get_class()) 关联的 AVClass 调用 av_opt_next() / av_opt_find() 函数 。
私有(format特有的)的 options 由 AVFormatContext.priv_data 提供,而且当且仅当对应 format 相关的AVInputFormat.priv_class 或 AVOutputFormat.priv_class 非空时有效。
更多的 options 可能由 AVFormatContext.pb 提供,这个字段表示一个“I/O上下文”,如果它关联的 AVClass 是非空的,且当前使用的是协议层,那么就会有更多的 options 存在。
option 在 libavutil库 中有详细介绍。
6、I/O 子模块
I/O 子模块,简称为 lavf_io。负责对 input 和 output 的读写操作。
AVIOContext 是 I/O 模块中最重要的类型,libavformat 通过它来完成具体的读写操作。它被设置到 AVFormatContext.pb 字段,在解封装时用于读取输入,在封装时用于向输出写入数据。
在 demux 时,一般情况下,我们不需要具体关注 I/O 的事情,在 avformat_open_input() 调用中,该函数会自动根据输入的 url 分配一个 AVIOContext,然后将其设置给 pb 字段。当然,如果我们想要自定义 I/O,我们也可以手动设置它,此时,我们也必须手动去关闭它。
在 mux 时,则必须手动设置 pb 字段,而且必须在调用 avformat_write_header() 之前设置它,此时用户必须负责在使用完它之后手动去关闭它。
我们不必太过关心 AVIOContext 中的具体字段,它的使用是通过 AVFormatContext 负责的,我们仅需要提供一个分配并设置良好的 io context 示例即可。
比较重要的部分是如下两个AVIOContext 相关的函数。avio_alloc_context() 分配一个良好定义的AVIOContext ,avio_context_free() 则负责释放已分配的AVIOContext。
官方示例 avio_reading.c 展示了 AVIOContext 的用法。
AVIOContext *avio_alloc_context(
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence));
/*
分配并初始化 AVIOContext。分配成功后必须使用 avio_context_free() 函数来释放它。
buffer参数:通过 AVIOContext 执行输入/输出操作的内存块。它必须是通过函数av_malloc()或者类似函数分配而来。
libavformat可能会释放这个内存块然后用一个新的内存块代替它。AVIOContext.buffer保存着当前使用的内存,它在不使用之后也必须通过av_free()释放。
buffer_size参数:表示 buffer 的大小,这个参数取值的大小对性能非常重要。对于有固定块大小的协议来说,应该设为这个固定块大小。
对于其他情况来说,我们一般使用一个典型的值,即缓冲页的大小,也就是 4k。
write_flag参数:如果buffer可被写入,则置为1,否则置为0。
opaque参数:一个不透明指针,用于指向用户自定义数据。这个指针在下面三个read,write,seek回调中,会被传递进去。
read_packet参数:一个回调函数,用于重新填充 buffer,可以设置NULL。对于流协议来说,这个函数宁可返回一个适当的 AVERROR 错误码,也绝对不要返回0。
write_packet参数:一个回调函数,用于向buffer写入内容,可为NULL。这个函数不应该改变输入缓冲的内容。
seek参数:一个回调函数,用于seek到指定字节数的位置,可为NULL。
返回值:成功分配后的 AVIOContext,如果失败返回NULL。
*/
void avio_context_free(AVIOContext **s);
/*
释放指定的 IO Context,以及与之关联的一切内容。
参数s:指定 IO Context 的二级指针,调用完成后,这个函数会将s置为NULL。
*/
7、directory listing API
directory:目录 dictionary:字典
directory listing API 可以让我们将远程服务器上的文件一一列出。
在以下情况就需要这个功能:
-
在GUI中,弹出一个“打开文件”对话框,从而选择一个远程位置的文件
-
从一个给定的目录递归查找媒体文件,并提供播放所有找到的媒体文件的功能
我们介绍一下相关的类型:AVIODirContext 表示一个 dir 对象,它包含多个条目,条目使用 AVIODirEntry 类型表示。条目表示一个相关的实体,可以是文件、另一个目录、其他任何东西等,其类型由结构体AVIODirEntryType 描述。它还有其他诸多的字段来描述该实体的情况。比如,实体的 name,type,上次修改,访问,文件状态变化的 timestamp,文件大小等;如果代码是在 Linux 上,还有文件拥有者的 User ID 和 Group ID,以及该文件的 fileMode 等。
首先,需要通过调用 avio_open_dir() 来打开目录,获取到该目录相关的 AVIODirContext,目录由一个URL指定,第三个参数为 AVDictionary,它可以包含协议特有的私有option。函数调用成功返回 0 或 正整数 ,成功时会自动分配 AVIODirContext。
AVIODirContext *ctx = NULL;
if (avio_open_dir(&ctx, "smb://example.com/some_dir", NULL) < 0) {
fprintf(stderr, "Cannot open directory.\n");
abort();
}
上述代码试图打开一个 smb 协议指定的目录,而且没有指定任何额外参数。
目录中包含的条目(entry),由结构体AVIODirEntry表示。条目可以是文件、另一个目录、其他任何东西等,由结构体 AVIODirEntryType 描述。我们可以通过重复调用 avio_read_dir() 函数从打开的 AVIODirContext 中连续读取条目。调用如果成功,返回0或正整数。当我们读到 NULL entry 之后就可以停止重复调用,这意味着没有剩余的条目。
请注意:从 avio_read_dir() 返回的 entry,必须使用 avio_free_directory_entry() 函数释放。
请注意:AVIODirContext 在使用完后,也需要被 free 掉,我们使用函数 avio_close_dir() 来完成它。
下面的代码将从一个打开的 directory context中读取所有条目并打印他们的名字到标准输出。
AVIODirEntry *entry = NULL;
for (;;) {
if (avio_read_dir(ctx, &entry) < 0) {
fprintf(stderr, "Cannot list directory.\n");
abort();
}
if (!entry)
break;
printf("%s\n", entry->name);
avio_free_directory_entry(&entry);
}
avio_close_dir(&ctx);
除了上述的查看操作外,我们还可以对 entry 进行修改和删除:
-
avpriv_io_delete():删除指定的资源
-
avpriv_io_move():移动或者重命名一个资源
官方示例 avio_dir_cmd.c 展示了如何使用 directory listing API。
8、设置I/O中断机制
在 demux 时,我们首先需要调用 avformat_open_input() 打开一个输入,然后循环调用 av_read_frame() 函数来读取输入。
我们要注意的是: avformat_open_input() 和 av_read_frame() 都是阻塞函数,如果不能读取到足够的数据,那么它们将会一直阻塞。对于读取本地文件而言,这不是什么问题,但当我们读取的是网络上的实时流时,尤其是网络的情况不太好的情况下,就可能导致这个长时间的阻塞。
而在实际的代码中,我们可能不能让它们一直阻塞在这里,我们需要超过一定时长时返回,去处理其他一些事情,然后再尝试去重新调用这两个函数,去读取数据,这时候,我们就需要设置中断机制。
第一种方法是,使用 option 来设置中断,不同的协议需要使用的 option 也不太一样,比如 rtsp ,它使用私有选项 stimeout 设置中断,tcp 使用私有选项 timeout 设置中断等等。使用时,需要去查询相关协议的具体说明。
//设置rtsp超时
AVDictionary* opts = NULL;
//设置tcp or udp,默认一般优先tcp再尝试udp
av_dict_set(&opts, "rtsp_transport", "tcp" 或"udp", 0);
av_dict_set(&opts, "stimeout", "1000000", 0);//设置超时1秒
int ret = avformat_open_input(&ctx, url, NULL, &opts);
//设置udp,http超时
AVDictionary* opts = NULL;
av_dict_set(&opts, "timeout", "1000000", 0);//设置超时1秒
int ret = avformat_open_input(&ctx, url, NULL, &opts);
第二种方法是:设置一个回调函数,该回调的返回值会用于判断是否做中断操作。这种方式非常的灵活,允许我们自定义一个函数,自己来控制是否中断 I/O 操作。该函数返回 1 就表示中断,返回 0 则表示不作中断。
具体的操作是:1.提前分配 AVFormatContext ;2. 设置 format context 的 interrupt_callback 字段;3. 调用 avformat_open_input() 打开输入。
其中 interrupt_callback 的类型为:
typedef struct AVIOInterruptCB {
int (*callback)(void*); //判断是否中断的 callback
void *opaque; //传递给 callback 的自定义数据
} AVIOInterruptCB;
示例如下:
AVFormatContext* ctx = avformat_alloc_context();
ctx->interrupt_callback.callback = AVInterruptCallBackFun;//超时回调
ctx->interrupt_callback.opaque = this;
DWORD dwStart = GetTickCount();
avformat_open_input(&ctx, "rtmp://192.168.0.25/live/stream", 0, nullptr);
//超时回调函数
static int AVInterruptCallBackFun(void* ctx)
{
int nTimeOut = GetTickCount() - dwStart;
if(nTimeOut > 10000)//超过10秒则退出
return 1;//退出流读取回调
return 0;
}
9、元数据
我们知道,一个媒体文件中常常会保存一定的元数据,比如专辑名称,艺人信息等等。元数据属于 5 种媒体数据之一,因此它可以从一个打开的 format context 中获取。
在 AVFormatContext,AVStream,AVChapter 和 AVProgram 结构体中都有相关的元数据字段,这些字段的类型是 AVDictionary 类型,也就是说元数据是多个字符串键值对。就像 FFmpeg 中的所有字符串一样,元数据假设为 UTF-8 编码的。但请注意,一般来说,FFmpeg 并不会去检查其是否为有效的 UTF-8 编码。
对元数据的访问和修改非常简单,无非是访问相关的字段,然后以 AVDictionary 的 API 来操作即可。
如访问一个mp4文件中的元数据,仅需打开文件,获取相关的AVFormatContext,然后访问其 metadata 字段即可。
另外要注意的是:不同封装格式支持的元数据各不相同。有的封装格式仅仅支持有限的元数据字段,而有些封装格式则并不限制。
10、API详解
AVFormatContext *avformat_alloc_context(void);
/*
用于分配一个 AVFormatContext 对象,注意,这里仅仅是分配了 format context对象,尚未打开。
avformat_free_context() 函数可用于释放 AVFormatContext 对象以及与之相关的一切内容。
*/
void avformat_free_context(AVFormatContext *s);
/*
释放一个 AVFormatContext 以及其全部的 streams。
*/
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
/*
打开一个输入流然后读取其 header 部分。此时 codec 尚未打开。这个流必须使用 avformat_close_input()来关闭。
ps参数:指向由用户提供的 AVFormatContext(由 avformat_alloc_context()返回),可为NULL,此时函数会自动生成一个 AVFormatContext,然后将其地址写入 ps。
注意:在该函数失败时,用户提供的 AVFormatContext 会被释放掉。
url参数:要打开流的url
fmt参数:如果非NULL,该参数会强制指定输入format,否则该 format 会以自动检测到的为主。
options参数:用于设置 AVFormatContext option(即共有options) 和 特定解码器私有options 的 dictionary。函数返回时,这个参数会被销毁,然后被一个新的 dictionary 所取代,这个新的 dictionary 中包含了 options 有的但未被发现和使用的 option 。这个参数可以为NULL。
返回值:返回 0 表示成功,一个负的 AVERROR 表示失败。
注意:如果你想要的使用自定义 IO,那么需要提前自己分配 format context,然后设置它的 pb 字段。
*/
void avformat_close_input(AVFormatContext **s);
/*
关闭已打开的 format context,释放其所有内容,并将 *s 置为 NULL。
*/
int av_find_best_stream(AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
AVCodec **decoder_ret,
int flags);
/*
查找到文件中的“最好的” stream。“最好的”流是根据各种方法确定的,很有可能是用户所期望的流。
如果 decoder 参数是非空的,av_find_best_stream 将会返回流相关的默认decoder;如果找不到相关的 decoder,则该参数被忽略。
参数ic:媒体文件句柄
参数type:指定流的类型,如 video,audio,subtitles等
参数wanted_stream_nb:用户指定流的标识数字,如果为 -1 则自动选择流
参数 related_stream:尝试发现一个与之相关的流(在同一个程序中),如为 -1 则不做任何事
参数decoder_ret:如果为非NULL,返回选择的流相关的decoder
参数 flags :当前尚未定义
返回值:成功时返回非负的流标识数字。如果没有指定的流类型,则返回AVERROR_STREAM_NOT_FOUND,如果找到了流,但没有相关的 decoder,则返回 AVERROR_DECODER_NOT_FOUND.
注意:如果 av_find_best_stream 返回成功,且 decoder_ret 不为 NULL,那么 *decoder_ret 可以保证是一个有效的 AVCodec。
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
/*
读取媒体文件的 packets 并获取其中流的信息。这对于没有 header 的文件格式,如 MPEG 等很有用。如果是 MPEG-2重复帧模式,这个函数也会计算出实际的帧率。这个函数并不会改变文件当前的逻辑位置。这个函数检测过的 packets 会被缓存起来以便后续处理。
参数 ic:媒体文件的句柄,一个打开的 format context
参数 options:如果非NULL,那么它必须是指向 AVDictionary 的一个数组,且数组的长度必须与 ic.nb_streams相同,
而且数组中第 i 个 AVDictionary 就是用于保存第 i 个流的 options 。
在函数返回时,每个 dictionary 会被其中未发现的 options 填充
返回值:成功返回 0 ,失败返回 AVERROR_XXX
注意:这个函数并不保证可以打开所有的 codec,所以 options 在返回时如果是非空的,是一个很正常的行为。
*/
void av_dump_format(AVFormatContext *ic,
int index,
const char *url,
int is_output);
/*
打印 input or output format 的详细信息。如 时长,比特率,流信息,容器,程序,元数据,side data,编解码器以及时基。
ic参数:要被分析的 format context
index参数:要打印信息的stream的index
url参数:要打印的URL,如源文件或者目标文件
is_output:指定ic是input还是output,input为0,output为1
*/
更多推荐
所有评论(0)