【FFmpeg】avformat_open_input函数

【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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/35691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pyecharts进阶篇

欢迎观看Pyecharts部分&#xff0c;上一节&#xff1a;Pyecharts入门-CSDN博客 本章节&#xff0c;我们会使用全国空气质量数据呈现中国地图&#xff0c;还会使用全球各个国家或地区GDP数据绘制全球地图。 全国空气质量分布图 数据准备 import pandas as pd import numpy a…

树莓派4B_OpenCv学习笔记13:OpenCv颜色追踪_程序手动调试HSV色彩空间_检测圆

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; OpenCv颜色追踪_程序手动调试HSV色彩空间_检测灰度图中的…

AI新热点:边云协同:大模型结合小模型(大小模型联合推理)

背景 AI模型规模不断剧增已是不争的事实。模型参数增长至百亿、千亿、万亿甚至十万亿&#xff0c;大模型在算力推动下演变为人工智能领域一场新的“军备竞赛”。 这种竞赛很大程度推动了人工智能的发展&#xff0c;但随之而来的能耗和端侧部署问题限制了大模型应用落地。2022…

非极大值抑制算法(Non-Maximum Suppression,NMS)

https://tcnull.github.io/nms/ https://blog.csdn.net/weicao1990/article/details/103857298 目标检测中检测出了许多的候选框&#xff0c;候选框之间是有重叠的&#xff0c;NMS作用重叠的候选框只保留一个 算法&#xff1a; 将所有候选框放入到集和B从B中选出分数S最大的b…

VBA技术资料MF168:移动工作表为单独工作簿

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

eventbus和vuex

EventBus和Vuex EventBus 工作原理 创建一个vue实例&#xff0c;然后通过空的vue实例作为组件之间的桥梁&#xff0c;进行通信&#xff0c;利用到的设计模式有发布订阅模式 Vuex 工作原理 维护了一个state树&#xff0c;是独立的状态树&#xff0c;有明显的层级关系。不论…

云计算-期末复习题-框架设计/选择/填空/简答(2)

目录 框架设计 1.负载分布架构 2.动态可扩展架构 3.弹性资源容量架构 4.服务负载均衡架构 5.云爆发结构 6.弹性磁盘供给结构 7.负载均衡的虚拟服务器实例架构 填空题/简答题 单选题 多选题 云计算期末复习部分练习题&#xff0c;包括最后的部分框架设计大题(只是部分…

AI办公自动化:多音频轨电影视频抽取出英语音频

很多电影视频是有中、英、粤语等多个音频轨的&#xff0c;如果直接转换成音频&#xff0c;很有可能不是自己想要的那种语音。 可以先查看音频流信息&#xff0c;确定属于哪个音频轨&#xff1a; Reading video file: E:\1-7\比得兔1.mp4 输出音频流信息 Available audio str…

利用viztracer进行性能分析和优化

上一篇文章&#xff0c;我们详细讲解了scalene这个性能分析和优化工具的使用流程&#xff1b;今天&#xff0c;我们将深入探讨另一个性能分析和优化工具——viztracer。 什么是viztracer&#xff1f; viztracer是一个非常强大的分析器&#xff0c;可以生成详细的性能报告和可…

设计师进阶指南:掌握这6条版式设计要点

布局设计是设计师的必修课。优秀的排版不是强制性的“东拼西凑”&#xff0c;而是通过设计师独特的排版获得的。这不是简单的信息列表&#xff0c;而是认真思考如何分层、有节奏地组织和安排元素。今天我将给你带来它 6 文章还附带了布局设计模板资源&#xff0c;设计师朋友一定…

EthernetIP IO从站设备数据 转opc ua项目案例

1 案例说明 设置网关采集EthernetIP IO设备数据把采集的数据转成opc ua协议转发给其他系统。 2 VFBOX网关工作原理 VFBOX网关是协议转换网关&#xff0c;是把一种协议转换成另外一种协议。网关可以采集西门子&#xff0c;欧姆龙&#xff0c;三菱&#xff0c;AB PLC&#xff0…

Element 页面滚动表头置顶

在开发后台管理系统时&#xff0c;表格是最常用的一个组件&#xff0c;为了看数据方便&#xff0c;时常需要固定表头。 如果页面基本只有一个表格区域&#xff0c;我们可以根据屏幕的高度动态的计算出一个值&#xff0c;给表格设定一个固定高度&#xff0c;这样表头就可以固定…

红酒达人教你秘技:选酒、存酒,一招一式皆学问

在繁忙的都市生活中&#xff0c;红酒不仅仅是一种饮品&#xff0c;更是一种生活态度&#xff0c;一种品味的象征。然而&#xff0c;面对琳琅满目的红酒品牌与种类&#xff0c;如何选择一瓶心仪的红酒&#xff0c;又如何妥善保存&#xff0c;使其保持很好口感&#xff0c;成为了…

LabVIEW遇到无法控制国外设备时怎么办

当使用LabVIEW遇到无法控制国外产品的问题时&#xff0c;解决此类问题需要系统化的分析和处理方法。以下是详细的解决思路和具体办法&#xff0c;以及不同方法的分析和比较&#xff0c;包括寻求代理、国外技术支持、国内用过的人请教等内容。 1. 了解产品的通信接口和协议 思路…

Python 基础 (标准库):collections (集合类)

1. 官方文档 collections --- 容器数据类型 — Python 3.12.4 文档 Python 的 collections 模块提供了许多有用的数据类型&#xff08;包括 OrderedDict、Counter、defaultdict、deque 和 namedtuple&#xff09;用于扩展 Python 的标准数据类型。掌握 collections 中的数据类…

五子棋纯python手写,需要的拿去

import pygame,sys from pygame import * pygame.init()game pygame.display.set_mode((600,600)) gameover False circlebox [] # 棋盘坐标点存储 box [] def xy():for x in range(0,800//40): for y in range(0,800//40): box.append((x*40,y*40)) xy() defaultColor wh…

8.DELL R730服务器对RAID5进行扩容

如果服务器的空间不足了&#xff0c;如何进行扩容&#xff1f;我基本上按照如何重新配置虚拟磁盘或添加其他硬盘来进行操作。我的机器上已经有三块硬盘了&#xff0c;组了Raid5&#xff0c;现在再添加一块硬盘。 先把要添加的硬盘插入服务器&#xff0c;无论是在IDRAC还是管理…

物联网“此用户无权修改接入点名称设置”解决方案

根本原因apns-conf.xml里面没有 符合 物理网卡 的配置 可以先加一个APN试一下&#xff0c;看看默认的MCC和MNC是什么 然后在”命令行“查询一下 adb shell sqlite3 /data/user_de/0/com.android.providers.telephony/databases/telephony.db "select * from carriers wh…

乐鑫已支持Matter 1.2标准新增多种设备类型,启明云端乐鑫代理商

随着物联网技术的飞速发展&#xff0c;智能家居正逐渐成为现代生活的一部分。物联网和智能家居行业应用取得了巨大的增长&#xff0c;一系列无线连接的智能设备涌入家庭&#xff0c;为家庭生活带来自动化和便利。 像是可以连网的扬声器、灯泡和中控开关&#xff0c;它们都可以…

迁移学习——CycleGAN

CycleGAN 1.导入需要的包2.数据加载&#xff08;1&#xff09;to_img 函数&#xff08;2&#xff09;数据加载&#xff08;3&#xff09;图像转换 3.随机读取图像进行预处理&#xff08;1&#xff09;函数参数&#xff08;2&#xff09;数据路径&#xff08;3&#xff09;读取文…