文/朱先忠编译
分三个步骤完成的技术攻关
不幸的是,上面概述编码方法是失败的,至少对于Windows 版的JMF性能包版本2.1.1e是这样。经过几番修改,我最后得到一个可以正常工作的版本JMFSnapper。
攻关1 上述的两个控件FramePositioningControl与FrameGrabbingControl,在JMF缺省的播放器模块中是难以得到(Solaris和Win32性能包均支持两种不同的MPEG播放器)的,要求用"native modular(本地组件)"播放器才行,其选取方式如下:
Manager.setHint(Manager.PLUGIN_PLAYER, new Boolean(true)); |
该播放器是一个重量级的组件,其与轻量级的Swing GUI如JFrame 和JPanel交互性较差。不过,我不需要显示播放器的界面。使用本地组件播放器的一个更为严重的后果是,需要较长的时间装入媒体和出现一些不确定的播放结果(如播放快慢不一致且漏掉一些帧)。
攻关2 经过一番考虑,我定出最好的加速播放器的方式是让其承担较少量的工作。我从MPEG文件中提取出了音频轨道部分,并确保该文件以相对简单的MPEG-1格式存储。任何一些视频编辑工具都可以胜任这些工作。我使用了两个自由软件工具: MPEG Properties和 FlasKMPEG。前者用于提供动画格式信息,后者是一个不错的编辑器。
经提取加工后的动画播放速度快捷,帧速率稳定并且没有漏帧的情况发生。
然而,FramePositioningControl控件类并不可靠。在我的WinXP机器上,seek()方法几乎总是失败,skip()方法大约有百分之八十情况下工作正常。
攻关3 我只好对FramePositioningControl忍痛割爱。我运用的帧抓取算法依赖于调用FrameGrabbingControl的grabFrame()方法--当播放器播放动画时以常规的间隔时段调用该方法。
到此,我已有了可靠的从仅含视频的MPEG-1文件中抓取帧的代码。对于既有视频也有音频轨道的文件该代码也运行良好;但不足是,播放器启动很慢而且不确定的播放导致帧抓取的不确定性。
我在JMFSnapper 的开始加上了一些"等待"代码来处理既有视频也有音频的动画。JMFSnapper对象等待播放器的启动(即进入启动状态),并等待第一个动画帧可用。
等待第一帧
JMFSnapper类的构造器调用了方法waitForBufferToImage(),该方法反复地调用方法hasBufferToImage()直到它检测到第一个视频帧为止。
hasBufferToImage()调用了FrameGrabbingControl的方法grabFrame(),并检查是否返回的缓冲对象中包含视频格式数据。它使用该数据来初始化一个BufferToImag对象--该对象随后用于把每一个抓到帧转换成一幅图像。
// 全局变量 private FrameGrabbingControl fg; //帧抓取器 private BufferToImage bufferToImage = null; private int width, height; //帧尺寸 private boolean hasBufferToImage() { Buffer buf = fg.grabFrame(); //快照 if (buf == null) { System.out.println("No grabbed frame"); return false; } //存在一个缓冲区,但要检查其是否为空 VideoFormat vf = (VideoFormat) buf.getFormat(); if (vf == null) { System.out.println("No video format"); return false; } System.out.println("Video format: " + vf); //提取图像的大小 width = vf.getSize().width; height = vf.getSize().height;
// 用视频格式初始化bufferToImage bufferToImage = new BufferToImage(vf); return true; } |
这种编码方法的一个小缺点是,第一个视频帧(使得方法hasBufferToImage()返回true)在对象初始化后被放弃。该帧没有被转化为BufferedImage 并为JMFMovieScreen所用。
快照
JMFSnapper类中最主要的公共方法是getFrame(),它被周期性调用以取得正播放的动画的当前帧。
// 全局变量 private BufferedImage formatImg; // 帧图像
synchronized public BufferedImage getFrame() { //以缓冲对象形式抓取当前帧 Buffer buf = fg.grabFrame(); if (buf == null) { System.out.println("No grabbed buffer"); return null; }
//把缓冲区数据转换成图像 Image im = bufferToImage.createImage(buf); if (im == null) { System.out.println("No grabbed image"); return null; }
//把该图像转换成一个缓冲图像 Graphics g = formatImg.getGraphics(); g.drawImage(im, 0, 0,FORMAT_SIZE, FORMAT_SIZE, null);
// Overlay current time on top of the image g.setColor(Color.RED); g.setFont(new Font("Helvetica",Font.BOLD,12)); g.drawString(timeNow(), 5, 14); g.dispose(); return formatImg; } //结束getFrame() |
方法getFrame()和closeMovie()在JMFSnapper中都被同步处理。closeMovie()用于终止播放器,可以在任何时候调用。同步的目的是,为了确保提取一帧时,播放器不能被关闭。
缓冲图像对象formatImg在JMFSnapper的构造器中被初始化:
formatImg = new BufferedImage( FORMAT_SIZE, FORMAT_SIZE, BufferedImage.TYPE_3BYTE_BGR); |
6.抓取帧的另外一些方法
Sun的JMF示例站点提供了从动画中抓取帧的另外两种方法。
VideoRenderer接口
DemoJMFJ3D 示例结合了Java 3D和JMF两种技术,它显示了怎样把一个视频包装到一个圆柱体上。
例中的Java 3D部分其实与我前面讨论的技术相同-一个使用BufferedImage.TYPE_3BYTE_BGR格式的BufferedImage被传送给一个ImageComponent2D对象,然后该图像变成了圆柱体的纹理。该图像还可以使用BufferedImage.TYPE_4BYTE_ABGR格式,该格式在Solaris系统上使用以支持纹理的引用。
例中的JMF部分与我采用的技术有很大不同。一个JMF的VideoRenderer接口的实现被附着到动画视频轨道的TrackControl对象上。一旦TrackControl对象被启动,对于在视频中出现的每个帧VideoRenderer接口的process()方法被自动调用。process()方法的输入参数是缓冲区对象(也就是被抓取的帧)。不是采用我描述的Buffer-to-BufferedImage转换技术,DemoJMFJ3D是通过在缓冲区的原始数据和BufferedImage的像素映射之间执行一种低级的字节数组复制技术来构造实现的BufferedImage。
处理机Codec(多媒体数字信息编解码器)插件
FrameAccess示例中利用了一些高级的JMF成分,主要围绕着一个处理机Codec插件。
Processor类是Player的一个扩展版本,在处理媒体数据方面它提供了更多的能力。一个codec插件(其实是一个JMF接口Codec的执行)能够从轨道中读取帧,并对之进行任意的处理然后把它们写回到轨道中。特别地,每当在轨道中遇到一帧时,Codec的process()方法即被调用。该方法被提供给一个存有输入帧的缓冲对象和一个空缓冲对象用作输出。
FrameAccess例中把一个Codec插件附加到动画的视频轨道上,并使用传递到函数process()的输入帧缓冲对象来生成一些基本的有关该视频的统计数据。你可以轻易地修改这个例子以把缓冲对象转化成一个缓冲图像,为此,你既可以使用我介绍的方法也可以利用DemoJMFJ3D中的字节数组技术。
遗憾的是,要实现插件支持,Processor类不是必需的。结果,插件在JMF 1.0和基于2.0版本的一些JMF中并不工作。
在使用Sun的JMF示例前先搜一下JMF-兴趣邮件列表是个不错的注意,因为其中许多的程序都存在JMF版本不同带来的一些问题。下文将介绍该动画程序的另外一个版本-其中使用了Quicktime for Java技术。
[上一页] [1] [2] [3]