本节介绍FFmpeg如何查看文件中的音视频信息,首先描述怎样打开音视频文件和怎样关闭音视频文件,然后叙述如何查看文件中的音视频基本信息,最后阐述如何查看音视频编解码器的参数信息。
使用FFmpeg打开音视频文件,首先声明一个AVFormatContext结构的指针变量,接着调用avformat_open_input函数指定文件路径,然后调用avformat_find_stream_info函数查找文件中的数据流。avformat_open_input函数和avformat_find_stream_info函数都拥有返回值,正常情况返回0表示成功,返回值小于0表示失败。
打开音视频文件之后,即可访问AVFormatContext变量中的字段信息,比如iformat字段存放着文件的输入格式信息,对应AVInputFormat结构;又如oformat字段存放着文件的输出格式信息,对应AVOutputFormat结构。无论是AVInputFormat结构还是AVOutputFormat结构,它们都包含name和long_name两个字段,其中name字段描述当前格式的名称,long_name字段描述当前格式的完整名称。
处理完音视频文件,要调用avformat_close_input函数关闭文件并释放AVFormatContext指针。下面是使用FFmpeg函数打开音视频文件的例子(完整代码见chapter02/read.c):
#include <stdio.h> // 之所以增加__cplusplus的宏定义,是为了同时兼容GCC编译器和G++编译器 #ifdef __cplusplus extern "C" { #endif #include <libavformat/avformat.h> #include <libavutil/avutil.h> #ifdef __cplusplus }; #endif int main(int argc, char **argv) { const char *filename = "../fuzhou.mp4"; if (argc > 1) { filename = argv[1]; } AVFormatContext *fmt_ctx = NULL; // 打开音视频文件 int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Can't open file %s.\n", filename); return -1; } av_log(NULL, AV_LOG_INFO, "Success open input_file %s.\n", filename); // 查找音视频文件中的流信息 ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Can't find stream information.\n"); return -1; } av_log(NULL, AV_LOG_INFO, "Success find stream information.\n"); const AVInputFormat* iformat = fmt_ctx->iformat; av_log(NULL, AV_LOG_INFO, "format name is %s.\n", iformat->name); av_log(NULL, AV_LOG_INFO, "format long_name is %s\n", iformat->long_name); avformat_close_input(&fmt_ctx); // 关闭音视频文件 return 0; }
上面的FFmpeg代码同时兼容GCC和G++编译器,使用GCC的编译命令如下:
gcc read.c -o read -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
使用G++编译的命令如下,其实只需把GCC变为G++即可。
g++ read.c -o read -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
以上两个编译命令都会在当前目录生成可执行程序read,然后运行下面的命令,期望打开视频文件fuzhou.mp4。
./read ../fuzhou.mp4
程序运行完毕,看到控制台打印以下日志信息,说明正常打开了该视频文件。
Success open input_file ../fuzhou.mp4. Success find stream information. format name is mov,mp4,m4a,3gp,3g2,mj2. format long_name is QuickTime / MOV
注意,日志显示MP4文件的格式名称包含MOV、MP4、3GP等,表示这几种文件其实就是一家子。另外,这里的M4A应是M4V,因为M4A是音频格式,M4V才是视频格式。
若想在FFmpeg代码中打印音视频文件信息,调用av_dump_format函数即可实现该功能,比如下面的代码片段示范了如何调用av_dump_format函数(完整代码见chapter02/look.c)。
AVFormatContext *fmt_ctx = NULL; // 打开音视频文件 int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Can't open file %s.\n", filename); return -1; } av_log(NULL, AV_LOG_INFO, "Success open input_file %s.\n", filename); // 查找音视频文件中的流信息 ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Can't find stream information.\n"); return -1; } // 格式化输出文件信息 av_dump_format(fmt_ctx, 0, filename, 0);
接着执行下面的编译命令:
gcc look.c -o look -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
编译完毕运行下面的命令,期望查看视频信息:
./look ../2018.mp4
程序运行完毕,看到控制台输出以下日志信息:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '../2018.mp4': Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf57.71.100 Duration: 00:04:13.36, start: 0.000000, bitrate: 1353 kb/s Stream #0:0[0x1](eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1440x810 [SAR 1:1 DAR 16:9], 1218 kb/s, 25 fps, 25 tbr, 12800 t bn (default) Metadata: handler_name : VideoHandler vendor_id : [0][0][0][0] Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default) Metadata: handler_name : SoundHandler vendor_id : [0][0][0][0]
由此可见,该视频文件的持续时间为04:13.36(4分13秒又360毫秒),且视频流采用H.264编码,音频流采用AAC编码。不过这种格式化的日志显得信息繁多,令人看得眼花缭乱,最好能在代码中逐个解析每个信息字段,方便开发者理解各字段的具体含义。具体的解析步骤说明如下。
首先从AVFormatContext结构获取音视频文件的总体信息,该结构的常见字段说明如下。
· duration:音视频文件的持续播放时间,单位为微秒。
· bit_rate:音视频文件的播放速率,也叫作比特率,单位为比特每秒(bit/s)。
· nb_streams:音视频文件包含的数据流个数,其值通常为2,表示包含视频流和音频流。如果是个音频文件,那么nb_streams为1,表示只有音频流。
· max_streams:音视频文件可拥有的数据流最大个数。
· streams:音视频文件内部的数据流数组。当nb_streams为2时,streams[0]表示第一路数据流,streams[1]表示第二路数据流。
其次调用av_find_best_stream函数获取指定类型的数据流索引,索引值就作为streams数组的下标。传入AVMEDIA_TYPE_VIDEO时,av_find_best_stream函数会返回视频流的索引;传入AVMEDIA_TYPE_AUDIO时,av_find_best_stream函数会返回音频流的索引。如果找不到视频流或者音频流,av_find_best_stream函数就会返回一个负数,因此判断返回值是否大于或等于0,便成为是否找到数据流索引的依据。
无论是streams[0]还是streams[1],它们都属于AVStream结构的指针类型,该结构的常见字段说明如下。
· index:当前数据流的索引。
· start_time:当前数据流的开始播放时间戳。
· nb_frames:当前数据流包含的数据帧个数。
· duration:当前数据流的结束播放时间戳。
接下来编写FFmpeg代码,从视频文件中读取并打印音视频信息,主要代码片段示例如下(完整代码见chapter02/look.c):
av_log(NULL, AV_LOG_INFO, "duration=%d\n", fmt_ctx->duration); // 持续时间,单位为微秒 av_log(NULL, AV_LOG_INFO, "bit_rate=%d\n", fmt_ctx->bit_rate); // 比特率,单位为比特每秒 av_log(NULL, AV_LOG_INFO, "nb_streams=%d\n", fmt_ctx->nb_streams); // 数据流的数量 av_log(NULL, AV_LOG_INFO, "max_streams=%d\n", fmt_ctx->max_streams); // 数据流的最大数量 // 找到视频流的索引 int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); av_log(NULL, AV_LOG_INFO, "video_index=%d\n", video_index); if (video_index >= 0) { AVStream *video_stream = fmt_ctx->streams[video_index]; av_log(NULL, AV_LOG_INFO, "video_stream index=%d\n", video_stream->index); av_log(NULL, AV_LOG_INFO, "video_stream start_time=%d\n", video_stream->start_time); av_log(NULL, AV_LOG_INFO, "video_stream nb_frames=%d\n", video_stream->nb_frames); av_log(NULL, AV_LOG_INFO, "video_stream duration=%d\n", video_stream->duration); } // 找到音频流的索引 int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); av_log(NULL, AV_LOG_INFO, "audio_index=%d\n", audio_index); if (audio_index >= 0) { AVStream *audio_stream = fmt_ctx->streams[audio_index]; av_log(NULL, AV_LOG_INFO, "audio_stream index=%d\n", audio_stream->index); av_log(NULL, AV_LOG_INFO, "audio_stream start_time=%d\n", audio_stream->start_time); av_log(NULL, AV_LOG_INFO, "audio_stream nb_frames=%d\n", audio_stream->nb_frames); av_log(NULL, AV_LOG_INFO, "audio_stream duration=%d\n", audio_stream->duration); }
接着执行下面的编译命令:
gcc look.c -o look -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
编译完成后,执行以下命令启动测试程序,期望查看指定文件的音视频信息。
./look ../fuzhou.mp4
程序运行完毕,发现控制台输出如下日志信息:
Success open input_file ../fuzhou.mp4. duration=19120000 bit_rate=1216884 nb_streams=2 max_streams=1000 video_stream index=0 video_stream start_time=0 video_stream nb_frames=477 video_stream duration=244224 audio_stream index=1 audio_stream start_time=0 audio_stream nb_frames=820 audio_stream duration=837900
由日志信息可见,当前视频的持续时间为19秒多,播放速率为1216884比特每秒(bits/s),并且拥有视频流和音频流,其中视频流位于第一路,包含477个视频帧;音频流位于第二路,包含820个音频帧。
对于音视频的每种编码标准,FFmpeg都会赋予它们一个编号,调用avcodec_find_decoder函数可获得指定编号对应的解码器,调用avcodec_find_encoder函数可获得指定编号对应的编码器。无论是解码器还是编码器,它们都采用AVCodec结构,该结构的常用字段说明如下。
id:编解码器的编号。详细的编号定义及其与编码标准的对应关系见表2-5。
表2-5 常见编解码器与编码标准的对应关系
· name:编解码器的名称。
· long_name:编解码器的完整名称。
· type:编解码器的归属类型,同时也是所属数据流的类型。该类型的定义来自AVMediaType枚举,详细的类型定义及其说明见表2-2。
对于音视频的数据流来说,FFmpeg把编解码器的编号保存在codecpar的codec_id字段。调用avcodec_find_decoder函数传入编解码器的编号,即可获得编解码器的结构指针。接下来通过代码演示,看看如何获取音视频文件的详细编解码参数,示例代码如下(完整代码见chapter02/codec.c)。
// 找到视频流的索引 int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (video_index >= 0) { AVStream *video_stream = fmt_ctx->streams[video_index]; enum AVCodecID video_codec_id = video_stream->codecpar->codec_id; // 查找视频解码器 AVCodec *video_codec = (AVCodec*) avcodec_find_decoder(video_codec_id); if (!video_codec) { av_log(NULL, AV_LOG_ERROR, "video_codec not found\n"); return -1; } av_log(NULL, AV_LOG_INFO, "video_codec id=%d\n", video_codec->id); av_log(NULL, AV_LOG_INFO, "video_codec name=%s\n", video_codec->name); av_log(NULL, AV_LOG_INFO, "video_codec long_name=%s\n", video_codec->long_name); av_log(NULL, AV_LOG_INFO, "video_codec type=%d\n", video_codec->type); } // 找到音频流的索引 int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); if (audio_index >= 0) { AVStream *audio_stream = fmt_ctx->streams[audio_index]; enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id; // 查找音频解码器 AVCodec *audio_codec = (AVCodec*) avcodec_find_decoder(audio_codec_id); if (!audio_codec) { av_log(NULL, AV_LOG_ERROR, "audio_codec not found\n"); return -1; } av_log(NULL, AV_LOG_INFO, "audio_codec id=%d\n", audio_codec->id); av_log(NULL, AV_LOG_INFO, "audio_codec name=%s\n", audio_codec->name); av_log(NULL, AV_LOG_INFO, "audio_codec long_name=%s\n", audio_codec->long_name); av_log(NULL, AV_LOG_INFO, "audio_codec type=%d\n", audio_codec->type); }
接着执行下面的编译命令:
gcc codec.c -o codec -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
编译完成后,执行以下命令启动测试程序,期望查看指定文件采用的编解码器信息。
./codec ../fuzhou.mp4
程序运行完毕,发现控制台输出如下日志信息:
Success open input_file ../fuzhou.mp4. video_codec id=27 video_codec name=h264 video_codec long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 video_codec type=0 audio_index=1 audio_codec id=86018 audio_codec name=aac audio_codec long_name=AAC (Advanced Audio Coding) audio_codec type=1
由日志信息可见,FFmpeg成功找到了目标文件的视频解码器和音频解码器,其中视频编码器为H.264,音频编码器为AAC。