本文參考雷神博文整理:https://blog.csdn.net/leixiaohua1020。
init_input它的主要工作就是打開輸入的視頻數據并且探測視頻的格式。該函數的定義位于libavformat\utils.c,整體調用結構如下(注:目前分析的版本是4.0.2,圖中函數調用名稱可能跟舊版本不對應)。
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;
//當使用了自定義的AVIOContext的時候(AVFormatContext中的AVIOContext不為空,即
//s->pb!=NULL),如果指定了AVInputFormat就直接返回,如果沒有指定就
//調用av_probe_input_buffer2()推測AVInputFormat。這一情況出現的不算很多,但是當我們
//從內存中讀取數據的時候(需要初始化自定義的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;
}
//在更一般的情況下,如果已經指定了AVInputFormat,就直接返回;如果沒有
//指定AVInputFormat,就調用av_probe_input_format(NULL,…)根據文件路徑判斷文件格式。
//這里特意把av_probe_input_format()的第1個參數寫成“NULL”,是為了強調這個時候實際上
//并沒有給函數提供輸入數據,此時僅僅通過文件路徑推測AVInputFormat。
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;
//如果發現通過文件路徑判斷不出來文件格式,那么就需要打開文件探測文件格式了,這個
//時候會首先調用io_open打開文件,io_open在avformat_alloc_context()----->avformat_get_context_defaults(ic)----->s->io_open = io_open_default賦值。
//然后調用av_probe_input_buffer2()推測AVInputFormat。
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);
}
下面分析一下av_probe_input_format2(),s->io_open(),av_probe_input_buffer2()這幾個函數
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;
}
該函數用于根據輸入數據查找合適的AVInputFormat。參數含義如下所示:
pd:存儲輸入數據信息的AVProbeData結構體。
is_opened:文件是否打開。
score_max:判決AVInputFormat的門限值。只有某格式判決分數大于該門限值的時候,函數才會返回該封裝格式,否則返回NULL。
該函數中涉及到一個結構體AVProbeData,用于存儲輸入文件的一些信息,它的定義如下所示。
/**
* 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;
av_probe_input_format3()
av_probe_input_format3()是一個API函數,聲明位于libavformat\avformat.h,如下所示。
/**
* Guess the file format.
*
* @param is_opened Whether the file is already opened; determines whether
* demuxers with or without AVFMT_NOFILE are probed.
* @param score_ret The score of the best detection.
*/
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret);
從函數聲明中可以看出,av_probe_input_format3()和av_probe_input_format2()的區別是函數的第3個參數不同:av_probe_input_format2()是一個分數的門限值,而av_probe_input_format3()是一個探測后的最匹配的格式的分數值。
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
int *score_ret)
{
AVProbeData lpd = *pd;
const AVInputFormat *fmt1 = NULL;
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;
//id3相關的,這里先不管。MP3文件才有的
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;
}
while ((fmt1 = av_demuxer_iterate(&i))) {
if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
continue;
score = 0;
//如果AVInputFormat中包含read_probe(),就調用read_probe()函數獲取匹配分數(這一方法
//如果結果匹配的話,一般會獲得AVPROBE_SCORE_MAX的分值,即100分)
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) {
//如果不包含該函數,就使用av_match_ext()函數比較輸入媒體的擴展名和AVInputFormat的
//擴展名是否匹配,如果匹配的話,設定匹配分數
//為AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值為50,即50分)。
if (av_match_ext(lpd.filename, fmt1->extensions))
score = AVPROBE_SCORE_EXTENSION;
}
//使用av_match_name()比較輸入媒體的mime_type和AVInputFormat的mime_type,如果匹配
//的話,設定匹配分數為AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值為75,
//即75分)
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 = (AVInputFormat*)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;
}
av_probe_input_format3()根據輸入數據查找合適的AVInputFormat。輸入的數據位于AVProbeData中。前文已經提到過,其中filename是文件路徑, buf存儲用于推測AVInputFormat的媒體數據,最后還有個mime_type保存媒體的類型。其中buf可以為空,但是其后面無論如何都需要填充AVPROBE_PADDING_SIZE個0(AVPROBE_PADDING_SIZE取值為32,即32個0)。
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;
該函數最主要的部分是一個循環。該循環調用av_demuxer_iterate()遍歷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。
上述過程中涉及到以下幾個知識點:
AVInputFormat->read_probe()
AVInputFormat中包含read_probe()是用于獲得匹配函數的函數指針,不同的封裝格式包含不同的實現函數。位于libavformat\目錄下的flvdec.c,movdec.c等。
此函數作用一般是從文件中獲取文件頭的數據來匹配。例如文件頭包含FLV或者MOOV這些這段表明是flv文件或者mov文件等。
av_match_ext()
int av_match_ext(const char *filename, const char *extensions)
{
const char *ext;
if (!filename)
return 0;
//該函數返回 str 中最后一次出現字符 c 的位置。如果未找到該值,則函數返回一個空指針。
//例如返回“.mov”
ext = strrchr(filename, '.');
//av_match_name用于比較兩個格式的名稱。簡單地說就是比較字符串。注意該函數的字符串是
//不區分大小寫的,字符都轉換為小寫進行比較。
if (ext)
return av_match_name(ext + 1, extensions);
return 0;
}
av_match_name()
int av_match_name(const char *name, const char *names)
{
const char *p;
int len, namelen;
if (!name || !names)
return 0;
//strlen()用來計算指定的字符串s 的長度,不包括結束字符"\0"
namelen = strlen(name);
while (*names) {
int negate = '-' == *names;
//查找字符串names中首次出現','字符的位置
p = strchr(names, ',');
if (!p)
p = names + strlen(names);
names += negate;
len = FFMAX(p - names, namelen);
//比較name跟names的len長度,先轉成小寫來比較,相等的話返回0
//整個函數return !negate;
if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))
return !negate;
names = p + (*p == ',');
}
return 0;
}
上述函數還有一點需要注意,其中使用了一個while()循環,用于搜索“,”。這是因為FFmpeg中有些格式是對應多種格式名稱的,例如MKV格式的解復用器(Demuxer)的定義如下。
AVInputFormat ff_matroska_demuxer = {
.name = "matroska,webm",
.long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
.extensions = "mkv,mk3d,mka,mks",
.priv_data_size = sizeof(MatroskaDemuxContext),
.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()函數對于這樣的字符串,會把它按照“,”截斷成一個個的名稱,然后一一進行比較。
io_open->io_open_default
在avformat_alloc_context()----->avformat_get_context_defaults(ic)----->s->io_open = io_open_default賦值.此函數后續再看,打開本地文件或者URL基本都是進入這里識別出AVInputFormat 跟AVIOContext。
ffmpeg源碼分析4-io_open_default
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 filename the filename 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, AVInputFormat **fmt,
const char *filename, void *logctx,
unsigned int offset, unsigned int max_probe_size);
av_probe_input_buffer2()參數的含義如下所示:
pb:用于讀取數據的AVIOContext。
fmt:輸出推測出來的AVInputFormat。
filename:輸入媒體的路徑。
logctx:日志(沒有研究過)。
offset:開始推測AVInputFormat的偏移量。
max_probe_size:用于推測格式的媒體數據的最大值。
返回推測后的得到的AVInputFormat的匹配分數。
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;
}
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,則會退出循環并且返回錯誤信息。
這個函數的用法是從內存中讀取數據并偵測數據的格式,例如Winpcap抓取網絡上的RTP包,打算直接送給ffmpeg進行解碼。一直沒能找到合適的方法。因為抓取的數據包是存在內存中的,所以無法傳遞給avformat_open_input()函數其路徑(根本沒有路徑= =)。當然也可以將抓取的數據報存成文件,然后用ffmpeg打開這個文件,但是這樣的話,程序的就太難控制了。
后來經過分析ffmpeg的源代碼,發現其竟然是可以從內存中讀取數據的,代碼很簡單,如下所示:
FILE *fp_open;
//讀取數據的回調函數-------------------------
//AVIOContext使用的回調函數!
//注意:返回值是讀取的字節數
//手動初始化AVIOContext只需要兩個東西:內容來源的buffer,和讀取這個Buffer到FFmpeg中的函數
//回調函數,功能就是:把buf_size字節數據送入buf即可
//第一個參數(void *opaque)一般情況下可以不用
int fill_iobuffer(void * opaque,uint8_t *buf, int bufsize){
if(!feof(fp_open)){
int true_size=fread(buf,1,buf_size,fp_open);
return true_size;
}else{
return -1;
}
}
int main(){
...
fp_open=fopen("test.h264","rb+");
AVFormatContext *ic = NULL;
ic = avformat_alloc_context();
unsigned char * iobuffer=(unsigned char *)av_malloc(32768);
AVIOContext *avio =avio_alloc_context(iobuffer, 32768,0,NULL,fill_iobuffer,NULL,NULL);
ic->pb=avio;
err = avformat_open_input(&ic, "nothing", NULL, NULL);
...//解碼
}
關鍵要在avformat_open_input()之前初始化一個AVIOContext,而且將原本的AVFormatContext的指針pb(AVIOContext類型)指向這個自行初始化AVIOContext。當自行指定了AVIOContext之后,avformat_open_input()里面的URL參數就不起作用了。示例代碼開辟了一塊空間iobuffer作為AVIOContext的緩存。
fill_iobuffer則是將數據讀取至iobuffer的回調函數。fill_iobuffer()形式(參數,返回值)是固定的,是一個回調函數,如下所示(只是個例子,具體怎么讀取數據可以自行設計)。示例中回調函數將文件中的內容通過fread()讀入內存。