FFmpeg源代码简单分析-解码-打开媒体的函数avformat_open_input

参考链接

  • 图解FFMPEG打开媒体的函数avformat_open_input_雷霄骅的博客-CSDN博客_avformat_open_input 使用
  • FFmpeg源代码简单分析:avformat_open_input()_雷霄骅的博客-CSDN博客_avformat_open_input()

avformat_open_input

  • FFmpeg打开媒体的的过程开始于avformat_open_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;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;}if ((ret = init_input(s, filename, &tmp)) < 0)goto fail;s->probe_score = ret;if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);if (!s->protocol_whitelist) {ret = AVERROR(ENOMEM);goto fail;}}if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);if (!s->protocol_blacklist) {ret = AVERROR(ENOMEM);goto fail;}}if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);ret = AVERROR(EINVAL);goto fail;}avio_skip(s->pb, s->skip_initial_bytes);/* Check filename in case an image number is expected. */if (s->iformat->flags & AVFMT_NEEDNUMBER) {if (!av_filename_number_test(filename)) {ret = AVERROR(EINVAL);goto fail;}}s->duration = s->start_time = AV_NOPTS_VALUE;/* Allocate private data. */if (s->iformat->priv_data_size > 0) {if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {ret = AVERROR(ENOMEM);goto fail;}if (s->iformat->priv_class) {*(const AVClass **) s->priv_data = s->iformat->priv_class;av_opt_set_defaults(s->priv_data);if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)goto fail;}}/* e.g. AVFMT_NOFILE formats will not have an AVIOContext */if (s->pb)ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);if (s->iformat->read_header)if ((ret = s->iformat->read_header(s)) < 0) {if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP)goto close;goto fail;}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;if (s->pb && !si->data_offset)si->data_offset = avio_tell(s->pb);si->raw_packet_buffer_size = 0;update_stream_avctx(s);if (options) {av_dict_free(options);*options = tmp;}*ps = s;return 0;close:if (s->iformat->read_close)s->iformat->read_close(s);
fail:ff_id3v2_free_extra_meta(&id3v2_extra_meta);av_dict_free(&tmp);if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))avio_closep(&s->pb);avformat_free_context(s);*ps = NULL;return ret;
}
  • 在该函数中,FFMPEG完成了:
    • 输入输出结构体AVIOContext的初始化;
    • 输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):
      • 1判断文件名的后缀
      • 2读取文件头的数据进行比对;
    • 使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);
    • 剩下的就是调用该URLProtocol的函数进行open,read等操作了  没看到相关的代码
  • 函数的形参
    • ps:函数调用成功之后处理过的AVFormatContext结构体。
    • file:打开的视音频流的URL。
    • fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
    • options:附加的一些选项,一般情况下可以设置为NULL。
  •  函数执行成功的话,其返回值大于等于0。
  • 以下是函数调用关系图(有存在差异的地方)
    • Avio_open源代码里面已经不用了,而是使用io_open,只是打开了,但是没看到open,read等操作的代码

  •  avformat_open_input()源代码比较长,一部分是一些容错代码,比如说如果发现传入的AVFormatContext指针没有初始化过,就调用avformat_alloc_context()初始化该结构体;
  • 还有一部分是针对一些格式做的特殊处理,比如id3v2信息的处理等等。
  • 有关上述两种信息不再详细分析,在这里只选择它关键的两个函数进行分析:
  • init_input():绝大部分初始化工作都是在这里做的。
  • s->iformat->read_header():读取多媒体数据文件头,根据视音频流创建相应的AVStream。  
  • 下面我们逐一看看上述函数。

init_input()

  • 它的主要工作就是打开输入的视频数据并且探测视频的格式
static int init_input(AVFormatContext *s, const char *filename,AVDictionary **options)
{int ret;AVProbeData pd = { filename, NULL, 0 };int score = AVPROBE_SCORE_RETRY;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;}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;return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);
}
  • 这个函数在短短的几行代码中包含了好几个return,因此逻辑还是有点复杂的,我们可以梳理一下:  
  • 在函数的开头的score变量是一个判决AVInputFormat的分数的门限值,如果最后得到的AVInputFormat的分数低于该门限值,就认为没有找到合适的AVInputFormat。
  • FFmpeg内部判断封装格式的原理实际上是对每种AVInputFormat给出一个分数,满分是100分,越有可能正确的AVInputFormat给出的分数就越高。最后选择分数最高的AVInputFormat作为推测结果。
  • score的值是一个宏定义AVPROBE_SCORE_RETRY,我们可以看一下它的定义:
    • #define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)  
  • 其中AVPROBE_SCORE_MAX是score的最大值,取值是100:
    • #define AVPROBE_SCORE_MAX       100 ///< maximum score  
  • 由此我们可以得出score取值是25,即如果推测后得到的最佳AVInputFormat的分值低于25,就认为没有找到合适的AVInputFormat。
  •  整个函数的逻辑大体如下:
    • (1)当使用了自定义的AVIOContext的时候(AVFormatContext中的AVIOContext不为空,即s->pb!=NULL),如果指定了AVInputFormat就直接返回,如果没有指定就调用av_probe_input_buffer2()推测AVInputFormat。这一情况出现的不算很多,但是当我们从内存中读取数据的时候(需要初始化自定义的AVIOContext),就会执行这一步骤。
    • (2)在更一般的情况下,如果已经指定了AVInputFormat,就直接返回;如果没有指定AVInputFormat,就调用av_probe_input_format(NULL,…)根据文件路径判断文件格式。这里特意把av_probe_input_format()的第1个参数写成“NULL”,是为了强调这个时候实际上并没有给函数提供输入数据,此时仅仅通过文件路径推测AVInputFormat。
    • (3)如果发现通过文件路径判断不出来文件格式,那么就需要打开文件探测文件格式了,这个时候会首先调用io_open()打开文件,然后调用av_probe_input_buffer2()推测AVInputFormat。  

