FFmpeg零基础学习(二)——视频文件信息获取

目录

  • 前言
  • 正文
    • 一、获取宽高信息
      • 1、核心代码
      • 2、AVFormatContext
      • 3、avformat_alloc_context
      • 4、avformat_open_input
      • 5、avformat_find_stream_info
      • 6、av_dump_format
      • 7、av_find_best_stream
    • End、遇到的问题
      • 1、Qt Debug模式avformat_alloc_context 无法分配对象,而Release模式可以
      • 2、avformat_open_input 返回值为-22
  • 参考

前言

本篇文章的目的在于输出导入的视频文件的宽高信息,或者其他信息。

正文

一、获取宽高信息

1、核心代码

int CFFmpegDemoTest::_GetVideoWidthAndHeight(const QString &_sVideoPath, int &_iWidth, int &_iHeight)
{
    AVFormatContext *fmt_ctx = avformat_alloc_context();//创建对象并初始化

    if (fmt_ctx == nullptr)
    {
        return -1;
    }
    int ret = 0;

    AVStream *videoStream = nullptr;
    const char* cFilePath = _sVideoPath.toStdString().c_str();
    char errbuf[AV_ERROR_MAX_STRING_SIZE];

    do {
        //打开文件

        if ((ret = avformat_open_input(&fmt_ctx, cFilePath, NULL, NULL)) < 0)
        {
            qDebug() << "--> Cannot open Video File";
            av_strerror(ret, errbuf, sizeof(errbuf));
            qDebug() << "错误信息:" << errbuf<<";ret:"<<ret;
            break;
        }

        if ((ret = avformat_find_stream_info (fmt_ctx, NULL)) < 0)
        {
            qDebug() << "--> Cannot Find Stream Information";
            av_strerror(ret, errbuf, sizeof(errbuf));
            qDebug() << "错误信息:" << errbuf;
            break;
        }

        av_dump_format(fmt_ctx, 0, cFilePath, 0);//输出视频信息
        int iVideoStreamIndex = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);

        if (iVideoStreamIndex >= 0)
        {
            videoStream = fmt_ctx->streams[iVideoStreamIndex];
            _iWidth = videoStream->codecpar->width;
            _iHeight = videoStream->codecpar->height;
            qDebug() << "--> CFFmpegDemoTest::_GetVideoWidthAndHeight _iWidth:"<<_iWidth<<";_iHeight:"<<_iHeight;
        }
    } while(0);


    avformat_close_input(&fmt_ctx);
    return ret;
}

获取到的打印信息

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'D:/FFmpeg/FFmpegPlayer/product/video/phone1.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2023-11-22T06:06:55.000000Z
    location        : +24.6182+118.0385/
    location-eng    : +24.6182+118.0385/
    com.android.version: 13
  Duration: 00:01:41.40, start: 0.000000, bitrate: 17998 kb/s
  Stream #0:0[0x1](eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1080x1920, 17845 kb/s, 29.67 fps, 60 tbr, 90k tbn (default)
    Metadata:
      creation_time   : 2023-11-22T06:06:55.000000Z
      handler_name    : VideoHandle
      vendor_id       : [0][0][0][0]
    Side data:
      displaymatrix: rotation of 90.00 degrees
  Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      creation_time   : 2023-11-22T06:06:55.000000Z
      handler_name    : SoundHandle
      vendor_id       : [0][0][0][0]

2、AVFormatContext

AVFormatContext是FFmpeg中的一个重要数据结构,用于表示音视频封装格式的上下文。它包含了音视频封装格式相关的信息,比如输入/输出文件、流信息、封装格式参数等。

AVFormatContext结构体定义在libavformat/avformat.h头文件中,它的定义如下:

typedef struct AVFormatContext {
    const AVClass *av_class;
    AVInputFormat *iformat;
    AVOutputFormat *oformat;
    void *priv_data;
    ...
} AVFormatContext;

下面是对AVFormatContext结构体中常用字段的详细解释:

av_class:指向一个AVClass结构体的指针,用于获取封装格式的类信息,比如名称、类型等。

iformat:指向一个AVInputFormat结构体的指针,表示输入的封装格式。在打开输入文件时,FFmpeg会根据输入文件的扩展名或者其他信息自动选择合适的AVInputFormat。

oformat:指向一个AVOutputFormat结构体的指针,表示输出的封装格式。在写入输出文件时,FFmpeg会根据输出文件的扩展名或者其他信息自动选择合适的AVOutputFormat。

priv_data:指向一个私有数据结构的指针,用于存储封装格式相关的私有数据。具体的结构体类型和内容取决于使用的封装格式。

除了上述字段之外,AVFormatContext还包含了一些其他的字段,用于存储音视频封装格式的相关信息,比如:

