Java媒体框架(JMF)使你能够编写出功能强大的多媒体程序却不用关心底层复杂的实现细节JMF API的使用相对比较简单但是能够满足几乎所有多媒体编程的需求在这篇文章中我将向你介绍如何用很少的代码就编写出多媒体程序 Java多媒体框架(JMF)中包含了许多用于处理多媒体的API它是一个相当复杂的系统完全了解这个系统可能需要花上几周的时间但是这篇文章将主要介绍JMF的几个核心接口和类然后通过一个简单的例子向你展示如何利用该接口进行编程 JMF目前的最新版本是Sun通过它向Java中引入处理多媒体的能力下面是JMF所支持的功能的一个概述 ● 可以在Java Applet和应用程序中播放各种媒体文件例如AUAVIMIDIMPEGQuickTime和WAV等文件 ● 可以播放从互联网上下载的媒体流 ● 可以利用麦克风和摄像机一类的设备截取音频和视频并保存成多媒体文件 ● 处理多媒体文件转换文件格式 ● 向互联网上传音频和视频数据流 ● 在互联网上广播音频和视频数据 JMF的结构为了更好地说明JMF的结构让我们用立体声音响做一个简单的比喻当你CD机播放CD唱片的时候CD唱片向系统提供音乐信号这些数据是在录音棚中用麦克风和其他类似的设备记录下来的CD播放机将音乐信号传送到系统的音箱上在这个例子中麦克风就是一个音频截取设备CD唱片是数据源而音箱是输出设备 JMF的结构和立体声音响系统非常相似在后面的文章中你会遇到下面的这些术语 ● 数据源(Data source) ● 截取设备(Capture Device包括视频和音频截取设备) ● 播放器(Player) ● 处理器(Processor) ● 数据格式(Format) ● 管理器(Manager) 下面让我们来看一看这些术语到底代表什么意思 .数据源 就像CD中保存了歌曲一样数据源中包含了媒体数据流在JMF中DataSource对象就是数据源它可以是一个多媒体文件也可以是从互联网上下载的数据流对于DataSource对象一旦你确定了它的位置和类型对象中就包含了多媒体的位置信息和能够播放该多媒体的软件信息当创建了DataSource对象后可以将它送入Player对象中而Player对象不需要关心DataSource中的多媒体是如何获得的以及格式是什么 在某些情况下你需要将多个数据源合并成一个数据源例如当你在制作一段录像时你需要将音频数据源和视频数据源合并在一起JMF支持数据源合并在后面的例子中我们将提到这一点 .截取设备 截取设备指的是可以截取到音频或视频数据的硬件如麦克风摄像机等截取到的数据可以被送入Player对象中进行处理 .播放器 在JMF中对应播放器的接口是PlayerPlayer对象将音频/视频数据流作为输入然后将数据流输出到音箱或屏幕上就像CD播放机读取CD唱片中的歌曲然后将信号送到音箱上一样Player对象有多种状态JMF中定义了JMF的六种状态在正常情况下Player对象需要经历每个状态然后才能播放多媒体下面是对这些状态的说明 ● Unrealized在这种状态下Player对象已经被实例化但是并不知道它需要播放的多媒体的任何信息 ● Realizing当调用realize()方法时Player对象的状态从Unrealized转变为Realizing在这种状态下Player对象正在确定它需要占用哪些资源 ● Realized在这种状态下Player对象已经确定了它需要哪些资源并且也知道需要播放的多媒体的类型 ● Prefetching当调用prefectch()方法时Player对象的状态从Realized变为Prefetching在该状态下的Player对象正在为播放多媒体做一些准备工作其中包括加载多媒体数据获得需要独占的资源等这个过程被称为预取(Prefetch) ● Prefetched当Player对象完成了预取操作后就到达了该状态 ● Started当调用start()方法后Player对象就进入了该状态并播放多媒体 .处理器 处理器对应的接口是Processor它一种播放器在JMF API中Processor接口继承了Player接口 Processor对象除了支持支持Player对象支持的所有功能还可以控制对于输入的多媒体数据流进行何种处理以及通过数据源向其他的Player对象或Processor对象输出数据 除了在播放器中提到了六种状态外Processor 对象还包括两种新的状态这两种状态是在Unrealized状态之后但是在Realizing状态之前 ● Configuring当调用configure()方法后Processor对象进入该状态在该状态下Processor对象连接到数据源并获取输入数据的格式信息 ● Configured当完成数据源连接获得输入数据格式的信息后Processor对象就处于Configured状态 .数据格式 Format对象中保存了多媒体的格式信息该对象中本身没有记录多媒体编码的相关信息但是它保存了编码的名称Format的子类包括AudioFormat和VideoFormat类ViedeoFomat又有六个子类HFormatHFormatIndexedColorFormatJPEGFormatRGBFormat和YUVFormat类 .管理器 JMF提供了下面四种管理器 ● ManagerManager相当于两个类之间的接口例如当你需要播放一个DataSource对象你可以通过使用Manager对象创建一个Player对象来播放它使用Manager对象可以创建PlayerProcessorDataSource和DataSink对象 ● PackageManager该管理器中保存了JMF类注册信息 ● CaptureDeviceManager该管理器中保存了截取设备的注册信息 ● PlugInManager该管理器中保存了JMF插件的注册信息 创建一个Player对象在JMF编程中最常见的工作就是创建一个Player对象你可以通过Manager类的createPlayer()方法创建Player对象Manager对象使用多媒体的URL或MediaLocator对象来创建Player对象当你获得了一个Player对象后你可以通过调用getVisualComponent()方法得到Player对象的图像部件(Visual Component在图像部件上可以播放多媒体的图像)然后将图像部件加入到应用程序或Applet的界面上Player对象还包含一个控制面板在上面可以控制媒体的播放停止和暂停等 Player类中的很多方法只有在Player对象处于Realized的状态下才会被调用为了保证Player对象已经到达了该状态你需要使用Manager的createRealizePlayer()方法来获得Player对象但是对于start()方法来说你可以在Player对象到达Prefetched状态之前调用它它可以自动将Player的状态转换到Started状态 截取多媒体数据多媒体数据的截取是JMF程序中另一个非常重要的功能你可以按照下面的步骤截取数据 ● 通过查询CaptureDevieceManager获得你希望使用的截取设备 ● 获得设备对应的CaptureDeviceInfo对象 ● 从CaptureDeviecInfo对象中获得MediaLocator对象然后用它创建一个DataSource对象 ● 使用DataSource对象创建Player对象或Processor对象 ● 调用start()方法开始截取多媒体数据 你可以使用CaptureDeviceManager对象获得系统中可用的视频和音频截取设备通过调用getDeviceList()方法你可以获得设备的列表每个设备都对应一个CaptrueDeviceInfo对象也可以通过调用CaptureDevieceManager对象的getDevice()方法来获得特定的CaptureDeviceInfo对象在使用设备截取多媒体数据前还需要从CaptureDeviceInfo对象中获得设备对应的MediaLocator对象然后你可以直接使用MediaLocator来构造Player或Processor的实例也可以用MediaLocator构造一个DataSource对象然后将DataSource对象送入Player或Processor对象中最后调用start()方法来截取多媒体数据 一个JMF例子当你使用JMF进行编程以前你需要安装JMF同时在硬件上也有一些要求由于本文的代码是在Windows 下编写和测试因此文章中提到的操作系统需要的软件都是与Windows有关的虽然Java是跨平台的但是JMF是个例外——并不是所有的平台上都实现了JMF 硬件和软件要求 硬件方面你需要与SoundBlaster兼容的声卡芯片最好使用奔腾III以上的芯片内存最好不小于MB同时你需要安装下面的软件 ● Windows/Windows NT Windows或 WindowsXP ● JDK或以上的Windows版本 ● JMF类和动态库 在Windows下安装JMF 当下载了JMF以后运行jmf__bwindowsiexe该程序会将JMF安装到你指定的目录下当安装成功后你需要确认一下安装程序正确设定了CLASSPATH和PATH环境变量在CLASSPATH中需要包含jmfjar和soundjar在PATH中需要包含JMF动态库的路径 JMFRegistry如果你希望使用视频和音频截取的设备你需要确认安装了这些设备的驱动程序除此之外你还需要运行JMFRegistry应用程序JMFRegistry可以向JMF注册新的数据源媒体处理器插件视频和音频截取设备然后你才能够在你的程序中使用它们你只需要运行一次JMFRegistry就能注册系统中所有的视频和音频截取设备 当你运行了JMFRegistry后会弹出图一所示的窗口 图一 通过JMFRegistry注册视频和音频截取设备选择Capture Devices标签然后按下Detect Capture Devices按钮程序将自动检测出系统中的视频和音频截取设备在左边的类表框中会列出所有检测到的设备的名称在图一中我们看到JMFRegsitery发现了JavaSound audio capturevfw:Logitech USB Video Camera:和vfw:Microsoft WDM Image Capture (Win):单击某个设备可以看到该设备支持的视频或音频格式如果JMFRegistry无法检测到设备有可能是没有正常安装设备的驱动程序 例子程序由于JMF比较复杂我不可能在在例子中包含JMF支持的所有功能因此我选择了下面几个在JMF中比较常用的功能:播放多媒体注册音频和视频截取设备截取视频和音频 .播放多媒体 在JMFjava中有一个play()方法该方法可以播放用户选择的多媒体文件当播放多媒体文件时你需要一个Player对象在例子中dualPlayer就是Player接口的实现对象 Player dualPlayer;
在Play()方法中通过使用FileDialog获得媒体文件的路径和文件名并保存在filename中 try { FileDialog fd = new FileDialog(this "Select File" FileDialogLOAD); fdshow(); String filename = fdgetDirectory() + fdgetFile(); } catch (Exception e) { Systemoutprintln(etoString()); }
然后你需要通过媒体管理器Manager间接创建一个Player对象你可以使用Manager类的createPlayer()方法或者createProcessor()方法来获得一个Player对象或Processor对象在play()方法中我使用的是createPlayer()方法 dualPlayer = ManagercreatePlayer (new MediaLocator("file:///" + filename));
有时你需要使用一个Player对象来控制多个其他的Player和Controller对象我们把这个Player对象称为主对象并把这些对象组成一个组通过调用主对象中的start()stop()setMediaTime()等方法就可以激活组中所有成员的相应方法主对象控制所有的状态变化和事件发布然后使用addControllerListerner()方法来将一个ControllerListener对象绑定到Player对象上Controller对象将向该ControllerListener对象发送事件消息 dualPlayeraddControllerListener(this);
最后需要调用start()方法来启动Player对象start()方法将Player对象的状态设置为Started如果Player没有被实体化(Realize)或预取(Prefetch)start()方法会自动执行这些操作 dualPlayerstart();
由于JMF类实现了ControllerLister接口因此需要实现该接口中的controllerUpdate()方法该方法在Controller对象产生一个事件时被调用 public synchronized void controllerUpdate(ControllerEvent event) { if (event instanceof RealizeCompleteEvent) { Component comp; if ((comp = dualPlayergetVisualComponent()) != null) add ("Center" comp); if ((comp = dualPlayergetControlPanelComponent()) != null) add("South" comp); validate(); }}
当JMF类产生了一个RealizeCompleteEvent事件后controllerUpdate()方法在界面上增加两个Component对象一个用于播放媒体一个用于放置控制按钮例如播放停止等 在运行程序的过程中程序会产生下面的输出 Starting player diaTransitionEvent [source=ntentvideompegHandler@bb previous=Unrealized current=Realizing target=Started]Open log file: C:\test\Java\JMF\JMF\jmflogdiaDurationUpdateEvent [source=ntentvideompegHandler@bbduration= diaTime@aadiaRealizeCompleteEvent[source=ntentvideompegHandler@bb previous=Realizing current=Realized target=Started]Adding visual componentAdding control paneldiaTransitionEvent [source=ntentvideompegHandler@bb previous=Realized current=Prefetching target=Started]diaPrefetchCompleteEvent [source=ntentvideompegHandler@bb previous=Prefetching current=Prefetchedtarget=Started]diaStartEvent [source=ntentvideompegHandler@bb previous=Prefetched current=Started target=Started mediaTime=diaTime@aetimeBaseTime= diaTime@a]diaEndOfMediaEvent [source=ntentvideompegHandler@bb previous=Started current=Prefetched target=Prefetched mediaTime=diaTime@db]
前面提到当调用start()方法的时候Player会切换到Started状态从上面列出的信息中可以看到Player对象的状态从Unrealized变成了Started当EndOfMedia事件被激活时(这时Player对象完成了媒体文件的播放)状态从Started变成了Prefetched图二显示了程序正在播放多媒体文件时的情况 图二 程序正在播放媒体文件.注册音频和视频截取设备 在例子中注册音频和视频截取设备的方法只在程序的内部注册这些设备在程序外则不起作用该方法的作用是当用户的计算机上存在多和音频和视频截取设备时告诉程序因该使用哪个设备和这些设备支持的音频和视频格式因此在进行截取处理之前需要获得设备的配置信息在例子中当在Configure菜单上按下Capture Device命令后会弹出CaptureDeviceDialog对话框如果在截取音频或视频前没有设定设备的配置也会弹出该对话框图三显示了该对话框 图三 设备注册对话框让我们来看一下CaptureDeviceDialog类中的init()方法在初始化了界面之后通过调用CaptureDeviceManager类的getDeviceList()方法 devices = CaptureDeviceManagergetDeviceList ( null );
CaptureDeviceManager类使用查询机制和一个注册表来定位设备然后将设备的信息放入CaptureDeviceInfo对象中返回我们还可以利用CaptureDeviceManager类来注册新的设备通过调用getDeviceList()方法程序获取了一个支持指定格式的设备的列表在例子中我将格式参数设定为null这意味着设备可以使用任何格式返回值被放入device变量中如果getDeviceList()方法返回的是一个非空值程序会将包含在其中的音频设备名称和视频设备名称分别放入两个下拉列表中中但是到目前为止我们还不知道哪些设备是音频设备哪些是视频设备 我们可以通过CaptureDeviceInfo的getFormat()方法获得Format对象组数在Format对象中保存了设备支持的媒体格式Format类间接被AudioFormat和VideoFormat类所继承因此我们可以利用设备支持的格式类型来区分设备的类型 if (devices!=null &#;&#; devicessize()>) { int deviceCount = devicessize(); audioDevices = new Vector(); videoDevices = new Vector(); Format[] formats; for ( int i = ; i < deviceCount; i++ ) { cdi = (CaptureDeviceInfo) deviceselementAt ( i ); formats = cdigetFormats(); for ( int j=; j<formatslength; j++ ) { if ( formats[j] instanceof AudioFormat ) { audioDevicesaddElement(cdi); break; } else if (formats[j] instanceof VideoFormat ) { videoDevicesaddElement(cdi); break; } } } }
上面的程序运行后audioDevices()中将包含所有的音频设备videoDevices()中将保存所有的视频设备其中cdi是CaptureDeviceInfo对象然后将设备名称填入下拉列表中 // 将音频设备显示在下拉列表中 for (int i=; i<audioDevicessize(); i++) { cdi = (CaptureDeviceInfo) audioDeviceselementAt(i); audioDeviceComboaddItem(cdigetName()); } // 将视频设备显示在下拉列表中 for (int i=; i<videoDevicessize(); i++) { cdi = (CaptureDeviceInfo) videoDeviceselementAt(i); videoDeviceComboaddItem(cdigetName()); }
然后程序显示出当前选中的设备支持的格式 displayAudioFormats(); displayVideoFormats();
下一步需要获取用户选中的音频设备和视频设备以及它们支持的格式相关的方法是JMF类中的getAudioDevice()getVideoDevice()getAudioFormat()和getVideoFormat()方法然后将获取的对象分别保存到audioCDIvideoCDIaudioFormat和videoFormat中 audioCDI = cdDialoggetAudioDevice(); if (audioCDI!=null) { audioDeviceName = audioCDIgetName(); Systemoutprintln("Audio Device Name: " + audioDeviceName); } videoCDI = cdDialoggetVideoDevice(); if (videoCDI!=null) { videoDeviceName = videoCDIgetName(); Systemoutprintln("Video Device Name: " + videoDeviceName); } // 获得选中的多媒体格式 videoFormat = cdDialoggetVideoFormat(); audioFormat = cdDialoggetAudioFormat();
.截取视频和音频 使用capture()方法可以截取音频和视频数据但是在使用该方法前需要确定是否已经选中了视频和音频截取设备 if (audioCDI==null &#;&#; videoCDI==null) registerDevices();
和play()方法类似可以通过使用Manger类中的静态方法createPlayer()创建一个Player对象该对象可以播放一个DataSource对象中的数据流 Player createPlayer(MediaLocator sourceLocator)
在例子中我首先通过调用audioCDI和videoCDI的getLocator()方法来获得MediaLocator对象然后利用Manager类的createPlayer()方法创建Player对象最后将一个ControllerListener对象绑定到视频Player对象上并开始播放 videoPlayer = ManagercreatePlayer(videoCDIgetLocator()); audioPlayer = ManagercreatePlayer(audioCDIgetLocator()); videoPlayeraddControllerListener(this); videoPlayerstart(); audioPlayerstart();
使用这种方法导致最后获得了两个Player对象我们也可以使用Manager类中的createDataSource()方法从视频和音频CaptureDeviceInfo对象(audioCID和videoCDI)中获得视频和音频数据源(DataSource对象)然后调用createMergingDataSource()方法将两个数据源合并成一个数据源(ds) DataSource[] dataSources = new DataSource[]; dataSources[] = ManagercreateDataSource(audioCDIgetLocator()); dataSources[] = ManagercreateDataSource(videoCDIgetLocator()); DataSource ds = ManagercreateMergingDataSource(dataSources);
然后可以使用ds作为createPlayer()方法的参数来获得一个Player对象dualPlayer调用addControllerListener()就可以进行播放了 dualPlayer = ManagercreatePlayer(ds);dualPlayeraddControllerListener(this);dualPlayerstart(); 小结Java多媒体框架是一个很好的多媒体编程工具在这篇文章中我只是简单介绍了JMF的一些基本功能如果有兴趣的话可以仔细阅读一下Sun公司的Java网站上提供的JMStudio的例子在JMStudio中不仅实现了简单的播放和视频/音频截取功能还实现了从互联网下载和向互联网上传多媒体数据流的功能而且它还包含了JMFRegistry的源代码将相应的代码移植到你的应用程序中后你就不需要在运行程序前运行JMFRegistry来向JMF注册设备了 作者简介冯睿年毕业于美国Northern Illinois大学电气工程系获硕士学位随后在New Monics软件公司工作了一年其间参加了Java虚拟机的开发和优化工作目前在国内一家GIS公司担任项目经理主要从事应急指挥系统的交通GIS系统的开发 |