java媒体处理解决方案之MPLAYER

对播放器有所了解的朋友,我相信对于mplayer这个开源的播放器自然是不会陌生的吧,这也是我最喜欢的播放器,主要原因就是简洁、功能强大、绿色、跨平台、万能。

本文介绍java如何调用mplayer(我的指的是内嵌mplayer到java应用程序)。说道java调用第三方程序分为直接调用和间接调用两种。这里说的直接调用指的是使用jvm管理第三方程序的调用方式,间接调用就是通过一些工人的协议达到通信的目的,进而实现调用。

直接调用又可使用直接调用第三方程序的方式,例如win下面的exe,这种方式实际就是让exe程序的进程处于jvm的控制之下(再次我也不说java控制进程的各种困难之处了),另外就是代码级的调用比如jni,JNI可能对于大多数人来说比较困难,那么久更加简单的JNA方式调用,JNA的话,你只需要对方程序的一个头文件,然后java映射一个(不知道这样说是不是恰当),对象指向dll即可,这对开源程序来说非常方便,对于闭源软件的话就需要对方提供接口了,比如对方告诉你某dll里面有某个方法,然后同样适用JNA映射对象到dll,就可以实现调用了,当然并不需要知道此dll里面的所有方法,针对这种方法将会在java媒体解决方案的下一篇,即另外一种方式的java媒体播放中详细讲解。

java调用exe,一般适用Runtime.getRuntime().exec()。此方法返回一个java.lang.Process对象,可简单的理解为被调用程序的进程。在mplayer目录使用mplayer -help命令即可看到一些参数信息,如下:

当然进一步详细的了解请到官方查看文档。mplayer默认是没有图形界面的,windows下面可能会存在多钟界面,linux下面的话会比较容易的看到类似外壳方式的界面,而一般是qt编写。

话归正题调用一个没有界面的播放器最重要的就是需要一个屏幕来绘制图像,这里就要就要说下swing和AWT的关系了,最简单的解释就是AWT是重量级的,SWING是轻量级的。AWT使用本地系统资源构建,而swing则是更高层的与系统无关其使用起来更加方便,但是swing无法充分利用系统资源,因为他是“画”出来的,无法与系统直接交互,换句话说就是没有句柄。但是播放器这种东西显然是需要本地资源支持的。所有就需要SWT组件充当播放器屏幕,因为AWT组件有句柄。而此处首选的就是:java.awt.Canvas这个类,从mplayer的帮助里面可看到-wid这个参数,这是句柄参数也就是说只要控件句柄传给mplayer,它就会在此控件上绘制屏幕。

而java本身获得句柄的方法被因隐藏,如之前使用一下方法获得句柄:

1
2
3
4
5
6
7
8
9
10
11
12
13
long getWid(){
long wid=-1;
try {
Class<?> cl = Class.forName("sun.awt.windows.WComponentPeer");
java.lang.reflect.Field f = cl.getDeclaredField("hwnd");
f.setAccessible(true);
ComponentPeer peer = canvas.getPeer();
wid = f.getLong(peer);
} catch (Exception e) {
e.printStackTrace();
}
return wid;
}

当然canvas.getPeer()方法已经过时了,但是依然可以,在jna的时代就比较简单,如下:Native.getComponentID()可直接返回long类型的句柄。然后要做的就是把画布Canvas布局在自己的窗口内,然后把long型句柄传入即可。当然还有一些基本的参数设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//调用命令行,更多选项请参考mplayer文档
cmd = new String[] {
mplayerPath,//mplayer路径
"-vo","directx",//视频驱动
"-identify", //输出详情
"-slave", //slave模式播放
"-wid",long,//视频窗口的window handle
"-colorkey", "0x030303",//视频窗口的背景色
"-osdlevel", String.valueOf(1),//osd样式
path //播放文件路径
// "rtsp://192.168.1.100/sd-mad.mpg"
};
try {
proc = Runtime.getRuntime().exec(cmd);
} catch (IOException e1) {
e1.printStackTrace();
return;
}

然后就可以在java的界面中播放视频了,且几乎是支持一切格式。然后关键的就是控制了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//读取并解析mplayer的输出信息
final InputStream is1 = proc.getErrorStream();
final InputStream is2 = proc.getInputStream();
final Runnable errorReader = new Runnable() {
public void run() {
try {
final BufferedReader lReader = new BufferedReader(new InputStreamReader(is1));
for (String l = lReader.readLine(); l != null ; l = lReader.readLine()) {
System.out.println("ERROR "+l);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
};


final Runnable standReader = new Runnable() {
public void run() {
try {
final BufferedReader lReader = new BufferedReader(new InputStreamReader(is2));
String l="";
while ((l=lReader.readLine())!=null) {

}
} catch (Throwable t) {
t.printStackTrace();
}
}
};

t1 = new Thread(errorReader);
t2 = new Thread(standReader);
t1.start();
t2.start();

从进程中去的标准输出流和错误流,然后根据流内容最初相应的判断,值得一提的是这些流是文本流,所以根据流内容即可知道播放器的实时情况,如果需要改变播放状态则取得输入流,以文本的方式向进城写入命令,当然具体命令内容请参看文档。

于是这种解决方案就如以上内容。接下来说一下优缺点。优点是(播放器本身无问题,跨平台、万能),关键的好处是播放器进程独立于java虚拟机,于是java糟糕的内存和资源管理问题就不活出现,因为播放器申请的资源有系统管理。缺点就是正因为是独立进程,当播放器挂掉java可知,但是java挂掉,播放器会依然在后台运行。当然如果使用JNI或者JNA直接调用dll的方式就不存在此问题,此种方式将在下一章节介绍。