1.框架分析
文章插图
ffplay.c是FFmpeg源码?带的播放器,调?FFmpeg和SDL API实现?个?常有?的播放器 。例如哔哩哔哩著名开源项?ijkplayer也是基于ffplay.c进??次开发 。ffplay实现了播放器的主体功能,掌握其原理对于我们独?开发播放器?常有帮助 。看看整体的框架,如下图:
从整体上,分这几个大的模块,数据读取,AVpacket缓存队列,音频解码,视频解码,AVframe缓存队列,声音和视频输出,音视频同步等模块组成 。
文章插图
首先进入stream_open,stream_open主要的工作是创建音视频解码前和后的数据缓存队列,初始化时钟,包括音频,视频,外部时钟等,初始化数据读取线程read_thread 。源码如下:
static VideoState *stream_open(const char *filename, AVInputFormat *iformat){VideoState *is;is = av_mallocz(sizeof(VideoState));if (!is)return NULL;is->filename = av_strdup(filename);if (!is->filename)goto fail;is->iformat = iformat;is->ytop= 0;is->xleft= 0;/* start video display *///初始化帧队列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;//初始化packet队列if (packet_queue_init(&is->videoq) < 0 ||packet_queue_init(&is->audioq) < 0 ||packet_queue_init(&is->subtitleq) < 0)goto fail;if (!(is->continue_read_thread = SDL_CreateCond())) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %sn", SDL_GetError());goto fail;}//初始化视频时钟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;if (startup_volume < 0)av_log(NULL, AV_LOG_WARNING, "-volume=%d < 0, setting to 0n", startup_volume);if (startup_volume > 100)av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100n", startup_volume);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(): %sn", SDL_GetError());fail:stream_close(is);return NULL;}return is;}
看看数据读取线程read_thread做了什么?
大致就是一个解封转过程,然后把解封装的packet放到packet队列 。分别调用avformat_alloc_context(分配解封装上下文),avforamt_open_input(打开文件或网络流,内存数据),avformat_find_stream_info(找到相关流信息),stream_component_open(打开音视频流),av_read_frame(解封转),packet_queue_put(放入到帧队列) 。源码如下,详细请看注释:
typedef struct MyAVPacketList {AVPacket pkt;struct MyAVPacketList *next;int serial;} MyAVPacketList;typedef struct PacketQueue {//链表MyAVPacketList *first_pkt, *last_pkt;//多少个packetint nb_packets;//每个packet大小int size;//每个packet时长int64_t duration;//请求标志int abort_request;int serial;//锁SDL_mutex *mutex;//条件变量SDL_cond *cond;} PacketQueue;
然后把解封装的数据,放到packet队列 。看看packet的封装结构,实际就是一个链表 。详细源码如下:
typedef struct MyAVPacketList {AVPacket pkt;struct MyAVPacketList *next;int serial;} MyAVPacketList;typedef struct PacketQueue {//链表MyAVPacketList *first_pkt, *last_pkt;//多少个packetint nb_packets;//每个packet大小int size;//每个packet时长int64_t duration;//请求标志int abort_request;int serial;//锁SDL_mutex *mutex;//条件变量SDL_cond *cond;} PacketQueue;
视频解码线程,然后就从video的packet 队列取数据,看看做了什么?大致就是使用packet_queue_get去取packet,然后发送解码,avcode_send_packet,avcode_receive_frame,并刷新解码后的frame队列 。详细看看源码,如下:可以看出ffplay.c还支持过滤器功能,功能还是很全 。
文章插图
文章插图
取数据 。
文章插图
可以看出音视频和subtitle的所调用的函数是不一样 。
推荐阅读
- 红茶蛋曲奇的做法,原味曲奇和抹茶曲奇的做法
- 雷达模拟:用python的pygame实现和代码分析
- 喝茶要有最基本的常识,喝茶的境界
- HDMI高清线的五个误区
- 如何选择一张合适的存储卡?
- 史记里面第一个皇帝是谁 史记记载的最后一个皇帝是谁
- 太监为什么娶妻子 古代太监娶老婆是干嘛的
- 了解机器学习的商业价值 机器预测逐渐取代人类做出的预测
- 鱿鱼仔和海兔子是一种东西吗 海兔和小鱿鱼的区别
- 如何实现一个优雅的Python的Json序列化库