ijkplayer框架簡析 -- avformat_open_input
avformat_open_input()
方法完成了媒體檔案的開啟和格式探測的功能
avformat_open_input
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options) { AVFormatContext *s = *ps; int i, ret = 0; AVDictionary *tmp = NULL; AVDictionary *tmp2 = NULL; ID3v2ExtraMeta *id3v2_extra_meta = NULL; if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM); if (!s->av_class) { av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n"); return AVERROR(EINVAL); } if (fmt) s->iformat = fmt; if (options) av_dict_copy(&tmp, *options, 0); if (s->pb) // must be before any goto fail s->flags |= AVFMT_FLAG_CUSTOM_IO; if ((ret = av_opt_set_dict(s, &tmp)) < 0) goto fail; av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename)); 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 a AVIOContext */ if (s->pb) ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); if (!(s->flags&AVFMT_FLAG_PRIV_OPT)) { if (s->iformat->read_header2) { if (options) av_dict_copy(&tmp2, *options, 0); if ((ret = s->iformat->read_header2(s, &tmp2)) < 0) goto fail; } else if (s->iformat->read_header && (ret = s->iformat->read_header(s)) < 0) goto fail; } if (!s->metadata) { s->metadata = s->internal->id3v2_meta; s->internal->id3v2_meta = NULL; } else if (s->internal->id3v2_meta) { int level = AV_LOG_WARNING; if (s->error_recognition & AV_EF_COMPLIANT) level = AV_LOG_ERROR; av_log(s, level, "Discarding ID3 tags because more suitable tags were found.\n"); av_dict_free(&s->internal->id3v2_meta); if (s->error_recognition & AV_EF_EXPLODE) return AVERROR_INVALIDDATA; } if (id3v2_extra_meta) { if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") || !strcmp(s->iformat->name, "tta")) { if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) goto fail; if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0) goto fail; } else av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n"); } ff_id3v2_free_extra_meta(&id3v2_extra_meta); if ((ret = avformat_queue_attached_pictures(s)) < 0) goto fail; if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset) s->internal->data_offset = avio_tell(s->pb); s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; update_stream_avctx(s); for (i = 0; i < s->nb_streams; i++) s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id; if (options) { av_dict_free(options); *options = tmp; av_dict_free(&tmp2); } *ps = s; return 0; fail: ff_id3v2_free_extra_meta(&id3v2_extra_meta); av_dict_free(&tmp); av_dict_free(&tmp2); if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO)) avio_closep(&s->pb); avformat_free_context(s); *ps = NULL; return ret; }
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options)
第一個引數是封裝格式的上下文,第二個引數是檔案地址,第三個引數是資料的封裝格式,第四個引數是配置?
接下來比較重要的是init_input(s, filename, &tmp)
,在這裡面完成了查詢流媒體協議和解複用器的工作
init_input
/* Open input file and probe the format if necessary. */ 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); }
開頭的 score 變數是一個判決 AVInputFormat 的分數的門限值,如果最後得到的 AVInputFormat 的分數低於該門限值,就認為沒有找到合適的 AVInputFormat。FFmpeg 內部判斷封裝格式的原理實際上是對每種 AVInputFormat 給出一個分數,滿分是 100 分,越有可能正確的 AVInputFormat 給出的分數就越高,最後選擇分數最高的 AVInputFormat 作為推測結果,這裡一開始的分數是 25 分
#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4) #define AVPROBE_SCORE_MAX100 ///< maximum score
- 當使用了自定義的 AVIOContext 的時候(AVFormatContext 中的 AVIOContext 不為空,即 s->pb!=NULL),如果指定了 AVInputFormat 就直接返回,如果沒有指定就呼叫 av_probe_input_buffer2() 推測 AVInputFormat。這一情況出現的不算很多,但是當我們從記憶體中讀取資料的時候(需要初始化自定義的 AVIOContext),就會執行這一步驟
- 在更一般的情況下,如果已經指定了 AVInputFormat,就直接返回;如果沒有指定 AVInputFormat,就呼叫 av_probe_input_format(NULL,…) 根據檔案路徑判斷檔案格式。這裡特意把 av_probe_input_format() 的第 1 個引數寫成 “NULL”,是為了強調這個時候實際上並沒有給函式提供輸入資料,此時僅僅通過檔案路徑推測 AVInputFormat
- 如果發現通過檔案路徑判斷不出來檔案格式,那麼就需要開啟檔案探測檔案格式了,這個時候會首先呼叫 avio_open2() 開啟檔案,然後呼叫 av_probe_input_buffer2() 推測 AVInputFormat
av_probe_input_buffer2
int av_probe_input_buffer2(AVIOContext *pb, 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'; } } #if 0 if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) { if (!av_strcasecmp(mime_type, "audio/aacp")) { *fmt = av_find_input_format("aac"); } av_freep(&mime_type); } #endif 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); } else av_log(logctx, AV_LOG_DEBUG, "Format %s probed with size=%d and score=%d\n", (*fmt)->name, probe_size, score); #if 0 FILE *f = fopen("probestat.tmp", "ab"); fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename); fclose(f); #endif } } if (!*fmt) ret = AVERROR_INVALIDDATA; fail: /* Rewind. Reuse probe buffer to avoid seeking. */ ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset); if (ret >= 0) ret = ret2; av_freep(&pd.mime_type); return ret < 0 ? ret : score; }
AVIOContext *pb
用於讀取資料的 AVIOContext,AVInputFormat **fmt
輸出推測出來的 AVInputFormat,unsigned int offset
開始推測 AVInputFormat 的偏移量,unsigned int max_probe_size
用於推測格式的媒體資料的最大值
首先需要確定用於推測格式的媒體資料的最大值 max_probe_size。max_probe_size 預設為 PROBE_BUF_MAX(PROBE_BUF_MAX 取值為 1 << 20,即 1048576Byte,大約 1MB)
在確定了 max_probe_size 之後,函式就會進入到一個迴圈中(因為並不是一次性讀取 max_probe_size 位元組的媒體資料),呼叫 avio_read() 讀取資料並且使用av_probe_input_format2()
去推測檔案格式:
av_probe_input_format2
AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max) { int score_ret; AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret); if (score_ret > *score_max) { *score_max = score_ret; return fmt; } else return NULL; }
av_probe_input_format3()
給出了一個分數score_ret
,然後進行比對:
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret) { AVProbeData lpd = *pd; AVInputFormat *fmt1 = NULL, *fmt; int score, score_max = 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; } else nodat = ID3_GREATER_PROBE; } fmt = NULL; while ((fmt1 = av_iformat_next(fmt1))) { 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; }
while ((fmt1 = av_iformat_next(fmt1)))
迴圈遍歷 AVInputFormat,該方法中的first_iformat
連結串列中的頭節點,並根據以下規則確定 AVInputFormat 和輸入媒體資料的匹配分數
- 如果 AVInputFormat 中包含 read_probe(),就呼叫 read_probe() 函式獲取匹配分數(這一方法如果結果匹配的話,一般會獲得 AVPROBE_SCORE_MAX 的分值,即 100 分)。如果不包含該函式,就使用 av_match_ext() 函式比較輸入媒體的副檔名和 AVInputFormat 的副檔名是否匹配,如果匹配的話,設定匹配分數為 AVPROBE_SCORE_EXTENSION(即 50 分)
- 使用 av_match_name() 比較輸入媒體的 mime_type 和 AVInputFormat 的 mime_type,如果匹配的話,設定匹配分數為 AVPROBE_SCORE_MIME(即 75 分)
- 如果該 AVInputFormat 的匹配分數大於此前的最大匹配分數,則記錄當前的匹配分數為最大匹配分數,並且記錄當前的 AVInputFormat 為最佳匹配的 AVInputFormat
read_probe
read_probe()
是用於獲得匹配函式的函式指標,不同的封裝格式包含不同的實現函式
AVInputFormat ff_mov_demuxer = { .name= "mov,mp4,m4a,3gp,3g2,mj2", .long_name= NULL_IF_CONFIG_SMALL("QuickTime / MOV"), .priv_class= &mov_class, .priv_data_size = sizeof(MOVContext), .extensions= "mov,mp4,m4a,3gp,3g2,mj2", .read_probe= mov_probe, .read_header= mov_read_header, .read_packet= mov_read_packet, .read_close= mov_read_close, .read_seek= mov_read_seek, .flags= AVFMT_NO_BYTE_SEEK, };
其中,read_probe()
函式對應的是mov_probe()
函式:
static int mov_probe(AVProbeData *p) { int64_t offset; uint32_t tag; int score = 0; int moov_offset = -1; /* check file header */ offset = 0; for (;;) { /* ignore invalid offset */ if ((offset + 8) > (unsigned int)p->buf_size) break; tag = AV_RL32(p->buf + offset + 4); switch(tag) { /* check for obvious tags */ case MKTAG('m','o','o','v'): moov_offset = offset + 4; case MKTAG('m','d','a','t'): case MKTAG('p','n','o','t'): /* detect movs with preview pics like ew.mov and april.mov */ case MKTAG('u','d','t','a'): /* Packet Video PVAuthor adds this and a lot of more junk */ case MKTAG('f','t','y','p'): if (AV_RB32(p->buf+offset) < 8 && (AV_RB32(p->buf+offset) != 1 || offset + 12 > (unsigned int)p->buf_size || AV_RB64(p->buf+offset + 8) == 0)) { score = FFMAX(score, AVPROBE_SCORE_EXTENSION); } else if (tag == MKTAG('f','t','y','p') && (AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ') || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ') )) { score = FFMAX(score, 5); } else { score = AVPROBE_SCORE_MAX; } offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; break; /* those are more common words, so rate then a bit less */ case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */ case MKTAG('w','i','d','e'): case MKTAG('f','r','e','e'): case MKTAG('j','u','n','k'): case MKTAG('p','i','c','t'): score= FFMAX(score, AVPROBE_SCORE_MAX - 5); offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; break; case MKTAG(0x82,0x82,0x7f,0x7d): case MKTAG('s','k','i','p'): case MKTAG('u','u','i','d'): case MKTAG('p','r','f','l'): /* if we only find those cause probedata is too small at least rate them */ score= FFMAX(score, AVPROBE_SCORE_EXTENSION); offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; break; default: offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset; } } if(score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) { /* moov atom in the header - we should make sure that this is not a * MOV-packed MPEG-PS */ offset = moov_offset; while(offset < (p->buf_size - 16)){ /* Sufficient space */ /* We found an actual hdlr atom */ if(AV_RL32(p->buf + offset) == MKTAG('h','d','l','r') && AV_RL32(p->buf + offset +8) == MKTAG('m','h','l','r') && AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')){ av_log(NULL, AV_LOG_WARNING, "Found media data tag MPEG indicating this is a MOV-packed MPEG-PS.\n"); /* We found a media handler reference atom describing an * MPEG-PS-in-MOV, return a * low score to force expanding the probe window until * mpegps_probe finds what it needs */ return 5; }else /* Keep looking */ offset+=2; } } return score; }
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; }
用於比較兩個格式的名稱(不區分大小寫)
av_match_ext
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; }
用於比較檔案的字尾
io_open
init_input
中呼叫了s->io_open
,實際上呼叫的就是io_open_default
:
static void avformat_get_context_defaults(AVFormatContext *s) { memset(s, 0, sizeof(AVFormatContext)); s->av_class = &av_format_context_class; s->io_open= io_open_default; s->io_close = io_close_default; av_opt_set_defaults(s); }
在io_open_default()
中會呼叫ffio_open_whitelist()
:
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist ) { URLContext *h; int err; err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL); if (err < 0) return err; err = ffio_fdopen(s, h); if (err < 0) { ffurl_close(h); return err; } return 0; }
接著跟下去:
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char* blacklist, URLContext *parent) { AVDictionary *tmp_opts = NULL; AVDictionaryEntry *e; int ret = ffurl_alloc(puc, filename, flags, int_cb); if (ret < 0) return ret; if (parent) av_opt_copy(*puc, parent); if (options && (ret = av_opt_set_dict(*puc, options)) < 0) goto fail; if (options && (*puc)->prot->priv_data_class && (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) goto fail; if (!options) options = &tmp_opts; av_assert0(!whitelist || !(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) || !strcmp(whitelist, e->value)); av_assert0(!blacklist || !(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) || !strcmp(blacklist, e->value)); if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0) goto fail; if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0) goto fail; if ((ret = av_opt_set_dict(*puc, options)) < 0) goto fail; ret = ffurl_connect(*puc, options); if (!ret) return 0; fail: ffurl_close(*puc); *puc = NULL; return ret; }
在ffurl_alloc()
中呼叫了url_find_protocol()
方法:
int ffurl_alloc(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb) { const URLProtocol *p = NULL; p = url_find_protocol(filename); if (p) return url_alloc_for_protocol(puc, p, filename, flags, int_cb); *puc = NULL; if (av_strstart(filename, "https:", NULL)) av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with " "openssl, gnutls " "or securetransport enabled.\n"); return AVERROR_PROTOCOL_NOT_FOUND; }
url_find_protocol
#define URL_SCHEME_CHARS\ "abcdefghijklmnopqrstuvwxyz"\ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"\ "0123456789+-." static const struct URLProtocol *url_find_protocol(const char *filename) { const URLProtocol **protocols; char proto_str[128], proto_nested[128], *ptr; size_t proto_len = strspn(filename, URL_SCHEME_CHARS); int i; if (filename[proto_len] != ':' && (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) || is_dos_path(filename)) strcpy(proto_str, "file"); else av_strlcpy(proto_str, filename, FFMIN(proto_len + 1, sizeof(proto_str))); av_strlcpy(proto_nested, proto_str, sizeof(proto_nested)); if ((ptr = strchr(proto_nested, '+'))) *ptr = '\0'; protocols = ffurl_get_protocols(NULL, NULL); if (!protocols) return NULL; for (i = 0; protocols[i]; i++) { const URLProtocol *up = protocols[i]; if (!strcmp(proto_str, up->name)) { av_freep(&protocols); return up; } if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && !strcmp(proto_nested, up->name)) { av_freep(&protocols); return up; } } av_freep(&protocols); return NULL; }
ffurl_get_protocols 可以得到當前編譯的 FFmpeg 支援的所有流媒體協議,通過 url 的 scheme 和 protocol->name 相比較,得到正確的 protocol
URLProtocol
url_find_protocol()
方法返回一個URLProtocol
變數
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()/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); int priv_data_size; const AVClass *priv_data_class; 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
功能就是完成各種輸入協議的讀寫等操作,ijkplayer 和 ffmpeg 支援的協議也有很多:
extern const URLProtocol ff_async_protocol; extern const URLProtocol ff_bluray_protocol; extern const URLProtocol ff_cache_protocol; extern const URLProtocol ff_concat_protocol; extern const URLProtocol ff_crypto_protocol; extern const URLProtocol ff_data_protocol; extern const URLProtocol ff_ffrtmpcrypt_protocol; extern const URLProtocol ff_ffrtmphttp_protocol; extern const URLProtocol ff_file_protocol; extern const URLProtocol ff_ftp_protocol; extern const URLProtocol ff_gopher_protocol; extern const URLProtocol ff_hls_protocol; extern const URLProtocol ff_http_protocol; extern const URLProtocol ff_httpproxy_protocol; extern const URLProtocol ff_https_protocol; extern const URLProtocol ff_icecast_protocol; extern const URLProtocol ff_ijkhttphook_protocol; extern const URLProtocol ff_ijklongurl_protocol; extern const URLProtocol ff_ijkmediadatasource_protocol; extern const URLProtocol ff_ijksegment_protocol; extern const URLProtocol ff_ijktcphook_protocol; extern const URLProtocol ff_ijkio_protocol; extern const URLProtocol ff_mmsh_protocol; extern const URLProtocol ff_mmst_protocol; extern const URLProtocol ff_md5_protocol; extern const URLProtocol ff_pipe_protocol; extern const URLProtocol ff_prompeg_protocol; extern const URLProtocol ff_rtmp_protocol; extern const URLProtocol ff_rtmpe_protocol; extern const URLProtocol ff_rtmps_protocol; extern const URLProtocol ff_rtmpt_protocol; extern const URLProtocol ff_rtmpte_protocol; extern const URLProtocol ff_rtmpts_protocol; extern const URLProtocol ff_rtp_protocol; extern const URLProtocol ff_sctp_protocol; extern const URLProtocol ff_srtp_protocol; extern const URLProtocol ff_subfile_protocol; extern const URLProtocol ff_tee_protocol; extern const URLProtocol ff_tcp_protocol; extern const URLProtocol ff_tls_gnutls_protocol; extern const URLProtocol ff_tls_schannel_protocol; extern const URLProtocol ff_tls_securetransport_protocol; extern const URLProtocol ff_tls_openssl_protocol; extern const URLProtocol ff_udp_protocol; extern const URLProtocol ff_udplite_protocol; extern const URLProtocol ff_unix_protocol; extern const URLProtocol ff_librtmp_protocol; extern const URLProtocol ff_librtmpe_protocol; extern const URLProtocol ff_librtmps_protocol; extern const URLProtocol ff_librtmpt_protocol; extern const URLProtocol ff_librtmpte_protocol; extern const URLProtocol ff_libssh_protocol; extern const URLProtocol ff_libsmbclient_protocol;
read_header
read_header()
也是用於獲得匹配函式的函式指標,不同的封裝格式包含不同的實現函式
AVInputFormat ff_mov_demuxer = { .name= "mov,mp4,m4a,3gp,3g2,mj2", .long_name= NULL_IF_CONFIG_SMALL("QuickTime / MOV"), .priv_class= &mov_class, .priv_data_size = sizeof(MOVContext), .extensions= "mov,mp4,m4a,3gp,3g2,mj2", .read_probe= mov_probe, .read_header= mov_read_header, .read_packet= mov_read_packet, .read_close= mov_read_close, .read_seek= mov_read_seek, .flags= AVFMT_NO_BYTE_SEEK, };
read_header()
指向了mov_read_header()
函式
static int mov_read_header(AVFormatContext *s) { MOVContext *mov = s->priv_data; AVIOContext *pb = s->pb; int j, err; MOVAtom atom = { AV_RL32("root") }; int i; if (mov->decryption_key_len != 0 && mov->decryption_key_len != AES_CTR_KEY_SIZE) { av_log(s, AV_LOG_ERROR, "Invalid decryption key len %d expected %d\n", mov->decryption_key_len, AES_CTR_KEY_SIZE); return AVERROR(EINVAL); } mov->fc = s; mov->trak_index = -1; /* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */ if (pb->seekable & AVIO_SEEKABLE_NORMAL) atom.size = avio_size(pb); else atom.size = INT64_MAX; /* check MOV header */ do { if (mov->moov_retry) avio_seek(pb, 0, SEEK_SET); if ((err = mov_read_default(mov, pb, atom)) < 0) { av_log(s, AV_LOG_ERROR, "error reading header\n"); mov_read_close(s); return err; } } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++); if (!mov->found_moov) { av_log(s, AV_LOG_ERROR, "moov atom not found\n"); mov_read_close(s); return AVERROR_INVALIDDATA; } av_log(mov->fc, AV_LOG_TRACE, "on_parse_exit_offset=%"PRId64"\n", avio_tell(pb)); if (pb->seekable & AVIO_SEEKABLE_NORMAL) { if (mov->nb_chapter_tracks > 0 && !mov->ignore_chapters) mov_read_chapters(s); for (i = 0; i < s->nb_streams; i++) if (s->streams[i]->codecpar->codec_tag == AV_RL32("tmcd")) { mov_read_timecode_track(s, s->streams[i]); } else if (s->streams[i]->codecpar->codec_tag == AV_RL32("rtmd")) { mov_read_rtmd_track(s, s->streams[i]); } } /* copy timecode metadata from tmcd tracks to the related video streams */ for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; if (sc->timecode_track > 0) { AVDictionaryEntry *tcr; int tmcd_st_id = -1; for (j = 0; j < s->nb_streams; j++) if (s->streams[j]->id == sc->timecode_track) tmcd_st_id = j; if (tmcd_st_id < 0 || tmcd_st_id == i) continue; tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", NULL, 0); if (tcr) av_dict_set(&st->metadata, "timecode", tcr->value, 0); } } export_orphan_timecode(s); for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; fix_timescale(mov, sc); if(st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && st->codecpar->codec_id == AV_CODEC_ID_AAC) { st->skip_samples = sc->start_pad; } if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sc->nb_frames_for_fps > 0 && sc->duration_for_fps > 0) av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den, sc->time_scale*(int64_t)sc->nb_frames_for_fps, sc->duration_for_fps, INT_MAX); if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { if (st->codecpar->width <= 0 || st->codecpar->height <= 0) { st->codecpar->width= sc->width; st->codecpar->height = sc->height; } if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE) { if ((err = mov_rewrite_dvd_sub_extradata(st)) < 0) return err; } } if (mov->handbrake_version && mov->handbrake_version <= 1000000*0 + 1000*10 + 2 &&// 0.10.2 st->codecpar->codec_id == AV_CODEC_ID_MP3 ) { av_log(s, AV_LOG_VERBOSE, "Forcing full parsing for mp3 stream\n"); st->need_parsing = AVSTREAM_PARSE_FULL; } } if (mov->trex_data) { for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; if (st->duration > 0) { if (sc->data_size > INT64_MAX / sc->time_scale / 8) { av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n", sc->data_size, sc->time_scale); mov_read_close(s); return AVERROR_INVALIDDATA; } st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale / st->duration; } } } if (mov->use_mfra_for > 0) { for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; if (sc->duration_for_fps > 0) { if (sc->data_size > INT64_MAX / sc->time_scale / 8) { av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n", sc->data_size, sc->time_scale); mov_read_close(s); return AVERROR_INVALIDDATA; } st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale / sc->duration_for_fps; } } } for (i = 0; i < mov->bitrates_count && i < s->nb_streams; i++) { if (mov->bitrates[i]) { s->streams[i]->codecpar->bit_rate = mov->bitrates[i]; } } ff_rfps_calculate(s); for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; switch (st->codecpar->codec_type) { case AVMEDIA_TYPE_AUDIO: err = ff_replaygain_export(st, s->metadata); if (err < 0) { mov_read_close(s); return err; } break; case AVMEDIA_TYPE_VIDEO: if (sc->display_matrix) { err = av_stream_add_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, (uint8_t*)sc->display_matrix, sizeof(int32_t) * 9); if (err < 0) return err; sc->display_matrix = NULL; } if (sc->stereo3d) { err = av_stream_add_side_data(st, AV_PKT_DATA_STEREO3D, (uint8_t *)sc->stereo3d, sizeof(*sc->stereo3d)); if (err < 0) return err; sc->stereo3d = NULL; } if (sc->spherical) { err = av_stream_add_side_data(st, AV_PKT_DATA_SPHERICAL, (uint8_t *)sc->spherical, sc->spherical_size); if (err < 0) return err; sc->spherical = NULL; } if (sc->mastering) { err = av_stream_add_side_data(st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, (uint8_t *)sc->mastering, sizeof(*sc->mastering)); if (err < 0) return err; sc->mastering = NULL; } if (sc->coll) { err = av_stream_add_side_data(st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, (uint8_t *)sc->coll, sc->coll_size); if (err < 0) return err; sc->coll = NULL; } break; } } ff_configure_buffers_for_index(s, AV_TIME_BASE); for (i = 0; i < mov->fragment_index_count; i++) { MOVFragmentIndex *idx = mov->fragment_index_data[i]; for (j = 0; j < idx->item_count; j++) if (idx->items[j].moof_offset <= mov->fragment.moof_offset) idx->items[j].headers_read = 1; } return 0; }