av_probe_input_format2()

  • av_probe_input_format2()是一个API函数。
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;
}
  • 该函数用于根据输入数据查找合适的AVInputFormat。参数含义如下所示:
    • pd:存储输入数据信息的AVProbeData结构体。
    • is_opened:文件是否打开。
    • score_max:判决AVInputFormat的门限值。只有某格式判决分数大于该门限值的时候,函数才会返回该封装格式,否则返回NULL。
  • 从函数中可以看出,av_probe_input_format2()调用了av_probe_input_format3(),并且增加了一个判断,当av_probe_input_format3()返回的分数大于score_max的时候,才会返回AVInputFormat,否则返回NULL。
  • 下面我们看一下av_probe_input_format3()
  • 从函数声明中可以看出,av_probe_input_format3()和av_probe_input_format2()的区别是函数的第3个参数不同:av_probe_input_format2()是一个分数的门限值,而av_probe_input_format3()是一个探测后的最匹配的格式的分数值。
  • av_probe_input_format3函数最主要的部分是循环。该循环调用av_iformat_next()遍历(av_demuxer_iterate函数替代了av_iformat_next)FFmpeg中所有的AVInputFormat,并根据以下规则确定AVInputFormat和输入媒体数据的匹配分数(score,反应匹配程度):
  • (1)如果AVInputFormat中包含read_probe(),就调用read_probe()函数获取匹配分数(这一方法如果结果匹配的话,一般会获得AVPROBE_SCORE_MAX的分值,即100分)。如果不包含该函数,就使用av_match_ext()函数比较输入媒体的扩展名和AVInputFormat的扩展名是否匹配,如果匹配的话,设定匹配分数为AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值为50,即50分)
  • (2)使用av_match_name()比较输入媒体的mime_type和AVInputFormat的mime_type,如果匹配的话,设定匹配分数为AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值为75,即75分)
  • (3)如果该AVInputFormat的匹配分数大于此前的最大匹配分数,则记录当前的匹配分数为最大匹配分数,并且记录当前的AVInputFormat为最佳匹配的AVInputFormat。
#define AVPROBE_PADDING_SIZE 32             ///< extra allocated bytes at the end of the probe buffer
#define AVPROBE_SCORE_EXTENSION  50 ///< score for file extension
#define AVPROBE_SCORE_MIME       75 ///< score for file mime type
#define AVPROBE_SCORE_MAX       100 ///< maximum scoreconst 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;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;}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;if (fmt1->read_probe) {score = 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);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: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) {if (av_match_ext(lpd.filename, fmt1->extensions))score = AVPROBE_SCORE_EXTENSION;}if (av_match_name(lpd.mime_type, fmt1->mime_type)) {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;}}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;
}
const AVInputFormat *av_demuxer_iterate(void **opaque)
{static const uintptr_t size = sizeof(demuxer_list)/sizeof(demuxer_list[0]) - 1;uintptr_t i = (uintptr_t)*opaque;const AVInputFormat *f = NULL;uintptr_t tmp;if (i < size) {f = demuxer_list[i];} else if (tmp = atomic_load_explicit(&indev_list_intptr, memory_order_relaxed)) {const AVInputFormat *const *indev_list = (const AVInputFormat *const *)tmp;f = indev_list[i - size];}if (f)*opaque = (void*)(i + 1);return f;
}

AVInputFormat->read_probe()

  •  AVInputFormat中包含read_probe()是用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数。
  • 例如,FLV封装格式的AVInputFormat模块定义如下所示。  
const AVInputFormat ff_flv_demuxer = {.name           = "flv",.long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),.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,.extensions     = "flv",.priv_class     = &flv_kux_class,
};
  • 其中,read_probe()函数对应的是flv_probe()函数
  • 我们可以看一下flv_probe()函数的定义:
static int flv_probe(const AVProbeData *p)
{return probe(p, 0);
}
  • 可见flv_probe()调用了一个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;
}
  •  从probe()函数我们可以看出,该函数做了如下工作:
  • (1)获得第6至第9字节的数据(对应Headersize字段)并且做大小端转换,然后存入offset变量。之所以要进行大小端转换是因为FLV是以“大端”方式存储数据,而操作系统是以“小端”方式存储数据,这一转换主要通过AV_RB32()函数实现。AV_RB32()是一个宏定义,其对应的函数是av_bswap32()。
  • (2)检查开头3个字符(Signature)是否为“FLV”
  • (3)第4个字节(Version)小于5
  • (4)第6个字节(Headersize的第1个字节?)为0
  • (5)offset取值大于8。  
  • 参照FLV文件头的格式可以对上述判断有一个更清晰的认识: 

  •   此外代码中还包含了有关live方式的FLV格式的判断,在这里我们不加探讨。对于我们打开FLV文件来说,live和is_live两个变量取值都为0。也就是说满足上述5个条件的话,就可以认为输入媒体数据是FLV封装格式了。
  • 满足上述条件,probe()函数返回AVPROBE_SCORE_MAX(AVPROBE_SCORE_MAX取值为100,即100分),否则返回0(0分)

