【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 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;}// ----- 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格式将没有AVIOContextif (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;} 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;// avio_tell = ftell(读写文件偏移量)if (s->pb && !si->data_offset)si->data_offset = avio_tell(s->pb);si->raw_packet_buffer_size = 0;// ----- 6.更新流的音频/视频上下文 ----- //// 更新AVCodecParameters,并且给到编解码器上下文AVCodecContextupdate_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;} elsereturn 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;} elsenodat = 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 = 50score = 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配置为mimeif (av_match_name(lpd.mime_type, fmt1->mime_type)) {// AVPROBE_SCORE_MIME = 75if (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及其对应的formatif (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 = 2048av_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);} elseav_log(logctx, AV_LOG_DEBUG,"Format %s probed with size=%d and score=%d\n",(*fmt)->name, probe_size, score);
#if 0FILE *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文件中的位置是0xe40000if(!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);// 不需要更新contextif (!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;// 使用二分查找,找到AVCodecDescriptorsti->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