【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.rhkb.cn/news/362312.html

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

相关文章

iOS 其他应用的文件如何在分享中使用自己的应用打开

废话少说 一、第一步&#xff1a;先配置好plist文件 右击info.plist如下图文件打开 根据自己需要配置支持的文件类型&#xff0c;也可使用property List中配置&#xff0c;一样的 其他的文件可是参考文档&#xff1a;System-Declared Uniform Type Identifiers 可复制的代码&am…

【前端】[vue3] [uni-app] 组件样式击穿:deep

我是在开发uni-app时测试的思路&#xff0c;大家可以借鉴一下。 我这边测试的是uni组件&#xff0c;但是我觉得即便你用element-plus之类的&#xff0c;样式击穿的思路都相同。 我自定义了一个全局样式scss文件&#xff0c;并引入到了项目中。(如图) 利用vue3 中的 deep 方式…

Java使用poi生成word文档的简单实例

Java使用poi生成word文档的简单实例 生成的效果如下&#xff1a; 用到的poi的简单的知识 新建一个word对象 //新建文件 XWPFDocument document new XWPFDocument();新建段落以及文字样式 //创建段落 XWPFParagraph paragraph document.createParagraph(); paragraph.se…

收银系统源码-开源收银系统-私有化独立部署

千呼新零售2.0-支持OEM私有化独立部署和全开源源码 千呼新零售2.0-支持OEM私有化独立部署和全开源源码 千呼新零售2.0-支持OEM私有化独立部署和全开源源码 千呼新零售2.0-支持OEM私有化独立部署和全开源源码 如需了解请私信交流

电脑系统重装怎么操作?分享四个win10重装系统方法

“我遇到了一些笔记本电脑的问题&#xff0c;别人告诉我解决这个问题需要重新安装Win10电脑系统。但我不记得我把光盘放在哪里了&#xff0c;我能否在不丢失文件的情况下重新安装操作系统&#xff1f;电脑系统重装怎么操作&#xff1f;”虽然电脑自带系统中有多种方法可供选择&…

【最佳实践】前端如何搭建自己的cli命令行工具,让自己编码的时候如虎添翼

作为前端开发人员&#xff0c;搭建自己的前端CLI工具是一个有趣且有意义的事情。以下是一篇详细的教程&#xff0c;包括使用场景和案例。 使用场景 假设你是一个前端团队的一员&#xff0c;需要频繁地在不同的项目中执行一些标准化的任务&#xff0c;比如&#xff1a; 根据模…

【02-02】SpringMVC基于注解的应用

一、请求处理 1、常用注解 RequestMapping 作用&#xff1a;用来匹配客户端发送的请求&#xff08;用来处理URL映射&#xff0c;将请求映射到处理方法中&#xff09;&#xff0c;可以在类或者方法上使用。 用在类上&#xff0c;可以将请求模块化&#xff0c;避免请求方法中的…

前端 Array.sort() 源码学习

源码地址 V8源码Array 710行开始为sort()相关 Array.sort()方法是那种排序呢&#xff1f; 去看源码主要是源于这个问题 // In-place QuickSort algorithm. // For short (length < 22) arrays, insertion sort is used for efficiency.源码中的第一句话就回答了我的问题…

微软发布Phi-3系列语言模型:手机端的强大AI助手

大模型&#xff08;LLMs&#xff09;在处理复杂任务时展现出的巨大潜力&#xff0c;但却需要庞大的计算资源和存储空间&#xff0c;限制了它们在移动设备等资源受限环境中的应用。微软公司最新发布的Phi-3系列语言模型&#xff0c;以其卓越的性能和小巧的体积&#xff0c;打破了…

FairGuard游戏加固无缝兼容 Android 15 预览版

2024年6月25日&#xff0c;谷歌发布了 Android 15 Beta 3 &#xff0c;作为Android 15 “平台稳定性”的里程碑版本&#xff0c;谷歌建议所有应用、游戏、SDK、库和游戏引擎开发者都将“平台稳定性”里程碑版本作为规划最终兼容性测试和公开发布的目标。 安卓开发者博客提供的版…