av_match_name()

  • av_match_name()是一个API函数,声明位于libavutil\avstring.h,如下所示
  • av_match_name()用于比较两个格式的名称。简单地说就是比较字符串。注意该函数的字符串是不区分大小写的:字符都转换为小写进行比较。
int av_match_name(const char *name, const char *names)
{const char *p;int 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;
}
  • 上述函数还有一点需要注意,其中使用了一个while()循环,用于搜索“,”。这是因为FFmpeg中有些格式是对应多种格式名称的,例如MKV格式的解复用器(Demuxer)的定义如下
const AVInputFormat ff_matroska_demuxer = {.name           = "matroska,webm",.long_name      = NULL_IF_CONFIG_SMALL("Matroska / WebM"),.extensions     = "mkv,mk3d,mka,mks,webm",.priv_data_size = sizeof(MatroskaDemuxContext),.flags_internal = FF_FMT_INIT_CLEANUP,.read_probe     = matroska_probe,.read_header    = matroska_read_header,.read_packet    = matroska_read_packet,.read_close     = matroska_read_close,.read_seek      = matroska_read_seek,.mime_type      = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
};
  •  从代码可以看出,ff_matroska_demuxer中的name字段对应“matroska,webm”,mime_type字段对应“audio/webm,audio/x-matroska,video/webm,video/x-matroska”。
  • av_match_name()函数对于这样的字符串,会把它按照“,”截断成一个个的名称,然后一一进行比较。

av_match_ext()

  • av_match_ext()是一个API函数,声明位于libavformat\avformat.h(注意位置和av_match_name()不一样),如下所示
  • av_match_ext()用于比较文件的后缀。该函数首先通过反向查找的方式找到输入文件名中的“.”,就可以通过获取“.”后面的字符串来得到该文件的后缀。
  • 然后调用av_match_name(),采用和比较格式名称的方法比较两个后缀。
/*** @file* Format register and lookup*/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;
}

AVProbeData

  • av_probe_input_format3()根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中 
  • 该函数中涉及到一个结构体AVProbeData,用于存储输入文件的一些信息,它的定义如下所示。
  • 其中filename是文件路径
  • buf存储用于推测AVInputFormat的媒体数据,其中buf可以为空,但是其后面无论如何都需要填充AVPROBE_PADDING_SIZE个0(AVPROBE_PADDING_SIZE取值为32,即32个0)。
  • 最后还有个mime_type保存媒体的类型。
/*** This structure contains the data a format has to probe a file.*/
typedef struct AVProbeData {const char *filename;unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */int buf_size;       /**< Size of buf except extra allocated bytes */const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
    /*** A callback for opening new IO streams.** Whenever a muxer or a demuxer needs to open an IO stream (typically from* avformat_open_input() for demuxers, but for certain formats can happen at* other times as well), it will call this callback to obtain an IO context.** @param s the format context* @param pb on success, the newly opened IO context should be returned here* @param url the url to open* @param flags a combination of AVIO_FLAG_** @param options a dictionary of additional options, with the same*                semantics as in avio_open2()* @return 0 on success, a negative AVERROR code on failure** @note Certain muxers and demuxers do nesting, i.e. they open one or more* additional internal format contexts. Thus the AVFormatContext pointer* passed to this callback may be different from the one facing the caller.* It will, however, have the same 'opaque' field.*/int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,int flags, AVDictionary **options);

av_probe_input_buffer2()

  • av_probe_input_buffer2()是一个API函数,它根据输入的媒体数据推测该媒体数据的AVInputFormat,声明位于libavformat\avformat.h,如下所示。
/*** Probe a bytestream to determine the input format. Each time a probe returns* with a score that is too low, the probe buffer size is increased and another* attempt is made. When the maximum probe size is reached, the input format* with the highest score is returned.** @param pb the bytestream to probe* @param fmt the input format is put here* @param url the url of the stream* @param logctx the log context* @param offset the offset within the bytestream to probe from* @param max_probe_size the maximum probe buffer size (zero for default)* @return the score in case of success, a negative value corresponding to an*         the maximal score is AVPROBE_SCORE_MAX* AVERROR code otherwise*/
int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,const char *url, void *logctx,unsigned int offset, unsigned int max_probe_size);
  • av_probe_input_buffer2()参数的含义如下所示:
    • pb:用于读取数据的AVIOContext。
    • fmt:输出推测出来的AVInputFormat。
    • url:输入媒体的路径。
    • logctx:日志(没有研究过)。
    • offset:开始推测AVInputFormat的偏移量。
    • max_probe_size:用于推测格式的媒体数据的最大值。
    •  返回推测后的得到的AVInputFormat的匹配分数。
  •  av_probe_input_buffer2()的定义位于libavformat\format.c,如下所示。
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;if (!max_probe_size)max_probe_size = PROBE_BUF_MAX;else if (max_probe_size < PROBE_BUF_MIN) {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';}}for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;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. */if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)goto fail;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 */}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. */*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;
}
  • av_probe_input_buffer2()首先需要确定用于推测格式的媒体数据的最大值max_probe_size
  • max_probe_size默认为PROBE_BUF_MAX(PROBE_BUF_MAX取值为1 << 20,即1048576Byte,大约1MB)。
  •  在确定了max_probe_size之后,函数就会进入到一个循环中,调用avio_read()读取数据并且使用av_probe_input_format2()(该函数前文已经记录过)推测文件格式。
  •  肯定有人会奇怪这里为什么要使用一个循环,而不是只运行一次?其实这个循环是一个逐渐增加输入媒体数据量的过程。av_probe_input_buffer2()并不是一次性读取max_probe_size字节的媒体数据,我个人感觉可能是因为这样做不是很经济,因为推测大部分媒体格式根本用不到1MB这么多的媒体数据。因此函数中使用一个probe_size存储需要读取的字节数,并且随着循环次数的增加逐渐增加这个值。函数首先从PROBE_BUF_MIN(取值为2048)个字节开始读取,如果通过这些数据已经可以推测出AVInputFormat,那么就可以直接退出循环了(参考for循环的判断条件“!*fmt”);如果没有推测出来,就增加probe_size的量为过去的2倍(参考for循环的表达式“probe_size << 1”),继续推测AVInputFormat;如果一直读取到max_probe_size字节的数据依然没能确定AVInputFormat,则会退出循环并且返回错误信息。

AVInputFormat-> read_header()

  •  在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。
  • read_header()是一个位于AVInputFormat结构体中的一个函数指针,对于不同的封装格式,会调用不同的read_header()的实现函数。
  • 举个例子
    • 当输入视频的封装格式为FLV的时候,会调用FLV的AVInputFormat中的read_header()
  • FLV的AVInputFormat定义位于libavformat\flvdec.c文件中,如下所示。
const AVInputFormat ff_flv_demuxer = {.name           = "flv",.long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),.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,.extensions     = "flv",.priv_class     = &flv_kux_class,
};
  • 可以看出read_header()指向了flv_read_header()函数
  • flv_read_header()的实现同样位于libavformat\flvdec.c文件中,如下所示。 
static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{FLVContext *flv = s->priv_data;int ret, i, size, flags;enum FlvTagType type;int stream_type=-1;int64_t next, pos, meta_pos;int64_t dts, pts = AV_NOPTS_VALUE;int av_uninit(channels);int av_uninit(sample_rate);AVStream *st    = NULL;int last = -1;int orig_size;retry:/* pkt size is repeated at end. skip it */pos  = avio_tell(s->pb);type = (avio_r8(s->pb) & 0x1F);orig_size =size = avio_rb24(s->pb);flv->sum_flv_tag_size += size + 11;dts  = avio_rb24(s->pb);dts |= (unsigned)avio_r8(s->pb) << 24;av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, dts, avio_tell(s->pb));if (avio_feof(s->pb))return AVERROR_EOF;avio_skip(s->pb, 3); /* stream id, always 0 */flags = 0;if (flv->validate_next < flv->validate_count) {int64_t validate_pos = flv->validate_index[flv->validate_next].pos;if (pos == validate_pos) {if (FFABS(dts - flv->validate_index[flv->validate_next].dts) <=VALIDATE_INDEX_TS_THRESH) {flv->validate_next++;} else {clear_index_entries(s, validate_pos);flv->validate_count = 0;}} else if (pos > validate_pos) {clear_index_entries(s, validate_pos);flv->validate_count = 0;}}if (size == 0) {ret = FFERROR_REDO;goto leave;}next = size + avio_tell(s->pb);if (type == FLV_TAG_TYPE_AUDIO) {stream_type = FLV_STREAM_TYPE_AUDIO;flags    = avio_r8(s->pb);size--;} else if (type == FLV_TAG_TYPE_VIDEO) {stream_type = FLV_STREAM_TYPE_VIDEO;flags    = avio_r8(s->pb);size--;if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD)goto skip;} else if (type == FLV_TAG_TYPE_META) {stream_type=FLV_STREAM_TYPE_SUBTITLE;if (size > 13 + 1 + 4) { // Header-type metadata stuffint type;meta_pos = avio_tell(s->pb);type = flv_read_metabody(s, next);if (type == 0 && dts == 0 || type < 0) {if (type < 0 && flv->validate_count &&flv->validate_index[0].pos     > next &&flv->validate_index[0].pos - 4 < next) {av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n");next = flv->validate_index[0].pos - 4;}goto skip;} else if (type == TYPE_ONTEXTDATA) {avpriv_request_sample(s, "OnTextData packet");return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_ONCAPTION) {return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_UNKNOWN) {stream_type = FLV_STREAM_TYPE_DATA;}avio_seek(s->pb, meta_pos, SEEK_SET);}} else {av_log(s, AV_LOG_DEBUG,"Skipping flv packet: type %d, size %d, flags %d.\n",type, size, flags);
skip:if (avio_seek(s->pb, next, SEEK_SET) != next) {// This can happen if flv_read_metabody above read past// next, on a non-seekable input, and the preceding data has// been flushed out from the IO buffer.av_log(s, AV_LOG_ERROR, "Unable to seek to the next packet\n");return AVERROR_INVALIDDATA;}ret = FFERROR_REDO;goto leave;}/* skip empty data packets */if (!size) {ret = FFERROR_REDO;goto leave;}/* now find stream */for (i = 0; i < s->nb_streams; i++) {st = s->streams[i];if (stream_type == FLV_STREAM_TYPE_AUDIO) {if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&(s->audio_codec_id || flv_same_audio_codec(st->codecpar, flags)))break;} else if (stream_type == FLV_STREAM_TYPE_VIDEO) {if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&(s->video_codec_id || flv_same_video_codec(st->codecpar, flags)))break;} else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) {if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)break;} else if (stream_type == FLV_STREAM_TYPE_DATA) {if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA)break;}}if (i == s->nb_streams) {static const enum AVMediaType stream_types[] = {AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_SUBTITLE, AVMEDIA_TYPE_DATA};st = create_stream(s, stream_types[stream_type]);if (!st)return AVERROR(ENOMEM);}av_log(s, AV_LOG_TRACE, "%d %X %d \n", stream_type, flags, st->discard);if (flv->time_pos <= pos) {dts += flv->time_offset;}if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY ||stream_type == FLV_STREAM_TYPE_AUDIO))av_add_index_entry(st, pos, dts, size, 0, AVINDEX_KEYFRAME);if ((st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || stream_type == FLV_STREAM_TYPE_AUDIO)) ||(st->discard >= AVDISCARD_BIDIR && ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && stream_type == FLV_STREAM_TYPE_VIDEO)) ||st->discard >= AVDISCARD_ALL) {avio_seek(s->pb, next, SEEK_SET);ret = FFERROR_REDO;goto leave;}// if not streamed and no duration from metadata then seek to end to find// the duration from the timestampsif ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&(!s->duration || s->duration == AV_NOPTS_VALUE) &&!flv->searched_for_end) {int size;const int64_t pos   = avio_tell(s->pb);// Read the last 4 bytes of the file, this should be the size of the// previous FLV tag. Use the timestamp of its payload as duration.int64_t fsize       = avio_size(s->pb);
retry_duration:avio_seek(s->pb, fsize - 4, SEEK_SET);size = avio_rb32(s->pb);if (size > 0 && size < fsize) {// Seek to the start of the last FLV tag at position (fsize - 4 - size)// but skip the byte indicating the type.avio_seek(s->pb, fsize - 3 - size, SEEK_SET);if (size == avio_rb24(s->pb) + 11) {uint32_t ts = avio_rb24(s->pb);ts         |= (unsigned)avio_r8(s->pb) << 24;if (ts)s->duration = ts * (int64_t)AV_TIME_BASE / 1000;else if (fsize >= 8 && fsize - 8 >= size) {fsize -= size+4;goto retry_duration;}}}avio_seek(s->pb, pos, SEEK_SET);flv->searched_for_end = 1;}if (stream_type == FLV_STREAM_TYPE_AUDIO) {int bits_per_coded_sample;channels = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1;sample_rate = 44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >>FLV_AUDIO_SAMPLERATE_OFFSET) >> 3;bits_per_coded_sample = (flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;if (!av_channel_layout_check(&st->codecpar->ch_layout) ||!st->codecpar->sample_rate ||!st->codecpar->bits_per_coded_sample) {av_channel_layout_default(&st->codecpar->ch_layout, channels);st->codecpar->sample_rate           = sample_rate;st->codecpar->bits_per_coded_sample = bits_per_coded_sample;}if (!st->codecpar->codec_id) {flv_set_audio_codec(s, st, st->codecpar,flags & FLV_AUDIO_CODECID_MASK);flv->last_sample_rate =sample_rate           = st->codecpar->sample_rate;flv->last_channels    =channels              = st->codecpar->ch_layout.nb_channels;} else {AVCodecParameters *par = avcodec_parameters_alloc();if (!par) {ret = AVERROR(ENOMEM);goto leave;}par->sample_rate = sample_rate;par->bits_per_coded_sample = bits_per_coded_sample;flv_set_audio_codec(s, st, par, flags & FLV_AUDIO_CODECID_MASK);sample_rate = par->sample_rate;avcodec_parameters_free(&par);}} else if (stream_type == FLV_STREAM_TYPE_VIDEO) {int ret = flv_set_video_codec(s, st, flags & FLV_VIDEO_CODECID_MASK, 1);if (ret < 0)return ret;size -= ret;} else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) {st->codecpar->codec_id = AV_CODEC_ID_TEXT;} else if (stream_type == FLV_STREAM_TYPE_DATA) {st->codecpar->codec_id = AV_CODEC_ID_NONE; // Opaque AMF data}if (st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264 ||st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {int type = avio_r8(s->pb);size--;if (size < 0) {ret = AVERROR_INVALIDDATA;goto leave;}if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {// sign extensionint32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;pts = av_sat_add64(dts, cts);if (cts < 0) { // dts might be wrongif (!flv->wrong_dts)av_log(s, AV_LOG_WARNING,"Negative cts, previous timestamps might be wrong.\n");flv->wrong_dts = 1;} else if (FFABS(dts - pts) > 1000*60*15) {av_log(s, AV_LOG_WARNING,"invalid timestamps %"PRId64" %"PRId64"\n", dts, pts);dts = pts = AV_NOPTS_VALUE;}}if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264)) {AVDictionaryEntry *t;if (st->codecpar->extradata) {if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)return ret;ret = FFERROR_REDO;goto leave;}if ((ret = flv_get_extradata(s, st, size)) < 0)return ret;/* Workaround for buggy Omnia A/XE encoder */t = av_dict_get(s->metadata, "Encoder", NULL, 0);if (st->codecpar->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE"))st->codecpar->extradata_size = 2;ret = FFERROR_REDO;goto leave;}}/* skip empty data packets */if (!size) {ret = FFERROR_REDO;goto leave;}ret = av_get_packet(s->pb, pkt, size);if (ret < 0)return ret;pkt->dts          = dts;pkt->pts          = pts == AV_NOPTS_VALUE ? dts : pts;pkt->stream_index = st->index;pkt->pos          = pos;if (flv->new_extradata[stream_type]) {int ret = av_packet_add_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA,flv->new_extradata[stream_type],flv->new_extradata_size[stream_type]);if (ret >= 0) {flv->new_extradata[stream_type]      = NULL;flv->new_extradata_size[stream_type] = 0;}}if (stream_type == FLV_STREAM_TYPE_AUDIO &&(sample_rate != flv->last_sample_rate ||channels    != flv->last_channels)) {flv->last_sample_rate = sample_rate;flv->last_channels    = channels;ff_add_param_change(pkt, channels, 0, sample_rate, 0, 0);}if (stream_type == FLV_STREAM_TYPE_AUDIO ||(flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY ||stream_type == FLV_STREAM_TYPE_SUBTITLE ||stream_type == FLV_STREAM_TYPE_DATA)pkt->flags |= AV_PKT_FLAG_KEY;leave:last = avio_rb32(s->pb);if (!flv->trust_datasize) {if (last != orig_size + 11 && last != orig_size + 10 &&!avio_feof(s->pb) &&(last != orig_size || !last) && last != flv->sum_flv_tag_size &&!flv->broken_sizes) {av_log(s, AV_LOG_ERROR, "Packet mismatch %d %d %d\n", last, orig_size + 11, flv->sum_flv_tag_size);avio_seek(s->pb, pos + 1, SEEK_SET);ret = resync(s);av_packet_unref(pkt);if (ret >= 0) {goto retry;}}}if (ret >= 0)flv->last_ts = pkt->dts;return ret;
}
  • 可以看出,函数读取了FLV的文件头并且判断其中是否包含视频流和音频流。如果包含视频流或者音频流,就会调用create_stream()函数。

create_stream

  • create_stream()函数定义也位于libavformat\flvdec.c中,如下所示。 
static int create_stream(AVFormatContext *s)
{XCBGrabContext *c = s->priv_data;AVStream *st      = avformat_new_stream(s, NULL);xcb_get_geometry_cookie_t gc;xcb_get_geometry_reply_t *geo;int64_t frame_size_bits;int ret;if (!st)return AVERROR(ENOMEM);ret = av_parse_video_rate(&st->avg_frame_rate, c->framerate);if (ret < 0)return ret;avpriv_set_pts_info(st, 64, 1, 1000000);gc  = xcb_get_geometry(c->conn, c->window_id);geo = xcb_get_geometry_reply(c->conn, gc, NULL);if (!geo) {av_log(s, AV_LOG_ERROR, "Can't find window '0x%x', aborting.\n", c->window_id);return AVERROR_EXTERNAL;}if (!c->width || !c->height) {c->width = geo->width;c->height = geo->height;}if (c->x + c->width > geo->width ||c->y + c->height > geo->height) {av_log(s, AV_LOG_ERROR,"Capture area %dx%d at position %d.%d ""outside the screen size %dx%d\n",c->width, c->height,c->x, c->y,geo->width, geo->height);free(geo);return AVERROR(EINVAL);}c->time_base  = (AVRational){ st->avg_frame_rate.den,st->avg_frame_rate.num };c->frame_duration = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);c->time_frame = av_gettime_relative();ret = pixfmt_from_pixmap_format(s, geo->depth, &st->codecpar->format, &c->bpp);free(geo);if (ret < 0)return ret;frame_size_bits = (int64_t)c->width * c->height * c->bpp;if (frame_size_bits / 8 + AV_INPUT_BUFFER_PADDING_SIZE > INT_MAX) {av_log(s, AV_LOG_ERROR, "Captured area is too large\n");return AVERROR_PATCHWELCOME;}c->frame_size = frame_size_bits / 8;#if CONFIG_LIBXCB_SHMc->shm_pool = av_buffer_pool_init2(c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE,c->conn, allocate_shm_buffer, NULL);if (!c->shm_pool)return AVERROR(ENOMEM);
#endifst->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;st->codecpar->codec_id   = AV_CODEC_ID_RAWVIDEO;st->codecpar->width      = c->width;st->codecpar->height     = c->height;st->codecpar->bit_rate   = av_rescale(frame_size_bits, st->avg_frame_rate.num, st->avg_frame_rate.den);return ret;
}
  • 从代码中可以看出,create_stream()调用了API函数avformat_new_stream()创建相应的视频流和音频流。
  • 上面这段解析FLV头的代码可以参考一下FLV封装格式的文件头格式,如下图所示。

  •  经过上面的步骤AVInputFormat的read_header()完成了视音频流对应的AVStream的创建。至此,avformat_open_input()中的主要代码分析完毕。

补充 

  •  可见最终都调用了URLProtocol结构体中的函数指针。   并未得出此结论
  • URLProtocol结构如下,是一大堆函数指针的集合(avio.h文件)
typedef struct URLProtocol {const char *name;int     (*url_open)( URLContext *h, const char *url, int flags);/*** This callback is to be used by protocols which open further nested* protocols. options are then to be passed to ffurl_open_whitelist()* or ffurl_connect() for those nested protocols.*/int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);int     (*url_accept)(URLContext *s, URLContext **c);int     (*url_handshake)(URLContext *c);/*** Read data from the protocol.* If data is immediately available (even less than size), EOF is* reached or an error occurs (including EINTR), return immediately.* Otherwise:* In non-blocking mode, return AVERROR(EAGAIN) immediately.* In blocking mode, wait for data/EOF/error with a short timeout (0.1s),* and return AVERROR(EAGAIN) on timeout.* Checking interrupt_callback, looping on EINTR and EAGAIN and until* enough data has been read is left to the calling function; see* retry_transfer_wrapper in avio.c.*/int     (*url_read)( URLContext *h, unsigned char *buf, int size);int     (*url_write)(URLContext *h, const unsigned char *buf, int size);int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);int     (*url_close)(URLContext *h);int (*url_read_pause)(URLContext *h, int pause);int64_t (*url_read_seek)(URLContext *h, int stream_index,int64_t timestamp, int flags);int (*url_get_file_handle)(URLContext *h);int (*url_get_multi_file_handle)(URLContext *h, int **handles,int *numhandles);int (*url_get_short_seek)(URLContext *h);int (*url_shutdown)(URLContext *h, int flags);const AVClass *priv_data_class;int priv_data_size;int flags;int (*url_check)(URLContext *h, int mask);int (*url_open_dir)(URLContext *h);int (*url_read_dir)(URLContext *h, AVIODirEntry **next);int (*url_close_dir)(URLContext *h);int (*url_delete)(URLContext *h);int (*url_move)(URLContext *h_src, URLContext *h_dst);const char *default_whitelist;
} URLProtocol;
  • URLProtocol功能就是完成各种输入协议的读写等操作
  • 但输入协议种类繁多,它是怎样做到“大一统”的呢?
  • 原来,每个具体的输入协议都有自己对应的URLProtocol。
  • 比如file协议(FFMPEG把文件也当做一种特殊的协议)(*file.c文件)
  • 可见它们把各自的函数指针都赋值给了URLProtocol结构体的函数指针
  • 因此avformat_open_input只需调用url_open,url_read这些函数就可以完成各种具体输入协议的open,read等操作了

 

 

 

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

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

相关文章

redis session java获取attribute_面试题:给我说说你能想到几种分布式session实现?...

作者&#xff1a;yanglbme 来源&#xff1a;https://github.com/doocs/advanced-java/blob/master/docs/distributed-system/distributed-session.md# 面试官心理分析面试官问了你一堆 dubbo 是怎么玩儿的&#xff0c;你会玩儿 dubbo 就可以把单块系统弄成分布式系统&#xff0…

FFmpeg源代码简单分析-解码-avformat_find_stream_info()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_find_stream_info()_雷霄骅的博客-CSDN博客_avformat_find_stream_info avformat_find_stream_info() ​该函数可以读取一部分视音频数据并且获得一些相关的信息avformat_find_stream_info()的声明位于libavformat\avform…

Tail Recursion尾递归

什么是尾递归 Tail Recursion /teɪl rɪˈkɜːrʒn/ In traditional recursion, the typical model is that you perform your recursive calls first, and then you take the return value of the recursive call and calculate the result. In this manner, you don’t g…

FFmpeg源代码简单分析-解码-av_read_frame()

参考链接 ffmpeg 源代码简单分析 &#xff1a; av_read_frame()_雷霄骅的博客-CSDN博客_ffmpeg frame av_read_frame() ffmpeg中的av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如&#xff0c;解码视频的时候&#xff0c;每解码一个视频帧&#xff0c;需要…