nb_streams:表示流的数量,即输入/输出文件中的音视频流的数量。

streams:一个指针数组,用于存储每个音视频流的信息。每个流都是一个AVStream结构体,包含了流的索引、时长、编解码器参数等信息。

duration:表示音视频文件的总时长。

bit_rate:表示音视频文件的比特率。

metadata:一个字典结构,用于存储音视频文件的元数据,比如标题、作者、描述等。

filename:指向输入/输出文件名的指针。

pb:指向一个AVIOContext结构体的指针,用于输入/输出文件的读写操作。

AVFormatContext结构体是FFmpeg中非常重要的一个数据结构,它提供了对音视频封装格式的访问和操作接口。通过操作AVFormatContext,可以实现音视频文件的解封装、封装、转码等功能。
FFmpeg零基础学习(二)——视频文件信息获取_第1张图片
执行完avformat_alloc_context,主要的一些东西。

3、avformat_alloc_context

avformat_alloc_context是FFmpeg库中的一个函数,用于动态分配并初始化一个AVFormatContext结构体。它的函数原型如下:

/**
 * Allocate an AVFormatContext.
 * avformat_free_context() can be used to free the context and everything
 * allocated by the framework within it.
 */
AVFormatContext *avformat_alloc_context(void);

该函数会分配一块内存,并将其初始化为一个空的AVFormatContext结构体,然后返回指向该结构体的指针。

使用avformat_alloc_context函数可以创建一个空的AVFormatContext对象,然后可以通过设置不同的字段和参数来配置它,以便进行音视频封装或解封装操作。
总结来说,avformat_alloc_context函数用于动态分配和初始化一个空的AVFormatContext对象,为后续的音视频封装和解封装操作做准备。
所以,分配后,可以对AVFormatContext 对象进行判空,防止初始化失败。

4、avformat_open_input

avformat_open_input是FFmpeg库中的一个函数,用于打开音视频输入文件并初始化相关的输入上下文(AVFormatContext)。它的函数原型如下:

/**
 * Open an input stream and read the header. The codecs are not opened.
 * The stream must be closed with avformat_close_input().
 *
 * @param ps       Pointer to user-supplied AVFormatContext (allocated by
 *                 avformat_alloc_context). May be a pointer to NULL, in
 *                 which case an AVFormatContext is allocated by this
 *                 function and written into ps.
 *                 Note that a user-supplied AVFormatContext will be freed
 *                 on failure.
 *> 传入值为avformat_alloc_context 分配的对象
 * @param url      URL of the stream to open.
 *> 要打开的流的地址
 * @param fmt      If non-NULL, this parameter forces a specific input format.
 *                 Otherwise the format is autodetected.
 * >输入的文件的格式,若为NULL,则自动检测
 * @param options  A dictionary filled with AVFormatContext and demuxer-private
 *                 options.
 *                 On return this parameter will be destroyed and replaced with
 *                 a dict containing options that were not found. May be NULL.
 *
 * @return 0 on success, a negative AVERROR on failure.
 *> 返回值0,为正确,其他值为失败
 * @note If you want to use custom IO, preallocate the format context and set its pb field.
 */
int avformat_open_input(AVFormatContext **ps, const char *url,
                        const AVInputFormat *fmt, AVDictionary **options);

该函数的参数说明如下:
ps:指向指针的指针,用于存储分配的AVFormatContext对象。
url:输入文件的URL或文件名。
fmt:指定输入格式,如果为NULL,则由FFmpeg自动检测输入文件的格式。
options:指向包含附加选项的字典。可以在打开输入文件时提供一些特定的选项,比如设置超时时间、设置输入缓冲大小等。

1、强调关闭使用avformat_close_input
2、函数返回一个整数值,表示操作的结果。如果返回值小于0,则表示打开输入文件失败,否则返回0表示操作成功。使用avformat_open_input函数可以打开一个音视频输入文件,并将其与一个AVFormatContext对象关联起来,以便后续的音视频解封装操作。
3、avformat_open_input函数用于打开音视频输入文件,并初始化相关的输入上下文。它是进行音视频解封装操作的起点之一。

