【FFmpeg】avformat_open_input函数
- 1.avformat_open_input
- 1.1 初始化输入格式(init_input)
- 1.1.1 文件路径判断格式(av_probe_input_format2)
- 1.1.1.1 格式探测(read_probe)
- 1.1.1.2 扩展匹配检查(av_match_ext)
- 1.1.2 打开文件推测格式(av_probe_input_buffer2)
- 1.2 读取头(read_header)
- 1.3 更新流的上下文(update_stream_avctx)
- 3.小结
示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染
avformat_open_input的函数调用关系如下
1.avformat_open_input
函数的声明位于libavformat\avformat.h中,从声明看,这个函数的主要作用是打开输入的数据源,并且读取头部,此时,没有打开解码器。同时,数据源必须使用avformat_close_input进行关闭
/**
* 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.
* @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.
* @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.
*
* @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);
函数的定义位于libavformat\demux.c中,定义如下,主要的工作流程为:
(1)为avformat分配空间(avformat_alloc_context)
(2)初始化输入格式(init_input)
(3)黑白名单的检查
(4)其他信息的检查
(5)读取头信息(read_header)
(6)更新流的音频/视频上下文(update_stream_avctx)
在函数执行的过程中,最核心的函数为初始化输入格式(init_input)
int avformat_open_input(AVFormatContext **ps, const char *filename,
const AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
FFFormatContext *si;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
int ret = 0;
// ----- 1.avformat分配空间 ------ //
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
si = ffformatcontext(s);
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;
}
// ----- 2.初始化输入格式 ----- //
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
s->probe_score = ret;
// ----- 3.检查黑白名单 ----- //
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;
}
// ----- 4.其他信息的检查 ----- //
// 检查跳过的字节
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 (ffifmt(s->iformat)->priv_data_size > 0) {
if (!(s->priv_data = av_mallocz(ffifmt(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 an AVIOContext */
// AVFMT_NOFILE格式将没有AVIOContext
if (s->pb)
ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
if (ffifmt(s->iformat)->read_header)
// ----- 5.读取头信息 ----- //
if ((ret = ffifmt(s->iformat)->read_header(s)) < 0) {
if (ffifmt(s->iformat)->flags_internal & FF_INFMT_FLAG_INIT_CLEANUP)
goto close;
goto fail;
}
// id3v3主要用于提供MP3文件的附加信息,例如标题、专辑、发行年份等等
if (!s->metadata) {
s->metadata = si->id3v2_meta;
si->id3v2_meta = NULL;
} else if (si->id3v2_meta) {
av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");
av_dict_free(&si->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;
// avio_tell = ftell(读写文件偏移量)
if (s->pb && !si->data_offset)
si->data_offset = avio_tell(s->pb);
si->raw_packet_buffer_size = 0;
// ----- 6.更新流的音频/视频上下文 ----- //
// 更新AVCodecParameters,并且给到编解码器上下文AVCodecContext
update_stream_avctx(s);
if (options) {
av_dict_free(options);
*options = tmp;
}
*ps = s;
return 0;
close:
if (ffifmt(s->iformat)->read_close)
ffifmt(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;
}
1.1 初始化输入格式(init_input)
函数的主要工作内容为打开视频并且探测视频格式,定义位于libavformat\demux.c中,主要的工作流程为:
(1)在自定义AVIOContext情况下,如果指定了format则直接返回,如果没有指定format则会使用av_probe_input_buffer2探测。这种情况出现的数量不多,但当内存中读取信息(需要初始化自定义AVIOContext),则会走入这一分支
(2)在给定format情况下,会直接返回score;如果没有给format,但可以根据文件路径来判断格式,调用av_probe_input_format2进行判断。这种情况是最一般的情况。
(3)如果也无法根据文件路径来判断格式,则需要打开文件进行文件格式的猜测,先调用io_open打开数据源,然后调用av_probe_input_buffer2进行文件格式的猜测
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
AVProbeData pd = { filename, NULL, 0 };
int score = AVPROBE_SCORE_RETRY;
// ----- 1.自定义AVIOContext ----- //
// 如果自定义了AVIOContext,如果指定了format则直接返回;如果没有指定format则使用av_probe_input_buffer2进行探测
// 这种情况出现的数量不多,但当从内存中读取信息(需要初始化自定义AVIOContext),则会走入这一分支
if (s->pb) {
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if (!s->iformat)
// 探测字节流以确定输入格式。每当探测返回的分数过低时,就会增加探测缓冲区的大小,并进行另一次尝试
// 当探测大小达到最大值时,返回得分最高的输入格式
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
else if (s->iformat->flags & AVFMT_NOFILE)
av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
"will be ignored with AVFMT_NOFILE format.\n");
return 0;
}
// ----- 2.给定format或没给定format但可以根据文件路径来判断格式 ----- //
// 更加普遍的情况,因为通常会指定一个format来避免格式出错
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;
// 打开这个数据源
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
if (s->iformat)
return 0;
// ----- 3.如果无法根据文件路径来判断格式,则需要打开文件进行探测格式 ----- //
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
1.1.1 文件路径判断格式(av_probe_input_format2)
函数通过调用av_probe_input_format3获取判断的fmt和对应的score,如果获取的score大于外部指定的score,则返回该fmt,否则返回NULL
const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,
int is_opened, int *score_max)
{
int score_ret;
const AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
if (score_ret > *score_max) {
*score_max = score_ret;
return fmt;
} else
return NULL;
}
av_probe_input_format3的定义如下,位于libavformat\format.c中,主要工作流程为:
(1)【音频】检测id3v2的标签头部信息
(2)循环遍历所有可用的fmt,查找一个score最大的fmt
(a)使用av_demuxer_iterate循环获取每一个fmt
(b)如果当前fmt的probe函数可用,则获取一个score;如果定义了extensions,会使用av_match_ext进行扩展匹配的检查。进行nodat的检查,并调整score,其中AVPROBE_SCORE_EXTENSION=50
(c)如果不包含probe但包含extensions,使用av_match_ext进行扩展的匹配
(d)比较输入媒体的mime和比较的mime,如果mime的score比score要大,则将score配置为mime
(e)寻找最大的score及其对应的format
const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
int is_opened, int *score_ret)
{
AVProbeData lpd = *pd;
const AVInputFormat *fmt1 = NULL;
const AVInputFormat *fmt = NULL;
int score, score_max = 0;
void *i = 0;
const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
enum nodat {
NO_ID3,
ID3_ALMOST_GREATER_PROBE,
ID3_GREATER_PROBE,
ID3_GREATER_MAX_PROBE,
} nodat = NO_ID3;
if (!lpd.buf)
lpd.buf = (unsigned char *) zerobuffer;
// ----- 1.检测id3v2的标签头部信息 ----- //
if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
int id3len = ff_id3v2_tag_len(lpd.buf);
if (lpd.buf_size > id3len + 16) {
if (lpd.buf_size < 2LL*id3len + 16)
nodat = ID3_ALMOST_GREATER_PROBE;
lpd.buf += id3len;
lpd.buf_size -= id3len;
} else if (id3len >= PROBE_BUF_MAX) {
nodat = ID3_GREATER_MAX_PROBE;
} else
nodat = ID3_GREATER_PROBE;
}
// ----- 2.循环遍历所有可用的fmt,查找一个score最大的fmt ----- //
while ((fmt1 = av_demuxer_iterate(&i))) {
if (fmt1->flags & AVFMT_EXPERIMENTAL)
continue;
if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
continue;
score = 0;
// read_probe: 判断给定文件是否有可能被解析为这种格式
// 提供的缓冲区保证是AVPROBE_PADDING_SIZE字节大,所以您不必检查它,除非您需要更多
// 例如flv格式,会调用flv_probe函数,进而调用probe函数进行格式探测
if (ffifmt(fmt1)->read_probe) {
score = ffifmt(fmt1)->read_probe(&lpd);
if (score)
av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
// extensions: 如果定义了扩展,则不执行探测。通常不应该使用扩展格式猜测,因为它不够可靠
if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
switch (nodat) {
case NO_ID3:
score = FFMAX(score, 1);
break;
case ID3_GREATER_PROBE:
case ID3_ALMOST_GREATER_PROBE:
// AVPROBE_SCORE_EXTENSION = 50
score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
break;
case ID3_GREATER_MAX_PROBE:
score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
break;
}
}
} else if (fmt1->extensions) { // 不包含probe但包含extensions,使用av_match_ext进行扩展的匹配
if (av_match_ext(lpd.filename, fmt1->extensions))
score = AVPROBE_SCORE_EXTENSION;
}
// 比较输入媒体的mime和比较的mime,如果mime的score比score要大,则将score配置为mime
if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
// AVPROBE_SCORE_MIME = 75
if (AVPROBE_SCORE_MIME > score) {
av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
score = AVPROBE_SCORE_MIME;
}
}
// 寻找最大的score及其对应的format
if (score > score_max) {
score_max = score;
fmt = fmt1;
} else if (score == score_max)
fmt = NULL;
}
if (nodat == ID3_GREATER_PROBE)
score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
*score_ret = score_max;
return fmt;
}
1.1.1.1 格式探测(read_probe)
在进行格式探测的时候,会根据不同的fmt来执行,例如当前输入的流为flv格式,会使用flv的probe函数,对应的结构体为ff_flv_demuxer,定义在libavformat\flvdec.c中
const FFInputFormat ff_flv_demuxer = {
.p.name = "flv",
.p.long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
.p.extensions = "flv",
.p.priv_class = &flv_kux_class,
.priv_data_size = sizeof(FLVContext),
.read_probe = flv_probe,
.read_header = flv_read_header,
.read_packet = flv_read_packet,
.read_seek = flv_read_seek,
.read_close = flv_read_close,
};
与旧版本的FFmpeg相比,这里有一点改动,使用的是FFInputFormat而不是AVInputFormat,FFInputFormat对AVInputFormat进行了封装,定义为libavformat\demux.h中
typedef struct FFInputFormat {
/**
* The public AVInputFormat. See avformat.h for it.
*/
AVInputFormat p;
/**
* Raw demuxers store their codec ID here.
*/
enum AVCodecID raw_codec_id;
/**
* Size of private data so that it can be allocated in the wrapper.
*/
int priv_data_size;
/**
* Internal flags. See FF_INFMT_FLAG_* above and FF_FMT_FLAG_* in internal.h.
*/
int flags_internal;
/**
* Tell if a given file has a chance of being parsed as this format.
* The buffer provided is guaranteed to be AVPROBE_PADDING_SIZE bytes
* big so you do not have to check for that unless you need more.
*/
int (*read_probe)(const AVProbeData *);
/**
* Read the format header and initialize the AVFormatContext
* structure. Return 0 if OK. 'avformat_new_stream' should be
* called to create new streams.
*/
int (*read_header)(struct AVFormatContext *);
/**
* Read one packet and put it in 'pkt'. pts and flags are also
* set. 'avformat_new_stream' can be called only if the flag
* AVFMTCTX_NOHEADER is used and only in the calling thread (not in a
* background thread).
* @return 0 on success, < 0 on error.
* Upon returning an error, pkt must be unreferenced by the caller.
*/
int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
/**
* Close the stream. The AVFormatContext and AVStreams are not
* freed by this function
*/
int (*read_close)(struct AVFormatContext *);
/**
* Seek to a given timestamp relative to the frames in
* stream component stream_index.
* @param stream_index Must not be -1.
* @param flags Selects which direction should be preferred if no exact
* match is available.
* @return >= 0 on success (but not necessarily the new offset)
*/
int (*read_seek)(struct AVFormatContext *,
int stream_index, int64_t timestamp, int flags);
/**
* Get the next timestamp in stream[stream_index].time_base units.
* @return the timestamp or AV_NOPTS_VALUE if an error occurred
*/
int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
int64_t *pos, int64_t pos_limit);
/**
* Start/resume playing - only meaningful if using a network-based format
* (RTSP).
*/
int (*read_play)(struct AVFormatContext *);
/**
* Pause playing - only meaningful if using a network-based format
* (RTSP).
*/
int (*read_pause)(struct AVFormatContext *);
/**
* Seek to timestamp ts.
* Seeking will be done so that the point from which all active streams
* can be presented successfully will be closest to ts and within min/max_ts.
* Active streams are all streams that have AVStream.discard < AVDISCARD_ALL.
*/
int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
/**
* Returns device list with it properties.
* @see avdevice_list_devices() for more details.
*/
int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
} FFInputFormat;
这里的.read_probe使用的是flv_probe,而flv_probe又调用probe
static int probe(const AVProbeData *p, int live)
{
const uint8_t *d = p->buf;
unsigned offset = AV_RB32(d + 5);
if (d[0] == 'F' &&
d[1] == 'L' &&
d[2] == 'V' &&
d[3] < 5 && d[5] == 0 &&
offset + 100 < p->buf_size &&
offset > 8) {
int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10);
if (live == is_live)
return AVPROBE_SCORE_MAX;
}
return 0;
}
static int flv_probe(const AVProbeData *p)
{
return probe(p, 0);
}
参考雷博的介绍可以知道flv头部的定义为
因此,上述函数执行的操作流程为:
(1)获得第6至第9字节的数据(对应Headersize字段)并且做大小端转换,然后存入offset变量。之所以要进行大小端转换是因为FLV是以“大端”方式存储数据,而操作系统是以“小端”方式存储数据,这一转换主要通过AV_RB32()函数实现。AV_RB32()是一个宏定义,其对应的函数是av_bswap32()
(2)解析前3个字节,分别为“F”,“L”和“V”
(3)解析第4个字节,版本号,必须小于5
(4)解析第5个字节,置为0
(5)offset + 100 < p->buf_size 并且 offset > 8
满足上述条件的话,则认为是flv格式
另外,还有一点注意,如果是别的格式,例如是raw H264 格式,不会有read_probe这个函数,如下
const FFOutputFormat ff_h264_muxer = {
.p.name = "h264",
.p.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),
.p.extensions = "h264,264",
.p.audio_codec = AV_CODEC_ID_NONE,
.p.video_codec = AV_CODEC_ID_H264,
.p.subtitle_codec = AV_CODEC_ID_NONE,
.flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
.write_packet = ff_raw_write_packet,
.check_bitstream = h264_check_bitstream,
.p.flags = AVFMT_NOTIMESTAMPS,
};
1.1.1.2 扩展匹配检查(av_match_ext)
函数的功能是检查扩展匹配,定义位于libavformat\avformat.c中,之中调用av_match_name进行名称匹配
int av_match_ext(const char *filename, const char *extensions)
{
const char *ext;
if (!filename)
return 0;
ext = strrchr(filename, '.');
if (ext)
return av_match_name(ext + 1, extensions);
return 0;
}
av_match_name的定义位于libavutil\avstring.c中,如下所示,是一个检查字符串匹配的函数
int av_match_name(const char *name, const char *names)
{
const char *p;
size_t len, namelen;
if (!name || !names)
return 0;
namelen = strlen(name);
while (*names) {
int negate = '-' == *names;
p = strchr(names, ',');
if (!p)
p = names + strlen(names);
names += negate;
len = FFMAX(p - names, namelen);
if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))
return !negate;
names = p + (*p == ',');
}
return 0;
}
1.1.2 打开文件推测格式(av_probe_input_buffer2)
函数根据输入的数据文件来推测使用的格式
/*
探测字节流以确定输入格式。每当探测返回的分数过低时,就会增加探测缓冲区的大小,并进行另一次尝试。
当探测大小达到最大值时,返回得分最高的输入格式
*/
int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,
const char *filename, void *logctx,
unsigned int offset, unsigned int max_probe_size)
{
AVProbeData pd = { filename ? filename : "" };
uint8_t *buf = NULL;
int ret = 0, probe_size, buf_offset = 0;
int score = 0;
int ret2;
int eof = 0;
// max_probe_size表示用于推测格式的最大probe大小,默认为PROBE_BUF_MAX (1<<20≈1MB)
if (!max_probe_size)
max_probe_size = PROBE_BUF_MAX;
else if (max_probe_size < PROBE_BUF_MIN) { // PROBE_BUF_MIN = 2048
av_log(logctx, AV_LOG_ERROR,
"Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN);
return AVERROR(EINVAL);
}
if (offset >= max_probe_size)
return AVERROR(EINVAL);
if (pb->av_class) {
uint8_t *mime_type_opt = NULL;
char *semi;
av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);
pd.mime_type = (const char *)mime_type_opt;
semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;
if (semi) {
*semi = '\0';
}
}
// 在确定了max_probe_size之后,以PROBE_BUF_MIN为最小值开始进行探测
for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt && !eof;
probe_size = FFMIN(probe_size << 1,
FFMAX(max_probe_size, probe_size + 1))) {
score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;
/* Read probe data. */
// 读取probe的数据
if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)
goto fail;
// avio_read中底层会调用read_packet,但是读取的比特数只有头部,不会读取内容
if ((ret = avio_read(pb, buf + buf_offset,
probe_size - buf_offset)) < 0) {
/* Fail if error was not end of file, otherwise, lower score. */
if (ret != AVERROR_EOF)
goto fail;
score = 0;
ret = 0; /* error was end of file, nothing read */
eof = 1;
}
buf_offset += ret;
if (buf_offset < offset)
continue;
pd.buf_size = buf_offset - offset;
pd.buf = &buf[offset];
memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);
/* Guess file format. */
// 猜测文件的格式
// av_probe_input_format2会调用av_probe_input_format3进行格式探测
*fmt = av_probe_input_format2(&pd, 1, &score);
if (*fmt) {
/* This can only be true in the last iteration. */
if (score <= AVPROBE_SCORE_RETRY) {
av_log(logctx, AV_LOG_WARNING,
"Format %s detected only with low score of %d, "
"misdetection possible!\n", (*fmt)->name, score);
} else
av_log(logctx, AV_LOG_DEBUG,
"Format %s probed with size=%d and score=%d\n",
(*fmt)->name, probe_size, score);
#if 0
FILE *f = fopen("probestat.tmp", "ab");
fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename);
fclose(f);
#endif
}
}
if (!*fmt)
ret = AVERROR_INVALIDDATA;
fail:
/* Rewind. Reuse probe buffer to avoid seeking. */
ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);
if (ret >= 0)
ret = ret2;
av_freep(&pd.mime_type);
return ret < 0 ? ret : score;
}
1.2 读取头(read_header)
在进行了格式探测之后,会使用.read_header进行头信息的读取,例如使用flv格式,如下,会使用flv_read_header进行头信息的读取
const FFInputFormat ff_flv_demuxer = {
.p.name = "flv",
.p.long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
.p.extensions = "flv",
.p.priv_class = &flv_kux_class,
.priv_data_size = sizeof(FLVContext),
.read_probe = flv_probe,
.read_header = flv_read_header,
.read_packet = flv_read_packet,
.read_seek = flv_read_seek,
.read_close = flv_read_close,
};
flv_read_header的定义如下。与老版本不同的是,这里只会进行header信息的读取,不会创建新的流stream
static int flv_read_header(AVFormatContext *s)
{
int flags;
FLVContext *flv = s->priv_data;
int offset;
int pre_tag_size = 0;
/* Actual FLV data at 0xe40000 in KUX file */
// KUX格式是优酷专属的视频格式,用于版权保护
// 这里的意思应该是FLV数据在KUX文件中的位置是0xe40000
if(!strcmp(s->iformat->name, "kux"))
avio_skip(s->pb, 0xe40000);
avio_skip(s->pb, 4);
flags = avio_r8(s->pb);
flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);
s->ctx_flags |= AVFMTCTX_NOHEADER;
offset = avio_rb32(s->pb);
avio_seek(s->pb, offset, SEEK_SET);
/* Annex E. The FLV File Format
* E.3 TheFLVFileBody
* Field Type Comment
* PreviousTagSize0 UI32 Always 0
* */
pre_tag_size = avio_rb32(s->pb);
if (pre_tag_size) {
av_log(s, AV_LOG_WARNING, "Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0\n");
}
s->start_time = 0;
flv->sum_flv_tag_size = 0;
flv->last_keyframe_stream_index = -1;
return 0;
}
这里就有一个问题待思考,新版本中的数据流在哪里进行创建?从代码来看,如果是flv格式,read_header之中不会去创建AVStream,如果是别的格式例如MPEG-TS格式,有可能会在read_header中创建AVStream,如下所示。
static int mpegts_read_header(AVFormatContext *s)
{
// ...
if (s->iformat == &ff_mpegts_demuxer.p) {
/* normal demux */
/* first do a scan to get all the services */
seek_back(s, pb, pos);
mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
mpegts_open_section_filter(ts, EIT_PID, eit_cb, ts, 1);
// ...
} else {
AVStream *st;
int pcr_pid, pid, nb_packets, nb_pcrs, ret, pcr_l;
int64_t pcrs[2], pcr_h;
uint8_t packet[TS_PACKET_SIZE];
const uint8_t *data;
/* only read packets */
// 在这里创建新的流
st = avformat_new_stream(s, NULL);
//...
}
如果谈到对于流的理解,我想这里的流AVStream应该是一个通道,这个通道里面不断的进行数据传输,由于一个多媒体文件中可能包含不同种类的数据(如视频,音频,字幕等),所以一个多媒体文件中会包含一个或者多个流。在avformat_input_open当中,会首先打开一个数据源,接收这个数据源的信息,再将这些信息进行解析成多个流进行处理。从代码上看,如果是flv格式,只会解析头部信息,而流通道的建立会在获取packet时去创建,即在flv_read_packet当中实现
1.3 更新流的上下文(update_stream_avctx)
static int update_stream_avctx(AVFormatContext *s)
{
int ret;
// 为每一个流进行更新
for (unsigned i = 0; i < s->nb_streams; i++) {
AVStream *const st = s->streams[i];
FFStream *const sti = ffstream(st);
// 不需要更新context
if (!sti->need_context_update)
continue;
/* close parser, because it depends on the codec */
if (sti->parser && sti->avctx->codec_id != st->codecpar->codec_id) {
av_parser_close(sti->parser);
sti->parser = NULL;
}
/* update internal codec context, for the parser */
// 将codecpar中的参数copy到avctx之中
ret = avcodec_parameters_to_context(sti->avctx, st->codecpar);
if (ret < 0)
return ret;
// 使用二分查找,找到AVCodecDescriptor
sti->codec_desc = avcodec_descriptor_get(sti->avctx->codec_id);
sti->need_context_update = 0;
}
return 0;
}
avcodec_parameters_to_context的定义如下,主要功能是将AVCodecParameters中的参数copy到AVCodecContext之中
int avcodec_parameters_to_context(AVCodecContext *codec,
const AVCodecParameters *par)
{
int ret;
codec->codec_type = par->codec_type;
codec->codec_id = par->codec_id;
codec->codec_tag = par->codec_tag;
codec->bit_rate = par->bit_rate;
codec->bits_per_coded_sample = par->bits_per_coded_sample;
codec->bits_per_raw_sample = par->bits_per_raw_sample;
codec->profile = par->profile;
codec->level = par->level;
switch (par->codec_type) {
case AVMEDIA_TYPE_VIDEO:
codec->pix_fmt = par->format;
codec->width = par->width;
codec->height = par->height;
codec->field_order = par->field_order;
codec->color_range = par->color_range;
codec->color_primaries = par->color_primaries;
codec->color_trc = par->color_trc;
codec->colorspace = par->color_space;
codec->chroma_sample_location = par->chroma_location;
codec->sample_aspect_ratio = par->sample_aspect_ratio;
codec->has_b_frames = par->video_delay;
codec->framerate = par->framerate;
break;
case AVMEDIA_TYPE_AUDIO:
codec->sample_fmt = par->format;
ret = av_channel_layout_copy(&codec->ch_layout, &par->ch_layout);
if (ret < 0)
return ret;
codec->sample_rate = par->sample_rate;
codec->block_align = par->block_align;
codec->frame_size = par->frame_size;
codec->delay =
codec->initial_padding = par->initial_padding;
codec->trailing_padding = par->trailing_padding;
codec->seek_preroll = par->seek_preroll;
break;
case AVMEDIA_TYPE_SUBTITLE:
codec->width = par->width;
codec->height = par->height;
break;
}
av_freep(&codec->extradata);
if (par->extradata) {
codec->extradata = av_mallocz(par->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
if (!codec->extradata)
return AVERROR(ENOMEM);
memcpy(codec->extradata, par->extradata, par->extradata_size);
codec->extradata_size = par->extradata_size;
}
av_packet_side_data_free(&codec->coded_side_data, &codec->nb_coded_side_data);
ret = codec_parameters_copy_side_data(&codec->coded_side_data, &codec->nb_coded_side_data,
par->coded_side_data, par->nb_coded_side_data);
if (ret < 0)
return ret;
return 0;
}
avcodec_descriptor_get的定义如下,其中调用了bsearch进行二分查找,该函数定义在corecrt_search.h中,是Windows内嵌函数
const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id)
{
return bsearch(&id, codec_descriptors, FF_ARRAY_ELEMS(codec_descriptors),
sizeof(codec_descriptors[0]), descriptor_compare);
}
3.小结
avformat_input_open作为FFmpeg项目中使用频率非常高的函数,它的使用目的可以理解为,输入一个url和一个AVFormatContext,将这个URL数据源当中的信息解析并存入到AVFormatContext之中,其中最终要的是AVInputFormat信息。在探测或者猜测了这个信息之后,可以确定后续以何种方式来解析这个源数据当中的头信息,比如FLV格式还是H264格式,其数据处理的方式是不同的。
在用法上看,avformat_input_open可以这么来用,需要注意最后需要使用一个avformat_close_input来关闭这个数据源
int main()
{
AVFormatContext* av_in_fmt_ctx = NULL;
const char* in_filename = "test.flv";
if ((ret = avformat_open_input(&av_in_fmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file.");
goto end;
}
// processing ....
avformat_close_input(&av_in_fmt_ctx);
}
CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen