目录
- 前言
- 正文
- 一、获取宽高信息
- 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 : mp42minor_version : 0compatible_brands: isommp42creation_time : 2023-11-22T06:06:55.000000Zlocation : +24.6182+118.0385/location-eng : +24.6182+118.0385/com.android.version: 13Duration: 00:01:41.40, start: 0.000000, bitrate: 17998 kb/sStream #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.000000Zhandler_name : VideoHandlevendor_id : [0][0][0][0]Side data:displaymatrix: rotation of 90.00 degreesStream #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.000000Zhandler_name : SoundHandlevendor_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,可以实现音视频文件的解封装、封装、转码等功能。
执行完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 fails->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_WARNINGSav_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endifif ((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_OPENif (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
#elseif (s->iformat->read_header)
#endifif ((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;} elseav_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_OPENif (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
#elseif (s->pb && !s->internal->data_offset)
#endifs->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调试环境搭建
有教在不同的环境中调试的方法,但依我的拙见,我觉得学习一样的东西,一定不断的给自己正反馈, 否则,学习很难进行下去。
2、avformat_open_input 返回值为-22
可能的问题出现在前面avformat_alloc_context 对对象的分配不对。分配的结果可能是空。
参考
1、FFmpeg调试环境搭建