mplayer严格来说是不支持插件的,这与他的定位有关。mplayer有很多定制版,比如GMplayer,SMPlayer,MPlayerX,更多定制版是用在嵌入式平台,没有名字。

mplayer是比较轻量级的播放器,结构小巧,但是编解码支持并不比其他播放器少。

在mplayer上增加插件,需要直接修改源码,发布自己的定制版。

与vlc不一样,mplayer中协议输入和解封装是必须分开的两个功能,不能合并在一起,因此,需要增加两个模块:stream,demux,对于源码文件分别是stream/stream_mylib.c、libmpdemux/demux_mylib.c

我们采用的是mplayer-1.0rc4版本的源代码,mplayer-1.1版本有问题,具体问题可以看我的另一篇文章《MPlayerWin32版本H.264解码问题》

1、注册stream模块

首先需要增加一个stream类型定义:

在stream/stream.h中增加STREAMTYPE_MYLIB宏定义

#define STREAMTYPE_MF 18
#define STREAMTYPE_RADIO 19
#define STREAMTYPE_BLURAY 20
#define STREAMTYPE_MYLIB 21 // 自定义流类型
#define STREAM_BUFFER_SIZE 2048

在stream/stream.c,auto_open_streams数组中增加stream_info_mylib

extern const stream_info_t stream_info_mylib;
static const stream_info_t* const auto_open_streams[] = {
#ifdef CONFIG_VCD
&stream_info_vcd,
#endif
....
&stream_info_mylib,
&stream_info_null,
&stream_info_mf,
&stream_info_file,
NULL
};

这里的stream_info_mylib实际定义在插件源码stream/stream_mylib.c中:

const stream_info_t stream_info_ppbox = {
"mylib input",
"mylib",
"luansxx",
"",
mylib_stream_open,
{ "vod", NULL },
NULL,
1
};

其中最关键的是定义了一个流打开回调接口:mylib_stream_open,以及协议标识:"vod"

2、实现mylib_stream_open

static int
mylib_stream_open (stream_t *stream, int mode, void *opts, int *file_format)
{
my_int ret = Mylib_Open(stream->url);
if (ret != mylib_success) {
return STREAM_ERROR;
}
stream->type = STREAMTYPE_MYLIB;
stream->close = mylib_stream_close;
*file_format = DEMUXER_TYPE_MYLIB;
return STREAM_OK;
}

这里主要工作是设置流类型(stream->type),用来关闭流的回调函数(stream->close)文件格式(file_format)和文件格式(file_format),通过文件类型可以与后面我们的demux模块对应。

3、实现mylib_stream_close

close就比较直接了

static void
mylib_stream_close (stream_t *stream)
{
Mylib_Close();
}

stream这边的代码就这些,下面是demux相关的代码

4、注册stream模块

和stream模块注册过程一样,我们需要在libmpdemux/demuxer.h中增加DEMUXER_TYPE_MYLIB,在libmpdemux/demuxer.c中的demuxer_list数组中增加我们的demux记录demuxer_desc_mylib

在libmpdemux/demux_mylib.c中定义demux相关信息:

const demuxer_desc_t demuxer_desc_mylib = {
"mylib demuxer",
"mylib",
"mylib",
"?",
"",
DEMUXER_TYPE_MYLIB,
0, // no autodetect
NULL,
demux_mylib_fill_buffer,
demux_mylib_open,
NULL,
demux_mylib_seek,
NULL
};

这里面关键信息有类型DEMUXER_TYPE_MYLIB,以及6个回调函数,分别是:检查、解封装、打开、关闭、拖动、控制,大都是可选的,如果觉得没有用设置为NULL。

5、实现demux_mylib_open