FFmpeg源代码简单分析-解码-avformat_close_input()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_close_input()_雷霄骅的博客-CSDN博客_avformat_close_input avformat_close_input() 本文简单分析FFmpeg的avformat_close_input()函数。该函数用于关闭一个AVFormatContext&#xff0c;一般情况下是和avformat_open_inp…

FFmpeg源代码简单分析-编码-avformat_alloc_output_context2()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_alloc_output_context2()_雷霄骅的博客-CSDN博客_avformat_alloc_context avformat_alloc_output_context2() 在基于FFmpeg的视音频编码器程序中&#xff0c;该函数通常是第一个调用的函数&#xff08;除了组件注册函数av…

FFmpeg源代码简单分析-编码-avformat_write_header()

参考链接 FFmpeg源代码简单分析&#xff1a;avformat_write_header()_雷霄骅的博客-CSDN博客_avformat_write_header avformat_write_header() FFmpeg写文件用到的3个函数&#xff1a;avformat_write_header()&#xff0c;av_write_frame()以及av_write_trailer()其中av_writ…

《深入理解JVM.2nd》笔记(二):Java内存区域与内存溢出异常

文章目录概述运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存HotSpot虚拟机对象探秘对象的创建第一步第二步第三步第四步最后一脚对象的内存布局对象头Header第一部分第二部分实例数据Instance对齐填充Padding对象的访问定位句柄直接指针对象…

