最近看了一些ffmpeg源代码,记录一些笔记. 从ffplay.c的main函数开始阅读源码
int main(int argc, char **argv)
{
...
//注册codec,dmx和一些protocol
av_register_all();
...
//parse一些命令后信息,保存在options中,后面经常会用到这个
parse_options(NULL, argc, argv, options, opt_input_file);
//后面会专门分析这个函数
is = stream_open(input_filename, file_iformat);
...
}
1. av_register_all();
以 注册file协议为例: REGISTER_PROTOCOL(FILE, file);
#define REGISTER_PROTOCOL(X, x) \
{ \
extern URLProtocol ff_##x##_protocol; \
if (CONFIG_##X##_PROTOCOL) \
ffurl_register_protocol(&ff_##x##_protocol); \
}
展开之后就等于:
{
extern URLProtocol ff_file_protocol;
if (CONFIG_FILE_PROTOCOL)
ffurl_register_protocol(&ff_file_protocol);
}
ff_file_protocol这个global 变量位于libavformat/file.c
URLProtocol ff_file_protocol = {
.name = "file",
.url_open = file_open,
.url_read = file_read,
.url_write = file_write,
.url_seek = file_seek,
.url_close = file_close,
.url_get_file_handle = file_get_handle,
.url_check = file_check,
.url_delete = file_delete,
.url_move = file_move,
.priv_data_size = sizeof(FileContext),
.priv_data_class = &file_class,
.url_open_dir = file_open_dir,
.url_read_dir = file_read_dir,
.url_close_dir = file_close_dir,
};
2.stream_open
static VideoState *stream_open(const char *filename, AVInputFormat *iformat)
{
...
//alloc VideoState 结构体,并初始化
is = av_mallocz(sizeof(VideoState));
if (!is)
return NULL;
av_strlcpy(is->filename, filename, sizeof(is->filename));
is->iformat = iformat;
is->ytop = 0;
is->xleft = 0;
//初始化video/subtitle/audio frame queue
if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)
goto fail;
if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
goto fail;
if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
goto fail;
//初始化video/subtitle/audio packet queue
packet_queue_init(&is->videoq);
packet_queue_init(&is->audioq);
packet_queue_init(&is->subtitleq);
is->continue_read_thread = SDL_CreateCond();
//初始化video/subtitle/audio clock
init_clock(&is->vidclk, &is->videoq.serial);
init_clock(&is->audclk, &is->audioq.serial);
init_clock(&is->extclk, &is->extclk.serial);
is->audio_clock_serial = -1;
is->av_sync_type = av_sync_type;
//创建read_thread
is->read_tid = SDL_CreateThread(read_thread, is);
...
}
3. read_thread
static int read_thread(void *arg)
{
//最开始将所有stream id 设为无效,eof为0
memset(st_index, -1, sizeof(st_index));
is->last_video_stream = is->video_stream = -1;
is->last_audio_stream = is->audio_stream = -1;
is->last_subtitle_stream = is->subtitle_stream = -1;
is->eof = 0;
//alloc AVFormatContext,这是ffmpeg中最重要的结构体之一,
//后续的AVInputFormat,AVStream等重要的结构体都挂在这个结构体中
ic = avformat_alloc_context();
if (!ic) {
av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
ret = AVERROR(ENOMEM);
goto fail;
}
ic->interrupt_callback.callback = decode_interrupt_cb;
ic->interrupt_callback.opaque = is;
if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
scan_all_pmts_set = 1;
}
//重要的函数,接下来会重点分析
err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
if (err < 0) {
print_error(is->filename, err);
ret = -1;
goto fail;
}
...
}
4.avformat_open_input
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int ret = 0;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
//AVFormatContext前面已经分配过Memory,不用再alloc
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
//s->av_class也不等于NULL
/*
static const AVClass av_format_context_class = {
.class_name = "AVFormatContext",
.item_name = format_to_name,
.option = avformat_options,//可以查看到这里有很多option
.version = LIBAVUTIL_VERSION_INT,
.child_next = format_child_next,
.child_class_next = format_child_class_next,
.category = AV_CLASS_CATEGORY_MUXER,
.get_category = get_category,
};
*/
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);
}
//fmt是NULL
if (fmt)
s->iformat = fmt;
//将option保存到tmp中
if (options)
av_dict_copy(&tmp, *options, 0);
//s-pb是NULL
if (s->pb) // must be before any goto fail
s->flags |= AVFMT_FLAG_CUSTOM_IO;
//将tmp中的字典,保存在s结构体中
//ffmpeg很多结构体第一个成员就是AVClass类,其实就是修改AVClass的option.
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
//重要函数,打开播放的文件,并进行认档
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
s->probe_score = ret;
...
}
5.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;
//s-pb为NULL
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;
}
//s->iformat为NULL,会call av_probe_input_format2 去认档.
/*
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
int *score_ret)
{
...
fmt = NULL;
while ((fmt1 = av_iformat_next(fmt1))) {
//从传入的参数看,此时的is_opened是0,
这个地方只会认image2的格式,但是会拿到最后一个fmt,
之后由于在av_probe_input_format2 比较分数时候,
拿到的分数会小于25,av_probe_input_format2会返回NULL,
所以s->iformat还会是NULL. 后面demuxer认档,
在这儿不会被执行到,但是可以先分析一下,
后面is_opened为1时候,会call 到 read_probe认档.
if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
continue;
score = 0;
//在av_register_all时候,有注册各种demuxer,循环调用demuxer的read_probe.去认档
//有点疑惑,为什么不是100分就退出,而是让所有的demuxer都去认一遍
****************************
例如matroska格式,就会调用matroska_probe 去认档
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"
};
****************************
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)) {
if (nodat == 0) score = FFMAX(score, 1);
else if (nodat == 1) score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
else score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
}
}
//如果read_probe函数指针为NULL,就根据文件扩展名认档,扩展名match只有50分
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))
score = FFMAX(score, AVPROBE_SCORE_MIME);
if (score > score_max) {
score_max = score;
fmt = fmt1;
} else if (score == score_max)
fmt = NULL;
}
if (nodat == 1)
score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
*score_ret = score_max;
return fmt;
}
*/
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;
//重要函数1
if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags,
&s->interrupt_callback, options)) < 0)
return ret;
//在做完 av_probe_input_format2之后,iformat是NULL. 会调用av_probe_input_buffer2去认档
if (s->iformat)
return 0;
//重要函数2
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
6.重要函数1 avio_open2
int avio_open2(AVIOContext **s, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options)
{
URLContext *h;
int err;
err = ffurl_open(&h, filename, flags, int_cb, options);
if (err < 0)
return err;
err = ffio_fdopen(s, h);
if (err < 0) {
ffurl_close(h);
return err;
}
return 0;
}
int ffurl_open(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options)
{
int ret = ffurl_alloc(puc, filename, flags, int_cb);
if (ret < 0)
return ret;
if (options && (*puc)->prot->priv_data_class &&
(ret = av_opt_set_dict((*puc)->priv_data, options)) < 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;
}
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
URLProtocol *p = NULL;
//还记得在av_register_all中注册了protocols吧,就是放在这个link_list中
if (!first_protocol) {
av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. "
"Missing call to av_register_all()?\n");
}
//根据文件名,找到相应的协议,返回 URLProtocol 不再追了,还记得前面的 ff_file_protocol吧
p = url_find_protocol(filename);
if (p)
return url_alloc_for_protocol(puc, p, filename, flags, int_cb);//alloc协议
...
}
//alloc url协议
static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up,
const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
URLContext *uc;
int err;
#if CONFIG_NETWORK
if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
return AVERROR(EIO);
#endif
//从前面传递过来的flags是AVIO_FLAG_READ,
//如果flag是 AVIO_FLAG_READ,而protocol中read函数指针为NULL,则出错退出
//如果flag是 AVIO_FLAG_WRITE,而protocol中write函数指针为NULL,则出错退出
if ((flags & AVIO_FLAG_READ) && !up->url_read) {
av_log(NULL, AV_LOG_ERROR,
"Impossible to open the '%s' protocol for reading\n", up->name);
return AVERROR(EIO);
}
if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {
av_log(NULL, AV_LOG_ERROR,
"Impossible to open the '%s' protocol for writing\n", up->name);
return AVERROR(EIO);
}
//将fileName(包含协议name) 和URLContext一起分配,第一个bytes为fileName
uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
if (!uc) {
err = AVERROR(ENOMEM);
goto fail;
}
/*
const AVClass ffurl_context_class = {
.class_name = "URLContext",
.item_name = urlcontext_to_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
.child_next = urlcontext_child_next,
.child_class_next = urlcontext_child_class_next,
};
*/
uc->av_class = &ffurl_context_class;
uc->filename = (char *)&uc[1];//第一个bytes为fileName
strcpy(uc->filename, filename);//fileName copy到URLContext中
uc->prot = up;//如果是file,就是ff_file_protocol
uc->flags = flags;//Read
uc->is_streamed = 0; /* default = not streamed */
uc->max_packet_size = 0; /* default: stream file */
/* 如果是file协议
URLProtocol ff_file_protocol = {
.name = "file",
.url_open = file_open,
.url_read = file_read,
.url_write = file_write,
.url_seek = file_seek,
.url_close = file_close,
.url_get_file_handle = file_get_handle,
.url_check = file_check,
.url_delete = file_delete,
.url_move = file_move,
.priv_data_size = sizeof(FileContext),
.priv_data_class = &file_class,
.url_open_dir = file_open_dir,
.url_read_dir = file_read_dir,
.url_close_dir = file_close_dir,
};
*/
//.priv_data_size = sizeof(FileContext),
/*
typedef struct FileContext {
const AVClass *class;
int fd;
int trunc;
int blocksize;
#if HAVE_DIRENT_H
DIR *dir;
#endif
} FileContext;
*/
if (up->priv_data_size) {
//在urlcontext中分配memory保存FileConext内容
uc->priv_data = av_mallocz(up->priv_data_size);
if (!uc->priv_data) {
err = AVERROR(ENOMEM);
goto fail;
}
//.priv_data_class = &file_class,
/*
static const AVClass file_class = {
.class_name = "file",
.item_name = av_default_item_name,
.option = file_options,
.version = LIBAVUTIL_VERSION_INT,
};
*/
if (up->priv_data_class) {
int proto_len= strlen(up->name);//up-name:"file"
char *start = strchr(uc->filename, ',');
/*将file_class保存到FileContext的class中.
ffmpeg中经常遇到将一个结构体强制转化为结构体的第一个成员,
由于AVClass是第一个成员,是可以这样操作.*/
*(const AVClass **)uc->priv_data = up->priv_data_class;
//设置default值,只有truncate和blocksize,fd是在后面open时候,才写入的
/*
static const AVOption file_options[] = {
{ "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
{ "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
{ NULL }
};
*/
av_opt_set_defaults(uc->priv_data);
...
}
再返回到上一层:
int ffurl_open(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options)
{
//刚刚已分析
int ret = ffurl_alloc(puc, filename, flags, int_cb);
//如果最开始有tmp option (truncate和blocksize),会改写file_options中的一些值
if (options && (*puc)->prot->priv_data_class &&
(ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
goto fail;
//同时将options设置到ffurl_context_class 中的option中
if ((ret = av_opt_set_dict(*puc, options)) < 0)
goto fail;
//调用prot的connect
ret = ffurl_connect(*puc, options);
...
}
int ffurl_connect(URLContext *uc, AVDictionary **options)
{
/*如果是file,就call file_open
static int file_open(URLContext *h, const char *filename, int flags)
{
...
//先判断一些读写属性,
fd = avpriv_open(filename, access, 0666);
...
c->fd = fd;//保存fd
...
}
int avpriv_open(const char *filename, int flags, ...)
{
//很熟悉了吧
fd = open(filename, flags, mode);
return fd;
}
*/
int err =
uc->prot->url_open2 ? uc->prot->url_open2(uc,
uc->filename,
uc->flags,
options) :
uc->prot->url_open(uc, uc->filename, uc->flags);
if (err)
return err;
uc->is_connected = 1;
/* We must be careful here as ffurl_seek() could be slow,
* for example for http */
if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
uc->is_streamed = 1;
return 0;
}
//再回到 这个函数里的ffio_fdopen
int avio_open2(AVIOContext **s, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options)
{
URLContext *h;
int err;
err = ffurl_open(&h, filename, flags, int_cb, options);
if (err < 0)
return err;
err = ffio_fdopen(s, h);
if (err < 0) {
ffurl_close(h);
return err;
}
return 0;
}
int ffio_fdopen(AVIOContext **s, URLContext *h)
{
...
if (max_packet_size) {
buffer_size = max_packet_size; /* no need to bufferize more than one packet */
} else {
buffer_size = IO_BUFFER_SIZE;//max_packet_size应该没有指定,使用32K
}
buffer = av_malloc(buffer_size);
//分配AVIOContext,并给它赋值,后面就可以直接调用avio的read/write/seek去读/写/seek
/*
要注意一个细节:avio_alloc_context参数中的实参h(URLContext*类型),对应的形参opaque,被赋值给了s->opaque,后面还会利用到这个参数.
int ffio_init_context(AVIOContext *s,
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
...
s->opaque = opaque;
...
}
AVIOContext *avio_alloc_context(
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
AVIOContext *s = av_mallocz(sizeof(AVIOContext));
if (!s)
return NULL;
ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
read_packet, write_packet, seek);
return s;
}
*/
*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
(int (*)(void *, uint8_t *, int)) ffurl_read,
(int (*)(void *, uint8_t *, int)) ffurl_write,
(int64_t (*)(void *, int64_t, int)) ffurl_seek);
(*s)->direct = h->flags & AVIO_FLAG_DIRECT;
(*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
(*s)->max_packet_size = max_packet_size;
if(h->prot) {
(*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;
(*s)->read_seek = (int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;
}
/*
static const AVOption ff_avio_options[] = {
{ NULL },
};
const AVClass ff_avio_class = {
.class_name = "AVIOContext",
.item_name = av_default_item_name,
.version = LIBAVUTIL_VERSION_INT,
.option = ff_avio_options,
.child_next = ff_avio_child_next,
.child_class_next = ff_avio_child_class_next,
};
*/
(*s)->av_class = &ff_avio_class;
return 0;
}
7.重要函数2: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)
{
...
if (pb->av_class) {
/*pb->av_class就是刚刚说的ff_avio_class
const AVClass ff_avio_class = {
.class_name = "AVIOContext",
.item_name = av_default_item_name,
.version = LIBAVUTIL_VERSION_INT,
.option = ff_avio_options,
.child_next = ff_avio_child_next,
.child_class_next = ff_avio_child_class_next,
};
av_opt_get是先拿ff_avio_options 然而这个option中没有任何属性,
当然也找不到"mine_type",紧接着会去call
.child_next和.child_class_next 去找mine_type,
.child_next就是拿s->opaue的options也就是URLContext属性(前面有提到),
对于file, 也没有mine_type,但是http是有这个属性的.
.child_class_next的options也是空,
所以对于file, pd.mime_type还是NULL.
*/
uint8_t *mime_type_opt = NULL;
av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);
pd.mime_type = (const char *)mime_type_opt;
}
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))) {
...
/* Guess file format. */
//这个前面分析过,去call read_probe去认档 ,此处略.至此,文件认档过程结束
*fmt = av_probe_input_format2(&pd, 1, &score);
...
}
}