typedef struct mylib_demux_priv {
uint64_t duration;
uint32_t stream_count;
demux_stream_t * streams[8];
} mylib_demux_priv_t;
static demuxer_t* demux_mylib_open(demuxer_t* demuxer) {
mylib_demux_priv_t * priv = (mylib_demux_priv_t *)calloc(sizeof(mylib_demux_priv_t), 1);
priv->duration = Mylib_GetDuration()();
priv->stream_count = Mylib_GetStreamCount()();
mp_msg(MSGT_DEMUX, MSGL_ERR, "demux_mylib_open duration %u\n", priv->duration);
mp_msg(MSGT_DEMUX, MSGL_ERR, "demux_mylib_open stream count %u\n", priv->stream_count);
for (uint32_t idx = 0; idx < priv->stream_count; ++idx) {
Mylib_StreamInfoEx info;
Mylib_GetStreamInfoEx()(idx, &info);
if (info.type == mylib_video) {
mp_msg(MSGT_DEMUX, MSGL_ERR, "video stream\n");
sh_video_t* sh_video = new_sh_video(demuxer, idx);;
BITMAPINFOHEADER *bih = calloc(sizeof(*bih) + info.format_size, 1);
bih->biSize = sizeof(*bih) + info.format_size;
bih->biCompression = mmioFOURCC('H', '2', '6', '4');
bih->biWidth = info.video_format.width;
bih->biHeight = info.video_format.height;
memcpy(bih + 1, info.format_buffer, info.format_size);
sh_video->format = bih->biCompression;
sh_video->disp_w = info.video_format.width;
sh_video->disp_h = info.video_format.height;
sh_video->fps = info.video_format.frame_rate;
sh_video->frametime = 1.0 / info.video_format.frame_rate;
sh_video->bih = bih;
demuxer->video->sh = sh_video;
priv->streams[idx] = demuxer->video;
} else if (info.type == mylib_audio) {
mp_msg(MSGT_DEMUX, MSGL_ERR, "audio stream\n");
#if STREAMTYPE_PPBOX == 21
sh_audio_t* sh_audio = new_sh_audio(demuxer, idx);
#else
sh_audio_t* sh_audio = new_sh_audio(demuxer, idx, NULL);
#endif
WAVEFORMATEX *wf = calloc(sizeof(*wf) + info.format_size, 1);;
wf->wFormatTag = 255;
wf->nChannels = info.audio_format.channel_count;
wf->nSamplesPerSec = info.audio_format.sample_rate;
wf->wBitsPerSample = info.audio_format.sample_size;
wf->cbSize = info.format_size;
memcpy(wf + 1, info.format_buffer, info.format_size);
sh_audio->wf = wf;
sh_audio->format = mmioFOURCC('M', 'P', '4', 'A');
sh_audio->samplerate = info.audio_format.sample_rate;
sh_audio->samplesize = info.audio_format.sample_size;
sh_audio->channels = info.audio_format.channel_count;
demuxer->audio->sh = sh_audio;
demuxer->audio->id = idx;
priv->streams[idx] = demuxer->audio;
}
}
demuxer->seekable = priv->duration > 0;
demuxer->priv = priv;
return demuxer;
}

这里设置了私有数据demuxer->priv=priv,增加了音视频流,主要工作在设置sh_video_t、sh_audio_t,比较诡异的是用了windows结构体:BITMAPINFOHEADER、WAVEFORMATEX。另外,运行的时候一直说没有audio,后来没办法强制设置demuxer->audio->id=idx。

6、实现demux_mylib_fill_buffer

static int demux_mylib_fill_buffer(demuxer_t* demuxer, demux_stream_t *ds) {
mylib_demux_priv_t *priv = (mylib_demux_priv_t *) demuxer->priv;
Mylib_SampleEx2 sample;
my_int32 ret;
while (1) {
ret = Mylib_ReadSampleEx2()(&sample);
if (ret == mylib_success) {
demux_packet_t *dp = new_demux_packet(sample.buffer_length);
memcpy(dp->buffer, sample.buffer, sample.buffer_length);
dp->pts = (double)(sample.start_time + sample.composite_time_delta) * 0.000001;
ds_add_packet(priv->streams[sample.stream_index], dp);
if (priv->streams[sample.stream_index] == ds)
return 1;
} else if (ret == mylib_would_block) {
if (mp_input_check_interrupt(100))
return 0;
} else if (ret == mylib_stream_end) {
demuxer->stream->eof = 1;
return 0;
} else {
return 0;
}
};
return 0;
}

这个函数的最终目标是通过ds_add_packet向后面的模块输出一个音视频帧,第二个参数好像是指定哪个流(音频或者视频)需要数据,但是也可以向另一个流输出数据。
mp_input_check_interrupt可以让你在输入流阻塞时也能够响应用户退出请求,退出循环。

6、实现demux_mylib_seek

拖动处理很简单,代码如下

static void demux_mylib_seek(demuxer_t *demuxer,float rel_seek_secs,float audio_delay,int flags)
{
Mylib_Seek((uint32_t)rel_seek_secs * 1000);
}