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

目录

前言

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

正文

一、获取宽高信息

1、核心代码

c 复制代码
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;
}

获取到的打印信息

c 复制代码
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头文件中,它的定义如下:

c 复制代码
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,可以实现音视频文件的解封装、封装、转码等功能。

执行完avformat_alloc_context,主要的一些东西。

3、avformat_alloc_context

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

c 复制代码
/**
 * 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)。它的函数原型如下:

c 复制代码
/**
 * 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函数用于打开音视频输入文件,并初始化相关的输入上下文。它是进行音视频解封装操作的起点之一。

源码如下:

c 复制代码
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数组中的每个元素中。

函数原型如下:

c 复制代码
/**
 * 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库中的一个函数,用于打印音视频文件的详细格式信息。它可以用于调试和分析音视频文件,提供有关文件的元数据、流信息、编码参数等方面的详细信息。

函数原型:

c 复制代码
/**
 * 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库中的一个函数,用于查找最佳的音视频流。它可以根据指定的流类型(音频、视频、字幕等)和其他条件,选择最合适的流进行后续操作,如解码、编码等。

函数原型如下:

c 复制代码
/**
 * 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调试环境搭建

有教在不同的环境中调试的方法,但依我的拙见,我觉得学习一样的东西,一定不断的给自己正反馈, 否则,学习很难进行下去。

2、avformat_open_input 返回值为-22

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

参考

1、FFmpeg调试环境搭建

相关推荐
西岸行者7 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意7 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码7 天前
嵌入式学习路线
学习
毛小茛7 天前
计算机系统概论——校验码
学习
babe小鑫7 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms7 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下7 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。7 天前
2026.2.25监控学习
学习
im_AMBER7 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J7 天前
从“Hello World“ 开始 C++
c语言·c++·学习