《深入理解JVM.2nd》笔记(三):垃圾收集器与垃圾回收策略

文章目录概述对象已死吗引用计数算法可达性分析算法再谈引用finalize()&#xff1a;生存还是死亡回收方法区垃圾收集算法标记-清除算法复制算法标记-整理算法分代收集算法HotSpot的算法实现枚举根结点安全点安全区域垃圾收集器SerialParNewParallel ScavengeSerial OldParallel…

FFmpeg源代码简单分析-编码-av_write_frame()

参考链接 FFmpeg源代码简单分析&#xff1a;av_write_frame()_雷霄骅的博客-CSDN博客_av_write_frame av_write_frame() av_write_frame()用于输出一帧视音频数据&#xff0c;它的声明位于libavformat\avformat.h&#xff0c;如下所示。 /*** Write a packet to an output me…

《深入理解JVM.2nd》笔记(四):虚拟机性能监控与故障处理工具

文章目录概述JDK的命令行工具jps&#xff1a;虚拟机进程状况工具jstat&#xff1a;虚拟机统计信息监视工具jinfo&#xff1a;Java配置信息工具jmap&#xff1a;Java内存映像工具jhat&#xff1a;虚拟机堆转储快照分析工具jstack&#xff1a;Java堆栈跟踪工具HSDIS&#xff1a;J…

