mplayer源码下载地址:
http://www.mplayerhq.hu/MPlayer/releases/
下载最新的MPlayer-1.0rc4
#mkdir /usr/local/mplayer
#mkdir /usr/local/codecs
#cdMPlayer-1.0rc4
#./configure --prefix=/usr/local/mplayer --codecsdir=/usr/local/ codecs --language=zh_CN
这里有的网上加了参数:--enable-gui (开启图形界面),但是我编译的时候会出现如下错误:mplayer dereferencing pointer to incomplete type
据网上说是结构体没有定义出现的错误。
我们不需要gui的界面所以去掉。
#make
#make install
#/usr/local/mplayer/bin/mplayer /media/hp-v245/nobody.avi
发现没有声音,提示:
audio_setup: Can't open audio device /dev/dsp:No such file or directory
#ls /dev/dsp
发现果然没有此文件
#sudo su
#modprobe snd_pcm_oss
创建dsp文件
再播放就可正常播放,
#ls mplayer
发现有10M,这是没有裁剪的mplayer很大,下面做的工作就是要裁剪mplayer。
交叉编译MPlayer-1.0rc2,到3210板子上运行,交叉编译工具链:gcc-3.4.6
./configure --enable-cross-compile --cc=mipsel-linux-gcc --target=mips-linux --as=mipsel-linux-as --ar=mipsel-linux-ar --host-cc=gcc --disable-gui --disable-ivtv
vo_ivtv.c:80: error: storage size of 'sd1' isn't known
。。。。。。。
这几天在学习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下。