购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3
FFmpeg查看音视频信息

本节介绍FFmpeg如何查看文件中的音视频信息,首先描述怎样打开音视频文件和怎样关闭音视频文件,然后叙述如何查看文件中的音视频基本信息,最后阐述如何查看音视频编解码器的参数信息。

2.3.1 打开与关闭音视频文件

使用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才是视频格式。

2.3.2 查看音视频的信息

若想在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个音频帧。

2.3.3 查看编解码器的参数

对于音视频的每种编码标准,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。 UvUPFC1ht0G5eOsjospz4eV7p6mRQvv0utD6WZyRRsnGpr2C5Kgb9tesJT6c4Tkd

点击中间区域
呼出菜单
上一章
目录
下一章
×