目录
- 前言
- 正文
- 一、获取宽高信息
- 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,可以实现音视频文件的解封装、封装、转码等功能。
执行完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调试环境搭建
有教在不同的环境中调试的方法,但依我的拙见,我觉得学习一样的东西,一定不断的给自己正反馈, 否则,学习很难进行下去。
2、avformat_open_input 返回值为-22
可能的问题出现在前面avformat_alloc_context 对对象的分配不对。分配的结果可能是空。
参考
1、FFmpeg调试环境搭建