Ffplay源码Read_Thread解读之一
前言:
大家好,源码我是解读小涂,今天继续给大家分享ffplay播放器里面的源码源码解读,今天原本想和大家一起解读一下下面这个三个线程函数:
在这个框架流程图,解读我忘记了介绍read_thread这块,源码所以,解读今天主要核心就是源码解读read_thread源码!
一、从Ffplay.c源码main入口开始:
我们首先拿到代码,解读打开Ffplay.c源码文件,源码然后找到main入口,解读接下来,源码我会简单介绍一下里面的解读一些操作,当然这里是源码挑重点介绍了,更多细节大家可以下载源码,解读详细解读:
上面的网站模板注解大概是播放器所做的工作,下面我就stream_open接口做详细的介绍,event_loop这个接口我做一个简单的说明:
event_loop这个接口主要是响应gui上的操作,比如你键盘操作了啥,会响应,就会调用它:
下面开始解读stream_open这个接口:
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;//初始化播放窗口y的起始坐标为0 is->xleft = 0;////初始化播放窗口x的起始坐 /* 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; //初始化数据包队列 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(): %s\n", SDL_GetError()); goto fail; } //初始化时钟, 时钟序列->queue_serial,实际上指向的是is->videoq.serial 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 0\n", startup_volume); if (startup_volume > 100) av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100\n", 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;// =1静音,=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; }通过上面源码解读,我们找到了read_thread线程:
同时我们通过这个接口的源码解读,我们大概知道,在进行视频播放的时候,我们做了哪些工作,比如说:帧队列的初始化,数据包队列的初始化;这也是为打开媒体文件,然后把数据送进来,亿华云计算进行依次操作;当然这里没有编码部分哈,播放器这里不涉及到编码,编码主要是采集原始音视频数据的时候,进行编码压缩,以减少内存的占用,不然搞容器里面占用内存太大了!
接着还进行视频、音频、字幕等时钟初始化,以及音频初始化音量大小,这里音量初始化为100的大小:
以及音视频同步类型,默认我们这里初始化为以音频为缺省值,关于音视频同步类型有三种:
/** *音视频同步方式,缺省以音频为基准 */ enum { AV_SYNC_AUDIO_MASTER, // 以音频为基准 AV_SYNC_VIDEO_MASTER, // 以视频为基准 AV_SYNC_EXTERNAL_CLOCK, // 以外部时钟为基准,synchronize to an external clock */ };最后,在进行上面的相关初始化操作,我们就可以开始进行执行read_thread操作了,也就是播放器开始进行播放读取数据,进行真正的操作了!
最后这里我提一下,结构体VideoState,你可以把它看做是音视频管理大总管,通过源码,你也发现了很多操作初始化,都跟这个结构体成员有关,所以这个结构体里面的内容,大家务必要了解清楚:
typedef struct VideoState { SDL_Thread *read_tid; // 读线程句柄 AVInputFormat *iformat; // 指向demuxer int abort_request; // =1时请求退出播放 int force_refresh; // =1时需要刷新画面,请求立即刷新画面的服务器托管意思 int paused; // =1时暂停,=0时播放 int last_paused; // 暂存“暂停”/“播放”状态 int queue_attachments_req; int seek_req; // 标识一次seek请求 int seek_flags; // seek标志,诸如AVSEEK_FLAG_BYTE等 int64_t seek_pos; // 请求seek的目标位置(当前位置+增量) int64_t seek_rel; // 本次seek的位置增量 int read_pause_return; AVFormatContext *ic; // iformat的上下文 int realtime; // =1为实时流 Clock audclk; // 音频时钟 Clock vidclk; // 视频时钟 Clock extclk; // 外部时钟 FrameQueue pictq; // 视频Frame队列 FrameQueue subpq; // 字幕Frame队列 FrameQueue sampq; // 采样Frame队列 Decoder auddec; // 音频解码器 Decoder viddec; // 视频解码器 Decoder subdec; // 字幕解码器 int audio_stream ; // 音频流索引 int av_sync_type; // 音视频同步类型, 默认audio master double audio_clock; // 当前音频帧的PTS+当前帧Duration int audio_clock_serial; // 播放序列,seek可改变此值 // 以下4个参数 非audio master同步方式使用 double audio_diff_cum; // used for AV difference average computation double audio_diff_avg_coef; double audio_diff_threshold; int audio_diff_avg_count; // end AVStream *audio_st; // 音频流 PacketQueue audioq; // 音频packet队列 int audio_hw_buf_size; // SDL音频缓冲区的大小(字节为单位) // 指向待播放的一帧音频数据,指向的数据区将被拷入SDL音频缓冲区。若经过重采样则指向audio_buf1, // 否则指向frame中的音频 uint8_t *audio_buf; // 指向需要重采样的数据 uint8_t *audio_buf1; // 指向重采样后的数据 unsigned int audio_buf_size; // 待播放的一帧音频数据(audio_buf指向)的大小 unsigned int audio_buf1_size; // 申请到的音频缓冲区audio_buf1的实际尺寸 int audio_buf_index; // 更新拷贝位置 当前音频帧中已拷入SDL音频缓冲区 // 的位置索引(指向第一个待拷贝字节) // 当前音频帧中尚未拷入SDL音频缓冲区的数据量: // audio_buf_size = audio_buf_index + audio_write_buf_size int audio_write_buf_size; int audio_volume; // 音量 int muted; // =1静音,=0则正常 struct AudioParams audio_src; // 音频frame的参数 #if CONFIG_AVFILTER struct AudioParams audio_filter_src; #endif struct AudioParams audio_tgt; // SDL支持的音频参数,重采样转换:audio_src->audio_tgt struct SwrContext *swr_ctx; // 音频重采样context int frame_drops_early; // 丢弃视频packet计数 int frame_drops_late; // 丢弃视频frame计数 enum ShowMode { SHOW_MODE_NONE = -1, // 无显示 SHOW_MODE_VIDEO = 0, // 显示视频 SHOW_MODE_WAVES, // 显示波浪,音频 SHOW_MODE_RDFT, // 自适应滤波器 SHOW_MODE_NB } show_mode; // 音频波形显示使用 int16_t sample_array[SAMPLE_ARRAY_SIZE]; // 采样数组 int sample_array_index; // 采样索引 int last_i_start; // 上一开始 RDFTContext *rdft; // 自适应滤波器上下文 int rdft_bits; // 自使用比特率 FFTSample *rdft_data; // 快速傅里叶采样 int xpos; double last_vis_time; SDL_Texture *vis_texture; // 音频Texture SDL_Texture *sub_texture; // 字幕显示 SDL_Texture *vid_texture; // 视频显示 int subtitle_stream; // 字幕流索引 AVStream *subtitle_st; // 字幕流 PacketQueue subtitleq; // 字幕packet队列 double frame_timer; // 记录最后一帧播放的时刻 double frame_last_returned_time; // 上一次返回时间 double frame_last_filter_delay; // 上一个过滤器延时 int video_stream; // 视频流索引 AVStream *video_st; // 视频流 PacketQueue videoq; // 视频队列 double max_frame_duration; // 一帧最大间隔. above this, we consider the jump a timestamp discontinuity struct SwsContext *img_convert_ctx; // 视频尺寸格式变换 struct SwsContext *sub_convert_ctx; // 字幕尺寸格式变换 int eof; // 是否读取结束 char *filename; // 文件名 int width, height, xleft, ytop; // 宽、高,x起始坐标,y起始坐标 int step; // =1 步进播放模式, =0 其他模式 #if CONFIG_AVFILTER int vfilter_idx; AVFilterContext *in_video_filter; // the first filter in the video chain AVFilterContext *out_video_filter; // the last filter in the video chain AVFilterContext *in_audio_filter; // the first filter in the audio chain AVFilterContext *out_audio_filter; // the last filter in the audio chain AVFilterGraph *agraph; // audio filter graph #endif // 保留最近的相应audio、video、subtitle流的steam index int last_video_stream, last_audio_stream, last_subtitle_stream; SDL_cond *continue_read_thread; // 当读取数据队列满了后进入休眠时,可以通过该condition唤醒读线程 } VideoState;好了,由于解读源码代码量比较多,所以本期文章暂时就分享到这里,现在我们对播放器的流程路线应该有了一个非常清楚的一个了解了。
二、总结:
下期我们再继续read_thread线程的源码解读,奥利给!后面的文章会用gdb来打断点,然后查看bt(栈帧),来查看一个某个重要的接口被调用路径,这样对读代码非常有帮助!
相关参考:
https://www.cnblogs.com/leisure_chn/p/10301831.html