当前位置: 首页 > news >正文

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(一)

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_AVDEVICEavdevice_register_all();
#endifavformat_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;
#elseav_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endifif (borderless)flags |= SDL_WINDOW_BORDERLESS;elseflags |= SDL_WINDOW_RESIZABLE;#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITORSDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endifwindow = 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的框架,学习思路是追踪音视频数据的流动过程。

  1. 原始数据通过什么模块获取?
  2. 如何通过原始数据匹配到对应的demuxer ,如: ts, mp4, mov, avi等?
  3. 如何通过demuxer后的audio/video数据匹配到对应的decoder, 如:h264, vp9, aac等?
  4. 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 threadstream_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);elseav_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的大致脉络已经摸清楚了,具体看下图:
ffplay_thread
结构关系图:
ff_state
后面的文章继续深入分析demuxer和decoder。

http://www.lryc.cn/news/613.html

相关文章:

  • C++蓝桥杯 基础练习,高精度加法,输入两个整数a和b,输出这两个整数的和。a和b都不超过100位。
  • MySQL面试题:SQL语句的基本语法
  • Fluid-数据编排能力原理解析
  • 并发线程、锁、ThreadLocal
  • CMMI-结项管理
  • 网络通信协议是什么?
  • 阶段5:Java分布式与微服务实战
  • 我的创作纪念日
  • Qml学习——动态加载控件
  • 设计模式之职责链模式
  • MySQL入门篇-MySQL 8.0 延迟复制
  • FPGA时序约束与分析 --- 实例教程(1)
  • go深拷贝和浅拷贝
  • linux网络系统层面的配置、管理及操作命令汇总
  • R数据分析:孟德尔随机化中介的原理和实操
  • 【C++】 类和对象 (下)
  • asp获取毫秒时间戳的方法 asp获取13位时间戳的方案
  • Python基础篇(十五)-- Python程序接入MySQL数据库
  • 程序员不得不知道的 API 接口常识
  • 【项目精选】基于Java的银行排号系统的设计与实现
  • 前端 基于 vue-simple-uploader 实现大文件断点续传和分片上传
  • 解决报错: ERR! code 128npm ERR! An unknown git error occurred
  • 聊城高新技术企业认定7项需要注意的问题 山东同邦科技分享
  • 菊乐食品更新IPO招股书:收入依赖单一地区,规模不及认养一头牛
  • Elasticsearch安装IK分词器、配置自定义分词词库
  • Linux嵌入式开发——shell脚本
  • CV【5】:Layer normalization
  • 跳跃游戏 II 解析
  • 易基因|猪肠道组织的表观基因组功能注释增强对复杂性状和人类疾病的生物学解释:Nature子刊
  • 01- NumPy 数据库 (机器学习)