利用JMF進行多媒體程式設計
JAVA媒體架構(JMF)使你能夠編寫出功能強大的多媒體程式,卻不用關心底層複雜的實作細節。JMF API的使用相對比較簡單,但是能夠滿足幾乎所有多媒體程式設計的需求。在這篇文章中,我将向你介紹如何用很少的代碼就編寫出多媒體程式。
JAVA多媒體架構(JMF)中包含了許多用于處理多媒體的API。它是一個相當複雜的系統,完全了解這個系統可能需要花上幾周的時間,但是這篇文章将主要介紹JMF的幾個核心接口和類,然後通過一個簡單的例子向你展示如何利用該接口進行程式設計。
JMF目前的最新版本是2.1,Sun通過它向JAVA中引入處理多媒體的能力。下面是JMF所支援的功能的一個概述:
● 可以在JAVA Applet和應用程式中播放各種媒體檔案,例如AU、AVI、MIDI、MPEG、QuickTime和WAV等檔案。
● 可以播放從網際網路上下載下傳的媒體流。
● 可以利用麥克風和錄影機一類的裝置截取音頻和視訊,并儲存成多媒體檔案。
● 處理多媒體檔案,轉換檔案格式。
● 向網際網路上傳音頻和視訊資料流。
● 在網際網路上廣播音頻和視訊資料。
JMF的結構
為了更好地說明JMF的結構,讓我們用立體聲音響做一個簡單的比喻。當你CD機播放CD唱片的時候,CD唱片向系統提供音樂信号。這些資料是在錄音棚中用麥克風和其他類似的裝置記錄下來的。CD播放機将音樂信号傳送到系統的音箱上。在這個例子中,麥克風就是一個音頻截取裝置,CD唱片是資料源,而音箱是輸出裝置。
JMF的結構和立體聲音響系統非常相似,在後面的文章中,你會遇到下面的這些術語:
● 資料源(Data source)
● 截取裝置(Capture Device,包括視訊和音頻截取裝置)
● 播放器(Player)
● 處理器(Processor)
● 資料格式(Format)
● 管理器(Manager)
下面讓我們來看一看這些術語到底代表什麼意思。
1.資料源
就像CD中儲存了歌曲一樣,資料源中包含了媒體資料流。在JMF中,DataSource對象就是資料源,它可以是一個多媒體檔案,也可以是從網際網路上下載下傳的資料流。對于DataSource對象,一旦你确定了它的位置和類型,對象中就包含了多媒體的位置資訊和能夠播放該多媒體的軟體資訊。當建立了DataSource對象後,可以将它送入Player對象中,而Player對象不需要關心DataSource中的多媒體是如何獲得的,以及格式是什麼。
在某些情況下,你需要将多個資料源合并成一個資料源。例如當你在制作一段錄像時,你需要将音頻資料源和視訊資料源合并在一起。JMF支援資料源合并,在後面的例子中我們将提到這一點。
2.截取裝置
截取裝置指的是可以截取到音頻或視訊資料的硬體,如麥克風、錄影機等。截取到的資料可以被送入Player對象中進行處理。
3.播放器
在JMF中對應播放器的接口是Player。Player對象将音頻/視訊資料流作為輸入,然後将資料流輸出到音箱或螢幕上,就像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對象就進入了該狀态并播放多媒體。
4.處理器
處理器對應的接口是Processor,它一種播放器。在JMF API中,Processor接口繼承了Player接口。 Processor對象除了支援支援Player對象支援的所有功能,還可以控制對于輸入的多媒體資料流進行何種處理以及通過資料源向其他的Player對象或Processor對象輸出資料。
除了在播放器中提到了六種狀态外,Processor 對象還包括兩種新的狀态,這兩種狀态是在Unrealized狀态之後,但是在Realizing狀态之前。
● Configuring:當調用configure()方法後,Processor對象進入該狀态。在該狀态下,Processor對象連接配接到資料源并擷取輸入資料的格式資訊。
● Configured:當完成資料源連接配接,獲得輸入資料格式的資訊後,Processor對象就處于Configured狀态。
5.資料格式
Format對象中儲存了多媒體的格式資訊。該對象中本身沒有記錄多媒體編碼的相關資訊,但是它儲存了編碼的名稱。Format的子類包括AudioFormat和VideoFormat類,ViedeoFomat又有六個子類:H261Format、H263Format、IndexedColorFormat、JPEGFormat、RGBFormat和YUVFormat類。
6.管理器
JMF提供了下面四種管理器:
● Manager:Manager相當于兩個類之間的接口。例如當你需要播放一個DataSource對象,你可以通過使用Manager對象建立一個Player對象來播放它。使用Manager對象可以建立Player、Processor、DataSource和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 2000下編寫和測試,是以文章中提到的作業系統需要的軟體都是與Windows有關的。雖然JAVA是跨平台的,但是JMF是個例外——并不是所有的平台上都實作了JMF。
硬體和軟體要求
硬體方面你需要與SoundBlaster相容的聲霸卡,晶片最好使用奔騰III以上的晶片。記憶體最好不小于64MB。同時你需要安裝下面的軟體:
● Windows95/98,Windows NT 4.0, Windows2000或 WindowsXP。
● JDK 1.1.6 或以上的Windows版本。
● JMF類和動态庫
在Windows下安裝JMF2.1
當下載下傳了JMF2.1以後,運作jmf-2_1_1b-windows-i586.exe。該程式會将JMF2.1安裝到你指定的目錄下。當安裝成功後,你需要确認一下安裝程式正确設定了CLASSPATH和PATH環境變量。在CLASSPATH中需要包含jmf.jar和sound.jar;在PATH中需要包含JMF動态庫的路徑。
JMFRegistry
如果你希望使用視訊和音頻截取的裝置,你需要确認安裝了這些裝置的驅動程式。除此之外,你還需要運作JMFRegistry應用程式。JMFRegistry可以向JMF注冊新的資料源、媒體處理器、插件、視訊和音頻截取裝置,然後你才能夠在你的程式中使用它們。你隻需要運作一次JMFRegistry就能注冊系統中所有的視訊和音頻截取裝置。
當你運作了JMFRegistry後,會彈出圖一所示的視窗:
圖一 通過JMFRegistry注冊視訊和音頻截取裝置
選擇“Capture Devices”标簽,然後按下“Detect Capture Devices”按鈕,程式将自動檢測出系統中的視訊和音頻截取裝置。在左邊的類表框中會列出所有檢測到的裝置的名稱。在圖一中我們看到JMFRegsitery發現了JavaSound audio capture、vfw:Logitech USB Video Camera:0和vfw:Microsoft WDM Image Capture (Win32):1。單擊某個裝置可以看到該裝置支援的視訊或音頻格式。如果JMFRegistry無法檢測到裝置,有可能是沒有正常安裝裝置的驅動程式。
例子程式
由于JMF2.1比較複雜,我不可能在在例子中包含JMF2.1支援的所有功能。是以我選擇了下面幾個在JMF中比較常用的功能:播放多媒體、注冊音頻和視訊截取裝置、截取視訊和音頻。
1.播放多媒體
在JMF.java中有一個play()方法。該方法可以播放使用者選擇的多媒體檔案。當播放多媒體檔案時,你需要一個Player對象。在例子中,dualPlayer就是Player接口的實作對象。
Player dualPlayer; |
在Play()方法中,通過使用FileDialog獲得媒體檔案的路徑和檔案名,并儲存在filename中。
try { FileDialog fd = new FileDialog(this, "Select File", FileDialog.LOAD); fd.show(); String filename = fd.getDirectory() + fd.getFile(); ... } catch (Exception e) { System.out.println(e.toString()); } |
然後你需要通過媒體管理器Manager間接建立一個Player對象。你可以使用Manager類的createPlayer()方法或者createProcessor()方法來獲得一個Player對象或Processor對象。在play()方法中,我使用的是createPlayer()方法。
dualPlayer = Manager.createPlayer (new MediaLocator("file:///" + filename)); |
有時你需要使用一個Player對象來控制多個其他的Player和Controller對象,我們把這個Player對象稱為主對象,并把這些對象組成一個組。通過調用主對象中的start()、stop()、setMediaTime()等方法就可以激活組中所有成員的相應方法。主對象控制所有的狀态變化和事件釋出。然後使用addControllerListerner()方法來将一個ControllerListener對象綁定到Player對象上,Controller對象将向該ControllerListener對象發送事件消息。
dualPlayer.addControllerListener(this); |
最後需要調用start()方法來啟動Player對象。start()方法将Player對象的狀态設定為Started。如果Player沒有被實體化(Realize)或預取(Prefetch),start()方法會自動執行這些操作。
dualPlayer.start(); |
由于JMF類實作了ControllerLister接口,是以需要實作該接口中的controllerUpdate()方法,該方法在Controller對象産生一個事件時被調用。
public synchronized void controllerUpdate(ControllerEvent event) { if (event instanceof RealizeCompleteEvent) { Component comp; if ((comp = dualPlayer.getVisualComponent()) != null) add ("Center", comp); if ((comp = dualPlayer.getControlPanelComponent()) != null) add("South", comp); validate(); } } |
當JMF類産生了一個RealizeCompleteEvent事件後,controllerUpdate()方法在界面上增加兩個Component對象,一個用于播放媒體,一個用于放置控制按鈕,例如播放、停止等。
在運作程式的過程中,程式會産生下面的輸出。
Starting player ...javax.media.TransitionEvent [[email protected], previous=Unrealized, current=Realizing, target=Started] Open log file: C:/test/Java/JMF/JMF/jmf.log javax.media.DurationUpdateEvent [s[email protected],duration= javax.media.Time@ 2a 37a 6 javax.media.RealizeCompleteEvent [[email protected], previous=Realizing, current=Realized, target=Started] Adding visual component Adding control panel javax.media.TransitionEvent [[email protected], previous=Realized, current=Prefetching, target=Started] javax.media.PrefetchCompleteEvent [[email protected], previous=Prefetching, current=Prefetched,target=Started] javax.media.StartEvent [[email protected], previous=Prefetched, current=Started, target=Started, mediaTime=javax.media.Time@ 56a 05e,timeBaseTime= javax.media.Time@ 3a 8602] javax.media.EndOfMediaEvent [[email protected], previous=Started, current=Prefetched, target=Prefetched, [email protected]] |
前面提到,當調用start()方法的時候,Player會切換到Started狀态。從上面列出的資訊中可以看到Player對象的狀态從Unrealized變成了Started。當EndOfMedia事件被激活時(這時Player對象完成了媒體檔案的播放),狀态從Started變成了Prefetched。圖二顯示了程式正在播放多媒體檔案時的情況。
圖二 程式正在播放媒體檔案
2.注冊音頻和視訊截取裝置
在例子中,注冊音頻和視訊截取裝置的方法隻在程式的内部注冊這些裝置,在程式外則不起作用。該方法的作用是當使用者的計算機上存在多和音頻和視訊截取裝置時,告訴程式因該使用哪個裝置和這些裝置支援的音頻和視訊格式。是以在進行截取處理之前需要獲得裝置的配置資訊。在例子中,當在Configure菜單上按下Capture Device指令後,會彈出CaptureDeviceDialog對話框。如果在截取音頻或視訊前沒有設定裝置的配置,也會彈出該對話框。圖三顯示了該對話框。
圖三 裝置注冊對話框
讓我們來看一下CaptureDeviceDialog類中的init()方法:在初始化了界面之後,通過調用CaptureDeviceManager類的getDeviceList()方法:
devices = CaptureDeviceManager.getDeviceList ( null ); |
CaptureDeviceManager類使用查詢機制和一個系統資料庫來定位裝置,然後将裝置的資訊放入CaptureDeviceInfo對象中傳回。我們還可以利用CaptureDeviceManager類來注冊新的裝置。通過調用getDeviceList()方法程式擷取了一個支援指定格式的裝置的清單。在例子中,我将格式參數設定為null,這意味着裝置可以使用任何格式。傳回值被放入device變量中。如果getDeviceList()方法傳回的是一個非空值,程式會将包含在其中的音頻裝置名稱和視訊裝置名稱分别放入兩個下拉清單中中,但是到目前為止我們還不知道哪些裝置是音頻裝置,哪些是視訊裝置。
我們可以通過CaptureDeviceInfo的getFormat()方法獲得Format對象組數,在Format對象中儲存了裝置支援的媒體格式。Format類間接被AudioFormat和VideoFormat類所繼承。是以我們可以利用裝置支援的格式類型來區分裝置的類型:
if (devices!=null && devices.size()>0) { int deviceCount = devices.size(); audioDevices = new Vector(); videoDevices = new Vector(); Format[] formats; for ( int i = 0; i < deviceCount; i++ ) { cdi = (CaptureDeviceInfo) devices.elementAt ( i ); formats = cdi.getFormats(); for ( int j=0; j<formats.length; j++ ) { if ( formats[j] instanceof AudioFormat ) { audioDevices.addElement(cdi); break; } else if (formats[j] instanceof VideoFormat ) { videoDevices.addElement(cdi); break; } } } . . . } |
上面的程式運作後,audioDevices()中将包含所有的音頻裝置,videoDevices()中将儲存所有的視訊裝置。其中cdi是CaptureDeviceInfo對象。然後将裝置名稱填入下拉清單中:
// 将音頻裝置顯示在下拉清單中 for (int i=0; i<audioDevices.size(); i++) { cdi = (CaptureDeviceInfo) audioDevices.elementAt(i); audioDeviceCombo.addItem(cdi.getName()); } // 将視訊裝置顯示在下拉清單中 for (int i=0; i<videoDevices.size(); i++) { cdi = (CaptureDeviceInfo) videoDevices.elementAt(i); videoDeviceCombo.addItem(cdi.getName()); } |
然後程式顯示出目前選中的裝置支援的格式:
displayAudioFormats(); displayVideoFormats(); |
下一步需要擷取使用者選中的音頻裝置和視訊裝置以及它們支援的格式,相關的方法是JMF類中的getAudioDevice()、getVideoDevice()、getAudioFormat()和getVideoFormat()方法。然後将擷取的對象分别儲存到audioCDI,videoCDI,audioFormat和videoFormat中:
audioCDI = cdDialog.getAudioDevice(); if (audioCDI!=null) { audioDeviceName = audioCDI.getName(); System.out.println("Audio Device Name: " + audioDeviceName); } videoCDI = cdDialog.getVideoDevice(); if (videoCDI!=null) { videoDeviceName = videoCDI.getName(); System.out.println("Video Device Name: " + videoDeviceName); } // 獲得選中的多媒體格式 videoFormat = cdDialog.getVideoFormat(); audioFormat = cdDialog.getAudioFormat(); |
3.截取視訊和音頻
使用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 = Manager.createPlayer(videoCDI.getLocator()); audioPlayer = Manager.createPlayer(audioCDI.getLocator()); videoPlayer.addControllerListener(this); videoPlayer.start(); audioPlayer.start(); |
使用這種方法導緻最後獲得了兩個Player對象。我們也可以使用Manager類中的createDataSource()方法從視訊和音頻CaptureDeviceInfo對象(audioCID和videoCDI)中獲得視訊和音頻資料源(DataSource對象),然後調用createMergingDataSource()方法将兩個資料源合并成一個資料源(ds):
DataSource[] dataSources = new DataSource[2]; dataSources[0] = Manager.createDataSource(audioCDI.getLocator()); dataSources[1] = Manager.createDataSource(videoCDI.getLocator()); DataSource ds = Manager.createMergingDataSource(dataSources); |
然後可以使用ds作為createPlayer()方法的參數來獲得一個Player對象dualPlayer。調用addControllerListener()就可以進行播放了。
dualPlayer = Manager.createPlayer(ds); dualPlayer.addControllerListener(this); dualPlayer.start(); |
小結
JAVA多媒體架構是一個很好的多媒體程式設計工具。在這篇文章中我隻是簡單介紹了JMF的一些基本功能。如果有興趣的話可以仔細閱讀一下Sun公司的JAVA網站上提供的JMStudio的例子。在JMStudio中不僅實作了簡單的播放和視訊/音頻截取功能,還實作了從網際網路下載下傳和向網際網路上傳多媒體資料流的功能。而且它還包含了JMFRegistry的源代碼,将相應的代碼移植到你的應用程式中後,你就不需要在運作程式前運作JMFRegistry來向JMF注冊裝置了。
作者簡介:馮睿,2000年畢業于美國Northern Illinois大學電氣工程系,獲碩士學位。随後在New Monics軟體公司工作了一年,其間參加了JAVA虛拟機的開發和優化工作。目前在國内一家GIS公司擔任項目經理,主要從事應急指揮系統的交通GIS系統的開發。