Java媒體框架(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。 
● JDK1.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
  [source=com.sun.media.content.video.mpeg.Handler@71bb78,
  previous=Unrealized,
  current=Realizing,
  target=Started]
Open log file: C:/test/Java/JMF/JMF/jmf.log
javax.media.DurationUpdateEvent
  [source=com.sun.media.content.video.mpeg.Handler@71bb78,duration=
  javax.media.Time@2a37a6
javax.media.RealizeCompleteEvent
[source=com.sun.media.content.video.mpeg.Handler@71bb78,
  previous=Realizing,
  current=Realized,
  target=Started]
Adding visual component
Adding control panel
javax.media.TransitionEvent
  [source=com.sun.media.content.video.mpeg.Handler@71bb78,
  previous=Realized,
  current=Prefetching,
  target=Started]
javax.media.PrefetchCompleteEvent
  [source=com.sun.media.content.video.mpeg.Handler@71bb78,
  previous=Prefetching,
  current=Prefetched,target=Started]
javax.media.StartEvent
  [source=com.sun.media.content.video.mpeg.Handler@71bb78,
  previous=Prefetched,
  current=Started,
  target=Started,
  mediaTime=javax.media.Time@56a05e,timeBaseTime=
  javax.media.Time@3a8602]
javax.media.EndOfMediaEvent
  [source=com.sun.media.content.video.mpeg.Handler@71bb78,
  previous=Started,
  current=Prefetched,
  target=Prefetched,
  mediaTime=javax.media.Time@1d332b]
 

前面提到,當調用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 &#38;&#38; 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註冊設備了。



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章