ffplay入口
ffmpeg\fftools\ffplay.c
int main(int argc, char **argv)
{
/*******************start 动态库加载/网络初始化等**************/
int flags;
VideoState *is;
init_dynload();
av_log_set_flags(AV_LOG_SKIP_REPEATED);
parse_loglevel(argc, argv, options);
/* register all codecs, demux and protocols */
#if CONFIG_AVDEVICE
avdevice_register_all();
#endif
avformat_network_init();
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
show_banner(argc, argv, options);
parse_options(NULL, argc, argv, options, opt_input_file);
if (!input_filename) {
show_usage();
av_log(NULL, AV_LOG_FATAL, "An input file must be specified\n");
av_log(NULL, AV_LOG_FATAL,
"Use -h to get full help or, even better, run 'man %s'\n", program_name);
exit(1);
}
/*******************end动态库加载/网络初始化等**************/
/*******************start 视频帧显示窗口初始化*******************/
if (display_disable) {
video_disable = 1;
}
flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
if (audio_disable)
flags &= ~SDL_INIT_AUDIO;
else {
/* Try to work around an occasional ALSA buffer underflow issue when the
* period size is NPOT due to ALSA resampling by forcing the buffer size. */
if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"))
SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);
}
if (display_disable)
flags &= ~SDL_INIT_VIDEO;
if (SDL_Init (flags)) {
av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());
av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n");
exit(1);
}
SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);
SDL_EventState(SDL_USEREVENT, SDL_IGNORE);
if (!display_disable) {
int flags = SDL_WINDOW_HIDDEN;
if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)
flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
av_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endif
if (borderless)
flags |= SDL_WINDOW_BORDERLESS;
else
flags |= SDL_WINDOW_RESIZABLE;
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
if (window) {
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer) {
av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError());
renderer = SDL_CreateRenderer(window, -1, 0);
}
if (renderer) {
if (!SDL_GetRendererInfo(renderer, &renderer_info))
av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", renderer_info.name);
}
}
if (!window || !renderer || !renderer_info.num_texture_formats) {
av_log(NULL, AV_LOG_FATAL, "Failed to create window or renderer: %s", SDL_GetError());
do_exit(NULL);
}
}
/*******************end 视频帧显示窗口初始化*******************/
/******************* 下载数据/demux/decoder*******************/
is = stream_open(input_filename, file_iformat);
if (!is) {
av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");
do_exit(NULL);
}
event_loop(is);
return 0;
}
从上面main函数入口来看,main中大部分是一些init工作,而ffmpeg的核心工作demux/decoder的工作都封装在stream_open中了。
想要摸清ffmpeg的框架,学习思路是追踪音视频数据的流动过程。
- 原始数据通过什么模块获取?
- 如何通过原始数据匹配到对应的demuxer ,如: ts, mp4, mov, avi等?
- 如何通过demuxer后的audio/video数据匹配到对应的decoder, 如:h264, vp9, aac等?
- ffmpeg中数据流转的驱动模型是什么样的?也就是数据是从什么地方驱动开始下载/demux/decode ?
static VideoState *stream_open(const char *filename,
const AVInputFormat *iformat)
{
VideoState *is = av_mallocz(sizeof(VideoState));
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->filename = av_strdup(filename);
is->iformat = iformat;
is->ytop = 0;
is->xleft = 0;
is->continue_read_thread = SDL_CreateCond();
/* start video display */
frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1);
frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0);
frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1);
packet_queue_init(&is->videoq);
packet_queue_init(&is->audioq);
packet_queue_init(&is->subtitleq);
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;
startup_volume = av_clip(startup_volume, 0, 100);
startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
is->audio_volume = startup_volume;
is->muted = 0;
is->av_sync_type = av_sync_type;
is->read_tid = SDL_CreateThread(read_thread, "read_thread", is);
if (!is->read_tid) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());
fail:
stream_close(is);
return NULL;
return is;
}
从上面的代码看,stream_open主要是用于初始化VideoState这个结构体,初始化audio/video/subtitile解码后数据的buffer queque。结构体的具体细节先暂不深入。
另外在stream_open中创建了一个thread: read_thread, 看来干活的是这个了。。。
static int read_thread(void *arg)
{
VideoState *is = arg;
AVPacket *pkt = av_packet_alloc();
AVFormatContext *ic = avformat_alloc_context();
//启动数据下载模块,demux模块
avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
is->ic = ic;
av_format_inject_global_side_data(ic);
if (find_stream_info) {
AVDictionary **opts = setup_find_stream_info_opts(ic, codec_opts);
avformat_find_stream_info(ic, opts);
}
if (!video_disable)
st_index[AVMEDIA_TYPE_VIDEO] =
av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
if (!audio_disable)
st_index[AVMEDIA_TYPE_AUDIO] =
av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
st_index[AVMEDIA_TYPE_AUDIO],
st_index[AVMEDIA_TYPE_VIDEO],
NULL, 0);
if (!video_disable && !subtitle_disable)
st_index[AVMEDIA_TYPE_SUBTITLE] =
av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
st_index[AVMEDIA_TYPE_SUBTITLE],
(st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
st_index[AVMEDIA_TYPE_AUDIO] :
st_index[AVMEDIA_TYPE_VIDEO]),
NULL, 0);
// 启动decoder thread
stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]);
for (;;) {
if (is->abort_request)
break;
if (is->paused != is->last_paused) {
is->last_paused = is->paused;
if (is->paused)
is->read_pause_return = av_read_pause(ic);
else
av_read_play(ic);
}
//读取demux后的数据,并存入audio/video/subtitle的buffer queue中
av_read_frame(ic, pkt);
/* check if packet is in play range specified by user, then queue, otherwise discard */
stream_start_time = ic->streams[pkt->stream_index]->start_time;
pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
pkt_in_play_range = duration == AV_NOPTS_VALUE ||
(pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
av_q2d(ic->streams[pkt->stream_index]->time_base) -
(double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
<= ((double)duration / 1000000);
if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
packet_queue_put(&is->audioq, pkt);
} else if (pkt->stream_index == is->video_stream && pkt_in_play_range
&& !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
packet_queue_put(&is->videoq, pkt);
} else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
packet_queue_put(&is->subtitleq, pkt);
} else {
av_packet_unref(pkt);
}
}
..............;
return 0;
}
从上面read_thread代码看,主要是通过avformat_open_input(…)启动数据下载和demux,再通过stream_component_open(…) 启动audio/video/subtitle的decoder。最后启动死循环通过av_read_frame(…) 读取demux出来的audio/video/subtitle存入对应的buffer queue供decoder使用。
下面再看看decoder是如何消耗数据的?以及decoder完成的数据缓存在什么地方?
static int stream_component_open(VideoState *is, int stream_index)
{
AVFormatContext *ic = is->ic;
const AVCodec *codec;
const char *forced_codec_name = NULL;
AVDictionary *opts = NULL;
const AVDictionaryEntry *t = NULL;
int sample_rate;
AVChannelLayout ch_layout = { 0 };
int ret = 0;
int stream_lowres = lowres;
AVCodecContext *avctx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
const AVCodec * codec = avcodec_find_decoder(avctx->codec_id);
avctx->codec_id = codec->id;
AVDictionary *opts = filter_codec_opts(codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);
avcodec_open2(avctx, codec, &opts));
is->eof = 0;
ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
switch (avctx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
/* prepare audio output */
audio_open(is, &ch_layout, sample_rate, &is->audio_tgt))‘
decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread));
decoder_start(&is->auddec, audio_thread, "audio_decoder", is));
static SDL_AudioDeviceID audio_dev;
SDL_PauseAudioDevice(audio_dev, 0);
break;
case AVMEDIA_TYPE_VIDEO:
is->video_stream = stream_index;
is->video_st = ic->streams[stream_index];
decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread));
decoder_start(&is->viddec, video_thread, "video_decoder", is));
is->queue_attachments_req = 1;
break;
case AVMEDIA_TYPE_SUBTITLE:
is->subtitle_stream = stream_index;
is->subtitle_st = ic->streams[stream_index];
decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread));
decoder_start(&is->subdec, subtitle_thread, "subtitle_decoder", is))
break;
default:
break;
}
....................;
return ret;
}
ffplay内部结构图
通过stream_component_open函数的代码可以看出,先初始化decoder,再创建thread。
至此, ffplay的大致脉络已经摸清楚了,具体看下图:
结构关系图:
后面的文章继续深入分析demuxer和decoder。