现在的位置: 首页 > 综合 > 正文

MPlayer源码分析 (转载)

2014年08月07日 ⁄ 综合 ⁄ 共 10405字 ⁄ 字号 评论关闭

MPlayer源码分析

这几天在学习mplayer以下是在网上搜集到的关于mplayer的文章,主要是源码分析这块。首先感谢这些文章的作者,有些没有标明原文出处,实在表示抱歉。

 

从Mplayer.c的main开始 
//处理参数 
mconfig = m_config_new(); 
m_config_register_options(mconfig,mplayer_opts); 
// TODO : add something to let modules register their options 
mp_input_register_options(mconfig); 
parse_cfgfiles(mconfig); 
//初始化mpctx结构体,mpctx应该是mplayer context的意思,顾名思义是一个统筹全局的变量。 
static MPContext *mpctx = &mpctx_s; 
// Not all functions in mplayer.c take the context as an argument yet 
static MPContext mpctx_s = { 
.osd_function = OSD_PLAY, 
.begin_skip = MP_NOPTS_VALUE, 
.play_tree_step = 1, 
.global_sub_pos = -1, 
.set_of_sub_pos = -1, 
.file_format = DEMUXER_TYPE_UNKNOWN, 
.loop_times = -1, 
#ifdef HAS_DVBIN_SUPPORT 
.last_dvb_step = 1, 
#endif 
}; 
一些GUI相关的操作 
打开字幕流 
打开音视频流 
mpctx->stream=open_stream(filename,0,&mpctx->file_format); 
//fileformat文件还是TV流DEMUXER_TYPE_PLAYLIST或DEMUXER_TYPE_UNKNOWN DEMUXER_TYPE_TV 
//current_module记录状态vobsub   open_stream handle_playlist dumpstream 
stream_reset(mpctx->stream); 
stream_seek(mpctx->stream,mpctx->stream->start_pos); 
f=fopen(stream_dump_name,”wb”); dump文件流 
stream->type==STREAMTYPE_DVD 
//============ Open DEMUXERS — DETECT file type ====================== 
//Demux:分离视频流和音频流 
mpctx->demuxer=demux_open(mpctx->stream,mpctx->file_format,audio_id,video_id,dvdsub_id,filename); 
mpctx->d_audio=mpctx->demuxer->audio; 
mpctx->d_video=mpctx->demuxer->video; 
mpctx->d_sub=mpctx->demuxer->sub; 
mpctx->sh_audio=mpctx->d_audio->sh; 
mpctx->sh_video=mpctx->d_video->sh; 
分离了之后就开始分别Play audio和video 
这里只关心play video 
/*========================== PLAY VIDEO ============================*/ 
vo_pts=mpctx->sh_video->timer*90000.0; 
vo_fps=mpctx->sh_video->fps; 
if (!mpctx->num_buffered_frames) { 
   double frame_time = update_video(&blit_frame); 
   mp_dbg(MSGT_AVSYNC,MSGL_DBG2,”*** ftime=%5.3f ***/n”,frame_time); 
   if (mpctx->sh_video->vf_inited < 0) { 
     mp_msg(MSGT_CPLAYER,MSGL_FATAL, MSGTR_NotInitializeVOPorVO); 
     mpctx->eof = 1; goto goto_next_file; 
   } 
   if (frame_time < 0) 
   mpctx->eof = 1; 
   else { 
     // might return with !eof && !blit_frame if !correct_pts 
     mpctx->num_buffered_frames += blit_frame; 
     time_frame += frame_time / playback_speed;   // for nosound 
   } 

//关键的函数是update_video 
根据pts是否正确调整一下同步并在必要的时候丢帧处理。 
最终调用decode_video开始解码(包括generate_video_frame里)。 
mpi = mpvdec->decode(sh_video, start, in_size, drop_frame); 
mpvdec是在main里通过reinit_video_chain的一系列调用动态选定的解码程序。 
其实就一结构体。它的原型是 
typedef struct vd_functions_s 

vd_info_t *info; 
int (*init)(sh_video_t *sh); 
void (*uninit)(sh_video_t *sh); 
int (*control)(sh_video_t *sh,int cmd,void* arg, …); 
mp_image_t* (*decode)(sh_video_t *sh,void* data,int len,int flags); 
} vd_functions_t; 
这是所有解码器必须实现的接口。 
int (*init)(sh_video_t *sh);是一个名为init的指针,指向一个接受sh_video_t *类型参数,并返回int类型值的函数地址。 
那些vd_开头的文件都是解码相关的。随便打开一个vd文件以上几个函数和info变量肯定都包含了。 
mpi被mplayer用来存储解码后的图像。在mp_image.h里定义。 
typedef struct mp_image_s { 
unsigned short flags; 
unsigned char type; 
unsigned char bpp;   // bits/pixel. NOT depth! for RGB it will be n*8 
unsigned int imgfmt; 
int width,height;   // stored dimensions 
int x,y,w,h;   // visible dimensions 
unsigned char* planes[MP_MAX_PLANES]; 
int stride[MP_MAX_PLANES]; 
char * qscale; 
int qstride; 
int pict_type; // 0->unknown, 1->I, 2->P, 3->B 
int fields; 
int qscale_type; // 0->mpeg1/4/h263, 1->mpeg2 
int num_planes; 
/* these are only used by planar formats Y,U(Cb),V(Cr) */ 
int chroma_width; 
int chroma_height; 
int chroma_x_shift; // horizontal 
int chroma_y_shift; // vertical 
/* for private use by filter or vo driver (to store buffer id or dmpi) */ 
void* priv; 
} mp_image_t; 
图像在解码以后会输出到显示器,mplayer本来就是一个视频播放器么。但也有可能作为输入提供给编码器进行二次编码,MP附带的mencoder.exe就是专门用来编码的。在这之前可以定义filter对图像进行处理,以实现各种效果。所有以vf_开头的文件,都是这样的filter。 
图像的显示是通过vo,即video out来实现的。解码器只负责把解码完成的帧传给vo,怎样显示就不用管了。这也是平台相关性最大的部分,单独分出来的好处是不言而喻的,像在Windows下有通过direcx实现的vo,Linux下有输出到X的vo。vo_*文件是各种不同的vo实现,只是他们不都是以显示为目的,像vo_md5sum.c只是计算一下图像的md5值。 
在解码完成以后,即得到mpi以后,filter_video被调用,其结果是整个filter链上的所有filter都被调用了一遍,包括最后的VO,在vo的put_image里把图像输出到显示器。这个时候需要考虑的是图像存储的方法即用哪种色彩空间。 
[MPlayer core] 
| (1) 
_____V______   (2)   /~~~~~~~~~~/     (3,4)   |~~~~~~| 
|           | —–> | vd_XXX.c |   ——-> | vd.c | 
| decvideo |         /__________/   <-(3a)– |______| 
|           | —–,   ,………….(3a,4a)…..: 
~~~~~~~~~~~~   (6) V   V 
/~~~~~~~~/     /~~~~~~~~/   (8) 
| vf_X.c | –> | vf_Y.c | —->   vf_vo.c / ve_XXX.c 
/________/     /________/ 
|               ^ 
(7) |   |~~~~~~|   : (7a) 
`-> | vf.c |…: 
|______| 
感觉Mplayer的开发人员们都是无比的牛,硬是用原始的C实现了很多OO语言才支持的特性,带来不好的结果是代码看起来比较费劲.

 

MPlayer流程

 

int c_mplay_main(int argc,char* argv[])
{
**调用 AddExcept()注册异常处理函数
** initmplayer();   //初始化,创建快进和暂停的信号量
**InitTimer();初始化计时器
**mp_msg_init();初始化消息系统
**set_path_env();设置路径、环境
**ipu_image_start();ipu初始化
**mplayer_showmode(1);设置显示模式
**parse_codec_cfg(NULL);解析codec配置寄存器
**打开数据流
**分析播放树
**添加播放树列表
**初始化预填充缓存
**打开播放的文件
**创建buffer
**打开数据流
**检测数据流类型(音频格式和视频格式)
**分析音频流视频流的信息(原始视频尺寸、分辨率、帧频率、码流大小)
**启动相应的分离器
**分析剪辑信息
**初始化codec(多媒体数字信号编解码器)
**选择打开相应的视频解码器
**初始化视频解码器,分析视频流信息
**选择打开相应的音频解码器
**初始化音频解码器、PCM,分析音频信息
**同步音频视频输出
**开始播放

}
   main函数中的入口如下~ 
/*========================== PLAY AUDIO ============================*/ 
if (mpctx->sh_audio) 
     if (!fill_audio_out_buffers()) 
     // at eof, all audio at least written to ao 
     //由mpctx->sh_audio引出,我想重点强调一下sh_audio_t结构,这是音频流头部结构~ 
// Stream headers: 
typedef struct { 
   int aid; 
   demux_stream_t *ds; 
   struct codecs_st *codec; 
   unsigned int format; 
   int initialized; 
   float stream_delay; // number of seconds stream should be delayed (according 
to dwStart or similar) 
   // output format: 
   int sample_format; 
   int samplerate; 
   int samplesize; 
   int channels; 
   int o_bps; // == samplerate*samplesize*channels   (uncompr. bytes/sec) 
   int i_bps; // == bitrate   (compressed bytes/sec) 
   // in buffers: 
   int audio_in_minsize;         // max. compressed packet size (== min. in buffer size 

   char* a_in_buffer; 
   int a_in_buffer_len; 
   int a_in_buffer_size; 
   // decoder buffers: 
   int audio_out_minsize; // max. uncompressed packet size (==min. out buffsize 

   char* a_buffer; 
   int a_buffer_len; 
   int a_buffer_size; 
   // output buffers: 
   char* a_out_buffer; 
   int a_out_buffer_len; 
   int a_out_buffer_size; 
   struct af_stream_s *afilter;           // the audio filter stream 
   struct ad_functions_s* ad_driver; 
#ifdef DYNAMIC_PLUGINS 
   void *dec_handle; 
#endif 
   // win32-compatible codec parameters: 
   AVIStreamHeader audio; 
   WAVEFORMATEX* wf; 
   // codec-specific: 
   void* context; // codec-specific stuff (usually HANDLE or struct pointer) 
   unsigned char* codecdata; // extra header data passed from demuxer to codec 

   int codecdata_len; 
   double pts;   // last known pts value in output from decoder 
   int pts_bytes; // bytes output by decoder after last known pts 
   char* lang; // track language 
   int default_track; 
} sh_audio_t; 

三.step by step 
   1.下面我们来分析fill_audio_out_buffers()函数 
   static int fill_audio_out_buffers(void) 

     1.1查看音频驱动有多大的空间可以填充解码后的音频数据 
     bytes_to_write = mpctx->audio_out->get_space(); 

     这里是查看音频驱动还有多少空闲空间(即pcm缓冲区队列的可用空间),如果有空闲的空间,就调用ao->play()
函数来填充这个buffer,并播放音频数据。(pcm缓冲区的数据会被dma自动送去播放,我们不能让pcm缓冲区为空,否则声音就没了!)这个音频驱动的buffer的大小要设置合适,太小会导致一帧图像还没播放完,buffer就已经空了,会导致音视频严重不同步;太大会导致Mplayer需要不停地解析媒体文件来填充这个音频buffer,而视频可能还来不及播放。。。 

     1.2 对音频流进行解码 
     if (decode_audio(sh_audio, playsize) < 0) 
     注:这里的decode_audio只是一个接口,并不是具体的解码api。这个函数从sh_audio->a_out_buffer获得至少playsize bytes的解码或过滤后的音频数据,成功返回0,失败返回-1 

     1.3 将解码后的音频数据进行播放 
     playsize = mpctx->audio_out->play(sh_audio->a_out_buffer, playsize, playflags); 
     注:从sh_audio->a_out_buffer中copy playsize bytes数据,注意这里一定要copy出来,因为当play调用后,这部分数据就会被覆盖了。这些数据不一定要用完,返回的值是用掉的数据长度。另外当flags|AOPLAY_FINAL_CHUNK的值是真时,说明音频文件快结束了。(play()函数把音频数据写到pcm缓冲区 。)

     1.4 if (playsize > 0) { 
             sh_audio->a_out_buffer_len -= playsize; 
             memmove(sh_audio->a_out_buffer, &sh_audio->a_out_buffer[playsize], 
                     sh_audio->a_out_buffer_len); 
             mpctx->delay += playback_speed*playsize/(double)ao_data.bps; 
         } 
     播放成功后就从sh_audio->a_out_buffer中移出这段数据,同时减去sh_audio->a_out_buffer_len的长度 . 

     2.刚才说了,decode_audio()函数并不是真正的解码函数,它只是提供一个接口,下面我们就来看看这个函数到底做了哪些事情. 
     int decode_audio(sh_audio_t *sh_audio, int minlen) 

     2.1 解码后的视频数据都被cut成大小相同的一个个区间~ 
     int unitsize = sh_audio->channels * sh_audio->samplesize * 16; 

     2.2 如果解码器设置了audio_out_minsize,解码可以等价于 
     while (output_len < target_len) output_len += audio_out_minsize; 
     因此我们必需保证a_buffer_size大于我们需要的解码数据长度加上audio_out_minsize,所以我们最大解码长度也就有了如下的限制:(是不是有点绕口?~~) 
     int max_decode_len = sh_audio->a_buffer_size - sh_audio->audio_out_minsize; 

     2.3 如果a_out_buffer中没有我们需要的足够多的解码后数据,我们当然要继续解码撒~ 只不过我们要注意, 
a_out_buffer中的数据是经过filter过滤了的(长度发生了变化),这里会有一个过滤系数,我们反方向求过滤前的数据长度,所以要除以这个系数。 
     while (sh_audio->a_out_buffer_len < minlen) { 
         int declen = (minlen - sh_audio->a_out_buffer_len) / filter_multiplier 
             + (unitsize << 5); // some extra for possible filter buffering 
         declen -= declen % unitsize; 
         if (filter_n_bytes(sh_audio, declen) < 0) 
             return -1; 
     } 

     3.我们来看看这个filter_n_bytes函数 
   static int filter_n_bytes(sh_audio_t *sh, int len) 

     3.1 还记得我们刚才说的那个最大解码长度吗? 这里是要确保不会发生解码后数据溢出。 

     assert(len-1 + sh->audio_out_minsize <= sh->a_buffer_size); 

     3.2 按需要解码更多的数据 
     while (sh->a_buffer_len < len) { 
         unsigned char *buf = sh->a_buffer + sh->a_buffer_len; 
         int minlen = len - sh->a_buffer_len; 
         int maxlen = sh->a_buffer_size - sh->a_buffer_len; 
         //这里才是调用之前确定的音频解码器进行真正音频解码 
         int ret = sh->ad_driver->decode_audio(sh, buf, minlen, maxlen); 
         if (ret <= 0) { 
             error = -1; 
             len = sh->a_buffer_len; 
             break; 
         } 
         //解码之后a_buffer_len增加 
         sh->a_buffer_len += ret; 
     } 

     3.3 在前面我们提到过过滤这个步骤,下面的图描述了整个过程 
     filter_output = af_play(sh->afilter, &filter_input); 
     注:这里实际上做的是过滤这部分的工作 
     ------------         ----------           ------------ 
     |             |   解码 |           |   过滤   |             |   播放 
     | 未解码数据 | ----->|解码后数据| ------>| 播放的数据 | ------> 
     | a_in_buffer|       | a_buffer |         |a_out_buffer| 
     ------------         ----------           ------------ 

     3.4 if (sh->a_out_buffer_size < sh->a_out_buffer_len + filter_output->len) 
     注:如果过滤后的数据长度太长,需要扩展a_out_buffer_size 

     3.5 将过滤后的数据从decoder buffer移除: 
     sh->a_buffer_len -= len; 
     memmove(sh->a_buffer, sh->a_buffer + len, sh->a_buffer_len); 

四.结语 
     回顾一下今天的内容,我们从sh_audio_t结构体出发,依次分析了fill_audio_out_buffers()函数, 
decode_audio()函数,filter_n_bytes()函数,但是并没有分析具体的decode和play函数。 
     顺便说点题外话,看Mplayer的代码,结构化模块化的特点很突出,通常是先定义一组 
接口,然后具体的程序去实现这些接口,这样子对代码的维护和扩展都很有好处~

 

 

这里再补充一下Mplayer的目录结构和子文件夹说明:

原文地址:http://qzone.qq.com/blog/81182980-1235106011


libavcodec libavformat libavutil三个文件夹来自ffmpeg的库 ;
libfaad2 libao2 liba52 libmpg2 mp3lib vidix几个文件夹是其它的三方库 ;
libmpcodecs libmpdemux 文件夹中为mplayer 的 demux 和codecs。 ;
其中demux_XXX.c为处理各种不同的container.
vd_XXX.c为mplayer的内置视频解码器,ad_xxx.c是音频解码器。
vf_XXX.c 和af_XXX.c 为视,音频的各种filter。
vo_XXX.c 为解码后视频输出。
libvo 视频输出模块的 Make 文件配置部分头
libao2 音频输出模块的 Make 文件配置部分头
drivers    使用 VIDIX 技术用到的直接硬件访问驱动程序

 

怎么样,还是很注重条理的吧?不过,vo_XXX.c是在libvo下

抱歉!评论已关闭.