当前位置: 首页 > 科技观察

Ffplay源码Read_Thread解读之一

时间:2023-03-16 19:31:04 科技观察

前言:大家好,我是小兔。今天继续给大家分享ffplay播放器中的源码解读。今天想跟大家讲解一下以下三个线程函数:video_threadaudio_threadsubtitle_thread在这个框架流程图中,忘了介绍read_thread这块,所以今天主要核心就是解读read_thread源码!1、从Ffplay.c源码的主入口开始:我们首先拿到代码,打开Ffplay.c源码文件,然后找到主入口,接下来我简单介绍一下里面的一些操作,当然,这里是重点介绍,更多细节可以下载源码,详细解释:/*Calledfromthemain*/intmain(intargc,char**argv){intflags;VideoState*is;init_dynload();//InitializeFFmpeg,如所有编解码器,各种协议,解复用器等av_log_set_flags(AV_LOG_SKIP_REPEATED);parse_loglevel(argc,argv,options);/*registerallcodecs,demuxandprotocols*/#ifCONFIG_AVDEVICEavdevice_register_all();#endifavformat_network_init();init_opts();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,"输入文件必须规范化\n");av_log(NULL,AV_LOG_FATAL,"使用-htogetfullhelpor,evenbetter,run'man%s'\n",program_name);exit(1);}//是否显示视频if(display_disable){video_disable=1;}//SDL初始化,会调用SDL_init()来进行初始化flags=SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER;//是否运行音频if(audio_disable)flags&=~SDL_INIT_AUDIO;else{/*TrytoworkaroundanoccasionalALSAbufferunderflowissuewhenthe*periodsizeisNPOTduetoALSAresamplingbyforcingthebuffersize.*/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,"CouldnotinitializeSDL-%s\n",SDL_GetError());av_log(NULL,AV_LOG_FATAL,“(你设置显示变量了吗?)\n”);退出(1);}SDL_EventState(SDL_SYSWMEVENT,SDL_IGNORE);SDL_EventState(SDL_USEREVENT,SDL_IGNORE);av_init_packet(&flush_pkt);flush_pkt.data=(uint8_t*)&flush_pkt;if(!display_disable){intflags=SDL_WINDOW_HIDDEN;if(alwaysontop)#ifSDL_VERSION_ATLEAST(2,0,5)flags|=SDL_WINDOW_ALWAYS_ON_TOP;#elseav_log(NULL,AV_LOG_WARNING,"你的SDL版本不支持SDL_WINDOW_ALWAYS_ON_TOP.Featurewillbeinactive.\n");#endifif(borderless)flags|=SDL_WINDOW_BORDERLESS;elseflags;/ZRESIBLE_WINDOW|=SDL_WINDOW_BORDERLESS;SDL接口来创建显示窗口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,"Failedtoinitializeahardwareacceleratedrenderer:%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%srenderer.\n",renderer_info.name);}}if(!window||!renderer||!renderer_info.num_texture_formats){av_log(NULL,AV_LOG_FATAL,"失败edtocreatewindoworrenderer:%s",SDL_GetError());do_exit(NULL);}}//这个是重点,后面可以在这个界面找到read_threadl,这个界面是打开输入媒体文件的is=stream_open(input_filename,file_iformat);if(!is){av_log(NULL,AV_LOG_FATAL,"FailedtoinitializeVideoState!\n");do_exit(NULL);}//事件响应event_loop(is);/*neverreturns*/return0;}上面的注解是大概是播放器完成的工作,接下来我会详细介绍stream_open接口,对event_loop接口做简单说明:event_loop接口主要是响应对gui的操作,比如你操作键盘,它会响应,它会调用:下面开始解释stream_open接口:staticVideoState*stream_open(constchar*filename,AVInputFormat*iformat){VideoState*is;is=av_mallocz(sizeof(VideoState));//为此结构分配内存if(!is)returnNULL;is->filename=av_strdup(filename);//调用th是给它赋一个字符串的接口if(!is->filename)gotofail;is->iformat=iformat;//指向解复用设备is->ytop=0;//初始化播放窗口y的起始坐标为0is->xleft=0;////初始化播放窗口起始位置x/*startvideodisplay*///初始化帧队列if(frame_queue_init(&is->pictq,&is->videoq,VIDEO_PICTURE_QUEUE_SIZE,1)<0)gotofail;if(frame_queue_init(&is->subpq,&is->subtitleq,SUBPICTURE_QUEUE_SIZE,0)<0)gotofail;if(frame_queue_init(&is->sampq,&is->audioq,SAMPLE_QUEUE_SIZE,1)<0)gotofail;//初始化包队列if(packet_queue_init(&is->videoq)<0||packet_queue_init(&is->audioq)<0||packet_queue_init(&is->subtitleq)<0)gotofail;if(!(is->continue_read_thread=SDL_CreateCond())){av_log(NULL,AV_LOG_FATAL,"SDL_CreateCond():%s\n",SDL_GetError());gotofail;}//初始化时钟,时钟序列->queue_serial实际指向is->videoq.serialinit_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,settingto0\n",startup_volume);if(startup_volume>100)av_log(NULL,AV_LOG_WARNING,"-volume=%d>100,settingto100\n",startup_volume);startup_volume=av_clip(startup_volume,0,100);startup_volumee=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);returnNULL;}returnis;}通过上面的源码解读,我们找到了read_thread线程:同时,通过源码解读this界面,我们大概知道视频在播放中做了哪些工作,比如:帧队列的初始化,数据包队列的初始化;这也是打开媒体文件,然后把数据送进去,进行顺序操作;当然这里没有编码部分,这里的播放器是不涉及编码的。编码主要是对原始音视频数据进行编码压缩,减少内存占用,否则容器会占用太多内存!然后,视频、音频、字幕等时钟初始化,还有音频初始化音量,这里音量初始化为100的大小:以及音视频同步类型,默认我们默认初始化为audiovalue,音视频同步分为三种:/***音视频同步方式,默认以Audio为参考*/enum{AV_SYNC_AUDIO_MASTER,//Audio为参考AV_SYNC_VIDEO_MASTER,//Video为参考AV_SYNC_EXTERNAL_CLOCK,//以外部时钟为参考,synchronizetoanexternalclock*/};最后,在上面的相关初始化操作中,我们就可以开始执行read_thread操作,即播放器开始播放和读取数据,然后进行真正的操作!最后在这里提一下,VideoState这个结构体,你可以把它看成是音视频管理的主要管理器,通过源码,你也发现了很多操作初始化,它们都和这个结构体的成员有关,所以一定要看清楚这个结构体的内容:typedefstructVideoState{SDL_Thread*read_tid;//读线程句柄AVInputFormat*iformat;//指向demuxerintabort_request;//请求退出播放时=1intforce_refresh;//=1,需要刷新屏幕,请求立即刷新屏幕intpaused;//=1,暂停,=0,播放intlast_paused;//暂存“暂停”/“播放”状态intqueue_attachments_req;intseek_req;//标识一个seek请求intseek_flags;//seekflag,如AVSEEK_FLAG_BYTE等int64_tseek_pos;//请求seek的目标位置(当前位置+增量)int64_tseek_rel;//本次seek的位置增量intread_pause_return;AVFormatContext*ic;//iformatThecontextinrealtime;//=1为实时流Clockaudclk;//音频时钟Clockvidclk;//视频时钟Clockextclk;//外部时钟FrameQueuepictq;//视频帧队列FrameQueuesubpq;//字幕帧队列FrameQueuesampq;//采样FramequeueDecoderauddec;//音频解码器Decoderviddec;//视频解码器Decodersubdec;//字幕解码器intaudio_stream;//音频流索引intav_sync_type;//音视频同步类型,默认audiomasterdoubleaudio_clock;//当前音频帧+current的PTSframeDurationintaudio_clock_serial;//播放顺序,seek可以改变这个值//以下4个参数在非audiomaster同步模式下使用doubleaudio_diff_cum;//用于AVdifferenceaveragecomputationdoubleaudio_diff_avg_coef;doubleaudio_diff_threshold;intaudio_diff_avg_count;//endAVStream*audio_st;//音频流PacketQueueaudioq;//音频包队列intaudio_hw_buf_size;//SDL音频缓冲区大小(以字节为单位)//指向要播放的一帧音频数据,指向数据区域将被复制到SDL音频缓冲区如果已经重采样,则指向audio_buf1,//否则指向帧中的音频uint8_t*audio_buf;//指向需要重采样的数据uint8_t*audio_buf1;//指向重采样后的数据unsignedintaudio_buf_size;//一帧要播放的音频数据的大小(audio_buf指向)unsignedintaudio_buf1_size;//申请的音频缓冲区的实际大小audio_buf1intaudio_buf_index;//更新复制到的当前音频帧中的复制位置theSDLaudiobuffer//位置索引(指向第一个pendingCopybytes)//当前音频帧中还没有复制到SDLaudiobuffer中的数据量//audio_buf_size=audio_buf_index+audio_write_buf_sizeintaudio_write_buf_size;intaudio_volume;//volumeintmuted;//=1静音,=0正常structAudioParamsaudio_src;//音频帧参数#ifCONFIG_AVFILTERstructAudioParamsaudio_filter_src;#endifstructAudioParamsaudio_tgt;//SDL支持的音频参数,resamplingconversion:audio_src->audio_tgtstructSwrContext*swr_ctx;//音频重采样contextintframe_drops_early;medropframe计数/丢弃视频packet_countingintframe;enumShowMode{SHOW_MODE_NONE=-1,//不显示SHOW_MODE_VIDEO=0,//显示视频SHOW_MODE_WAVES,//显示波形、音频SHOW_MODE_RDFT,//自适应滤波器SHOW_MODE_NB}show_mode;//音频波形显示使用int16_tsample_array[SAMPLE_ARRAY_SIZE];//样本数组intsample_array_index;//样本索引intlast_i_start;//最后开始RDFTContext*rdft;//自适应过滤器上下文intrdft_bits;//自用码率FFTSample*rdft_data;//快速傅里叶采样intxpos;doublelast_vis_time;SDL_Texture*vis_texture;//音频纹理SDL_Texture*sub_texture;//字幕显示SDL_Texture*vid_texture;//视频显示intsubtitle_stream;//字幕流索引AVStream*subtitle_st;//字幕流PacketQueuesubtitleq;//字幕包队列doubleframe_timer;//记录最后一帧播放时刻doubleframe_last_returned_time;//最后返回时间doubleframe_last_filter_delay;//最后一个过滤延迟intvideo_stream;//视频流索引AVStream*video_st;//VideostreamPacketQueuevideoq;//Videoqueuedoublemax_frame_duration;//一帧的最大间隔。上面这个,我们考虑jumpatimestamp的不连续性structSwsContext*img_convert_ctx;//视频大小格式转换structSwsContext*sub_convert_ctx;//字幕大小格式转换inteof;//是否结束读取char*filename;//文件名intwidth,height,xleft,ytop;//width,height,x起始坐标,y起始坐标intstep;//=1步播放模式,=0其他模式#ifCONFIG_AVFILTERintvfilter_idx;AVFilterContext*in_video_filter;//视频链中的第一个过滤器AVFilterContext*out_video_filter;//视频链中的最后一个过滤器AVFilterContext*in_audio_filter;//音频链中的第一个过滤器AVFilterContext*out_audio_filter;//音频链中的第一个过滤器AV_outFilterContext*tfilterintheaudiochainAVFilterGraph*agraph;//audiofiltergraph#endif//保留最新对应的音视频字幕流的steam索引last_video_stream,last_audio_stream,last_subtitle_stream;SDL_cond*continue_read_thread;//当读数据队列满进入休眠状态,可以通过这个条件唤醒读线程}VideoState;嗯,由于要解读的源码比较多,本文就暂时分享到这里。现在我们应该对玩家的流程路线有了一个非常清晰的认识。2.总结:下期继续read_thread线程源码解读,奥利!在后面的文章中,我们将使用gdb打断点,然后通过bt(stackframe)查看一个重要接口的调用路径,这对阅读代码很有帮助!相关参考:https://www.cnblogs.com/leisure_chn/p/10301831.html