积分的可视化

积分的可视化 flyfish 考虑平方根函数 f ( x ) x f(x) \sqrt{x} f(x)x ​&#xff0c;其中 x ∈ [ 0 , 1 ] x \in [0, 1] x∈[0,1] 。在区间 [ 0 , 1 ] [0, 1] [0,1] 上&#xff0c;函数 f f f 下方的面积是指函数 y f ( x ) y f(x) yf(x) 的图像与 x x x 轴之间的部…

【微服务网关——中间件实现】

1.中间件的意义 避免成为if狂魔提高复用、隔离业务调用清晰、组合随意 2.实现原理 中间件一般都封装在路由上&#xff0c;路由是URL请求分发的管理器中间件选型 基于链表构建中间件 基于责任链的实现缺点&#xff1a;实现复杂&#xff0c;调用方式不灵活 使用数组构建中间件 控…

cad怎么导出为图片?分享四种导出方法

cad怎么导出为图片&#xff1f;在工程设计、建筑设计、机械设计等领域&#xff0c;CAD图纸的编辑和分享是一项日常工作。然而&#xff0c;如何将CAD图纸高效、准确地导出为图片格式&#xff0c;一直是设计师们关注的焦点。今天&#xff0c;就为大家推荐四款强大的CAD导出图片软…

连接Huggingface报requests.exceptions.SSLError错误

最近在学习使用 SHAP 算法解释 BERT 模型的输出结果&#xff0c;然而在从 Huggingface 上导入模型和数据集的过程中出现了网络连接相关的错误&#xff0c;本文用于记录错误类型和解决错误的方法。 1 代码示例 SHAP 官方展示的代码如下&#xff1a; import datasets import nu…

企业微信内嵌H5项目接入聊天功能

产品需求是,在列表中把符合条件的列表接入聊天功能,以下是详细步骤: 1.引入企业微信 <script src"https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js"></script> 2.获取wx签名(必须要) /*** 获取wx签名**/ export function getWxJsApi(data) {r…

如何在信创领域中做好防泄露

随着信息技术的迅猛发展&#xff0c;数据安全和防泄露成为了企业和政府机构面临的重大挑战。在信创&#xff08;Creative and Innovative Intelligent Products&#xff09;领域中&#xff0c;沙箱技术以其独特的隔离和保护机制&#xff0c;成为了防泄露的关键手段之一。 一、沙…

上古世纪战争台服官网地址+台服预约+预创建角色教程

上古世纪战争台服上线啦&#xff0c;在《上古世纪战争》中&#xff0c;通过主要势力和地区&#xff0c;剧情和角色可以想起原作。《上古世纪战争》的主要背景为&#xff0c;原大陆消失之后&#xff0c;完成移民的种族们定居在诺伊大陆之后遇到的多个势力之间的冲突。同时&#…

鸿蒙期末项目(4)

day4 页面的设计与编写基本完成&#xff0c;接下来使用我们之前搭建好的服务器与相关的网络接口将鸿蒙中的逻辑真正实现一下。 在实现购物车页面展示功能时&#xff0c;使用了如下代码&#xff1a; getCartList(uid: number): Promise<CartItem[]> {return new Promise…

MTK平台Android13实现三方launcher为默认

一、前言 目前有遇到客户的定制需求,希望使用三方的launcher作为默认的launcher使用,一般情况下直接将三方launcher通过内置到系统并通过overlay机制即可很方便的实现launcher的替换,但是存在一个问题,需要增加ROM的维护成本。本文通过设备在使用前联网通过后台下发三方lau…

基于SpringBoot的财务管理系统

根据您提供的论文内容和模板要求&#xff0c;以下是定制化的文章输出&#xff1a; 你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a; Java 数据库&#xff1a; MySQL 技术&#xff1a; SpringBoot…