源码如下:

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    int i, ret = 0;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;

    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);
    if (!s->av_class) {
        av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
        return AVERROR(EINVAL);
    }
    if (fmt)
        s->iformat = fmt;

    if (options)
        av_dict_copy(&tmp, *options, 0);

    if (s->pb) // must be before any goto fail
        s->flags |= AVFMT_FLAG_CUSTOM_IO;

    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;

    if (!(s->url = av_strdup(filename ? filename : ""))) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    s->probe_score = ret;

    if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
        s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
        if (!s->protocol_whitelist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

    if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
        s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
        if (!s->protocol_blacklist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

    if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
        av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);
        ret = AVERROR(EINVAL);
        goto fail;
    }

    avio_skip(s->pb, s->skip_initial_bytes);

    /* Check filename in case an image number is expected. */
    if (s->iformat->flags & AVFMT_NEEDNUMBER) {
        if (!av_filename_number_test(filename)) {
            ret = AVERROR(EINVAL);
            goto fail;
        }
    }

    s->duration = s->start_time = AV_NOPTS_VALUE;

    /* Allocate private data. */
    if (s->iformat->priv_data_size > 0) {
        if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (s->iformat->priv_class) {
            *(const AVClass **) s->priv_data = s->iformat->priv_class;
            av_opt_set_defaults(s->priv_data);
            if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                goto fail;
        }
    }

    /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
    if (s->pb)
        ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

#if FF_API_DEMUXER_OPEN
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
#else
    if (s->iformat->read_header)
#endif
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;

    if (!s->metadata) {
        s->metadata = s->internal->id3v2_meta;
        s->internal->id3v2_meta = NULL;
    } else if (s->internal->id3v2_meta) {
        av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");
        av_dict_free(&s->internal->id3v2_meta);
    }

    if (id3v2_extra_meta) {
        if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
            !strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {
            if ((ret = ff_id3v2_parse_apic(s, id3v2_extra_meta)) < 0)
                goto close;
            if ((ret = ff_id3v2_parse_chapters(s, id3v2_extra_meta)) < 0)
                goto close;
            if ((ret = ff_id3v2_parse_priv(s, id3v2_extra_meta)) < 0)
                goto close;
        } else
            av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
    }
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);

    if ((ret = avformat_queue_attached_pictures(s)) < 0)
        goto close;

#if FF_API_DEMUXER_OPEN
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
#else
    if (s->pb && !s->internal->data_offset)
#endif
        s->internal->data_offset = avio_tell(s->pb);

    s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    update_stream_avctx(s);

    for (i = 0; i < s->nb_streams; i++)
        s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;

close:
    if (s->iformat->read_close)
        s->iformat->read_close(s);
fail:
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
    av_dict_free(&tmp);
    if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
        avio_closep(&s->pb);
    avformat_free_context(s);
    *ps = NULL;
    return ret;
}

分析可以查看雷神的这篇文章:
FFmpeg源代码简单分析:avformat_open_input()
可以看到,若打开文件失败,或是分配资源失败等等,都会将传入的AVFormatContext的对象置为NULL.

5、avformat_find_stream_info

avformat_find_stream_info是FFmpeg库中的一个函数,用于获取音视频文件的流信息。它会分析输入文件,并将解码器的相关信息填充到AVFormatContext中的streams数组中的每个元素中。
函数原型如下:

/**
 * Read packets of a media file to get stream information. This
 * is useful for file formats with no headers such as MPEG. This
 * function also computes the real framerate in case of MPEG-2 repeat
 * frame mode.
 * The logical file position is not changed by this function;
 * examined packets may be buffered for later processing.
 *
 * @param ic media file handle
 * @param options  If non-NULL, an ic.nb_streams long array of pointers to
 *                 dictionaries, where i-th member contains options for
 *                 codec corresponding to i-th stream.
 *                 On return each dictionary will be filled with options that were not found.
 * @return >=0 if OK, AVERROR_xxx on error
 *
 * @note this function isn't guaranteed to open all the codecs, so
 *       options being non-empty at return is a perfectly normal behavior.
 *
 * @todo Let the user decide somehow what information is needed so that
 *       we do not waste time getting stuff the user does not need.
 */
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

该函数的参数说明如下:
ic:指向AVFormatContext的指针,表示输入文件的上下文。
options:指向包含附加选项的字典。可以在获取流信息时提供一些特定的选项,比如设置解码器参数、设置过滤器等。

函数返回一个整数值,表示操作的结果。如果返回值小于0,则表示获取流信息失败,否则返回0表示操作成功。

使用avformat_find_stream_info函数可以在打开输入文件后调用,以获取输入文件的流信息,包括音频流、视频流、字幕流等。通过分析文件的数据包,该函数将填充AVFormatContext中的streams数组,每个数组元素对应一个流,并包含有关该流的详细信息,如编解码器类型、时间基准、时长、帧率等。
avformat_find_stream_info函数获取文件的流信息,并将其填充到AVFormatContext对象中的streams数组中。最后,可以通过遍历streams数组,处理每个流的相关信息。
总结来说,avformat_find_stream_info函数用于获取音视频文件的流信息,是进行音视频解封装操作的一步。通过分析输入文件的数据包,它将填充AVFormatContext对象中的streams数组。