FFmpeg源代码简单分析-编码-av_write_trailer()

参考链接&#xff1a; FFmpeg源代码简单分析&#xff1a;av_write_trailer()_雷霄骅的博客-CSDN博客_av_malloc av_write_trailer() av_write_trailer()用于输出文件尾&#xff0c;它的声明位于libavformat\avformat.h&#xff0c;如下所示 /*** Write the stream trailer to…

FFmpeg源代码简单分析-其他-日志输出系统(av_log()等)

参考链接 FFmpeg源代码简单分析&#xff1a;日志输出系统&#xff08;av_log()等&#xff09;_雷霄骅的博客-CSDN博客_ffmpeg源码分析 日志输出系统&#xff08;av_log()等&#xff09; 本文分析一下FFmpeg的日志&#xff08;Log&#xff09;输出系统的源代码。日志输出部分的…

FFmpeg源代码简单分析-其他-AVClass和AVoption

参考链接 FFmpeg源代码简单分析&#xff1a;结构体成员管理系统-AVClass_雷霄骅的博客-CSDN博客FFmpeg源代码简单分析&#xff1a;结构体成员管理系统-AVOption_雷霄骅的博客-CSDN博客 概述 AVOption用于在FFmpeg中描述结构体中的成员变量。它最主要的作用可以概括为两个字&a…

FFmpeg源代码简单分析-其他-libswscale的sws_getContext()

参考链接 FFmpeg源代码简单分析&#xff1a;libswscale的sws_getContext()_雷霄骅的博客-CSDN博客 libswscale的sws_getContext() FFmpeg中类库libswsscale用于图像处理&#xff08;缩放&#xff0c;YUV/RGB格式转换&#xff09;libswscale是一个主要用于处理图片像素数据的类…

FFmpeg源代码简单分析-其他-libswscale的sws_scale()

参考链接 FFmpeg源代码简单分析&#xff1a;libswscale的sws_scale()_雷霄骅的博客-CSDN博客_bad dst image pointers libswscale的sws_scale() FFmpeg的图像处理&#xff08;缩放&#xff0c;YUV/RGB格式转换&#xff09;类库libswsscale中的sws_scale()函数。libswscale是一…

FFmpeg源代码简单分析-其他-libavdevice的gdigrab

参考链接 FFmpeg源代码简单分析&#xff1a;libavdevice的gdigrab_雷霄骅的博客-CSDN博客_gdigrab libavdevice的gdigrab GDIGrab用于在Windows下屏幕录像&#xff08;抓屏&#xff09;gdigrab的源代码位于libavdevice\gdigrab.c。关键函数的调用关系图如下图所示。图中绿色背…

Ubuntu安装GmSSL库适用于ubuntu18和ubuntu20版本

参考链接 编译与安装【GmSSL】GmSSL 与 OpenSSL 共存的安装方法_阿卡基YUAN的博客-CSDN博客_openssl和gmssl在Linux下安装GmSSL_百里杨的博客-CSDN博客_安装gmssl ubuntu18操作 需要超级管理员权限本人将下载的安装包master.zip和安装的位置都设定在/usr/local下创建文件夹/u…

Windows7右键菜单栏添加打开cmd项

背景简介 众所周知&#xff0c;在Linux桌面操作系统中的工作目录窗口中&#xff0c;单击鼠标右键&#xff0c;弹出的菜单栏通常有一项“打开终端”&#xff0c;然后移动鼠标点击该项&#xff0c;就可以打开Shell窗口&#xff0c;在当前工作目录进行命令行操作。 但是&#xf…

在ubuntu环境下执行openssl编译和安装

参考链接 工具系列 | Ubuntu18.04安装Openssl-1.1.1_Tinywan的技术博客_51CTO博客密码学专题 openssl编译和安装_MY CUP OF TEA的博客-CSDN博客_openssl 编译安装 下载 /source/index.html编译 使用命令sudo tar -xvzf openssl-1.1.1q.tar.gz 解压。使用cd openssl-1.1.1q/进…