6、av_dump_format

av_dump_format是FFmpeg库中的一个函数,用于打印音视频文件的详细格式信息。它可以用于调试和分析音视频文件,提供有关文件的元数据、流信息、编码参数等方面的详细信息。
函数原型:

/**
 * Print detailed information about the input or output format, such as
 * duration, bitrate, streams, container, programs, metadata, side data,
 * codec and time base.
 *
 * @param ic        the context to analyze
 * @param index     index of the stream to dump information about
 * @param url       the URL to print, such as source or destination file
 * @param is_output Select whether the specified context is an input(0) or output(1)
 */
void av_dump_format(AVFormatContext *ic,
                    int index,
                    const char *url,
                    int is_output);

该函数的参数说明如下:
ic:指向AVFormatContext的指针,表示输入/输出文件的上下文。
index:要打印信息的流的索引。如果设置为-1,则会打印所有流的信息。
url:指定输入/输出文件的URL或文件名。
is_output:指示是否为输出文件。如果设为非零值(例如1),则会打印输出文件的格式信息。

av_dump_format函数会打印音视频文件的格式信息到标准输出(stdout)或由FFmpeg库设置的日志回调函数。它会包含文件的元数据(例如文件格式、时长、比特率)、流的信息(例如流索引、编解码器、时长、帧率)以及其他相关参数。
总结来说,av_dump_format函数是一个用于打印音视频文件格式信息的便捷函数。它可用于调试和分析音视频文件,提供有关文件的详细信息。

7、av_find_best_stream

av_find_best_stream是FFmpeg库中的一个函数,用于查找最佳的音视频流。它可以根据指定的流类型(音频、视频、字幕等)和其他条件,选择最合适的流进行后续操作,如解码、编码等。
函数原型如下:

/**
 * Find the "best" stream in the file.
 * The best stream is determined according to various heuristics as the most
 * likely to be what the user expects.
 * If the decoder parameter is non-NULL, av_find_best_stream will find the
 * default decoder for the stream's codec; streams for which no decoder can
 * be found are ignored.
 *
 * @param ic                media file handle
 * @param type              stream type: video, audio, subtitles, etc.
 * @param wanted_stream_nb  user-requested stream number,
 *                          or -1 for automatic selection
 * @param related_stream    try to find a stream related (eg. in the same
 *                          program) to this one, or -1 if none
 * @param decoder_ret       if non-NULL, returns the decoder for the
 *                          selected stream
 * @param flags             flags; none are currently defined
 *
 * @return  the non-negative stream number in case of success,
 *          AVERROR_STREAM_NOT_FOUND if no stream with the requested type
 *          could be found,
 *          AVERROR_DECODER_NOT_FOUND if streams were found but no decoder
 *
 * @note  If av_find_best_stream returns successfully and decoder_ret is not
 *        NULL, then *decoder_ret is guaranteed to be set to a valid AVCodec.
 */
int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        const struct AVCodec **decoder_ret,
                        int flags);

该函数的参数说明如下:
ic:指向AVFormatContext的指针,表示输入文件的上下文。
type:要查找的流类型,使用AVMediaType枚举类型,例如AVMEDIA_TYPE_VIDEO表示视频流,AVMEDIA_TYPE_AUDIO表示音频流,AVMEDIA_TYPE_SUBTITLE表示字幕流等。
wanted_stream_nb:期望的流索引。如果设置为大于等于0的值,则表示要查找的具体流索引;如果设置为负值(例如-1),则表示要查找符合条件的第一个流。
related_stream:关联流索引。在某些情况下,需要根据另一个流来选择最佳流,例如根据视频流选择对应的音频流。如果没有关联流,可以设置为-1。
decoder_ret:指向AVCodec指针的指针,用于返回找到的解码器。
flags:查找流的标志位。可以使用一些特定的标志位,如AV_FIND_STREAM_INFO_IGNORED表示忽略流信息等。

End、遇到的问题

1、Qt Debug模式avformat_alloc_context 无法分配对象,而Release模式可以

这个问题会出现在Qt 5.15 MSVC2019 Debug模式中,后面我切换成MinGW就可以了,就暂不深究了,若想深究的,可以看一下这篇文章。
FFmpeg调试环境搭建
FFmpeg零基础学习(二)——视频文件信息获取_第2张图片
有教在不同的环境中调试的方法,但依我的拙见,我觉得学习一样的东西,一定不断的给自己正反馈, 否则,学习很难进行下去。

2、avformat_open_input 返回值为-22

可能的问题出现在前面avformat_alloc_context 对对象的分配不对。分配的结果可能是空。

参考

1、FFmpeg调试环境搭建

你可能感兴趣的:(FFmpeg,ffmpeg,学习)