[轉(zhuǎn)] 技術(shù)交流:QuickTime流媒體和Java(圖)
這并不是即將問世的QuickTime for Java book一書的摘錄,雖然我希望是的。
你看,問題是,在QTJ世界中大多數(shù)的我們都一直假定QTJ中的流媒體API已經(jīng)損壞,我并不是想為此事而掩蓋什么。好的,我繼續(xù)來通過各種各樣的人通過不同的技術(shù)進行工作的掩蓋獲取去這樣做,但是我不想再來一次。此外,流媒體沖突的情況似乎特別的糟糕。沒有人能得到它的演示代碼工作方式――this post to the quicktime-java list 是令許多用戶對獲取蘋果公司的AudioBroadcaster 和DrawableBroadcaster 演示工作方式絕望的典型。讓事情更糟糕,演示之一依靠一個在QTJ 6.1版本中作為退回到原始的GUI提供的已經(jīng)被取消的GUI預(yù)覽組件類,提供組件只對Movies ,MovieControllers 和GraphicsImporters ,而不是流式的Presentations ,視頻捕捉,或者某些圖形美好得像來自多種資源“合成”制作。所以,官方給出的演示它首先看起來是不會工作,和現(xiàn)在的關(guān)鍵類有沖突(如果在Java 1.4中運行會拋出RuntimeExceptions異常)。預(yù)測實際的流式內(nèi)容和QTJ 6.1看上去會非常糟糕。
令我欣喜,甚至是有點吃驚的是,有報道稱流媒體能夠在QTJ 6.1中工作。在本文章中,我將介紹通過QTJ實現(xiàn)簡單的網(wǎng)絡(luò)廣播的基礎(chǔ)
需求
QuickTime的流媒體API,在Java中由只可在Mac OS(Classic和OS X)中運行的包quicktime.streaming聲明。在QTJ中存在Windows版本的類,但是它們卻不能工作。但是,你可以使用Windows版本的QuickTime作為流媒體的客戶端,如果在Java中運行并不是關(guān)鍵的,你可獲取Darwin Streaming Server,一個開源項目可在Windows 2000 Server 和2003 Server上運行如同在Solaris 9 和 Red Hat Linux 9上一樣。
使用QuickTime流媒體最簡單的事情就是我在此說明的生動內(nèi)容。你需要至少一個音頻輸入設(shè)備,如一個內(nèi)置的麥克風(fēng)或者一個耳機。當(dāng)然,擁有一個QuickTime支持的攝像機,如一個iSight,將會更讓人印象深刻。
Streaming 是什么,不是什么
給出了術(shù)語“流”的含義并不容易明確術(shù)語“Streaming ”的正確含義。例如,QuickTime長期支持一種“快速啟動”的特征――如果QuickTime明確擁有足夠的開始播放的資源并且不會用完目前下載速率下的資源一段錄像能開始播放――那是一些用戶將Streaming 的一種形式弄錯了。自然的,這有它自己的優(yōu)勢:容易創(chuàng)建并且確保了所有的包都到達了客戶端。但是真正的 Streaming ,換句話說,Streaming 符合Internet工程工作小組(IETF)的標(biāo)準(zhǔn),這是一個完全不同的問題,直到QuickTime 5才被支持,并且直到QTJ 6才支持Java。
Streaming 的形式允許服務(wù)器控制傳輸,但很難在實時中保證最優(yōu)化運行。客戶端未下載潛在的大文件,這樣的方法是獨特的便利的直接廣播。事實上,QuickTime的流媒體使用兩種“實時”的流媒體傳輸協(xié)議:實時傳輸協(xié)議(RTP)來傳輸媒體數(shù)據(jù)包,實時流協(xié)議(RTSP)用于控制信息。RTP使用潛在的有損UDP連接,所以所有的人都有意的忍耐再傳輸期間的包的丟失。這就意味著客戶端需要友好的操作未獲取所有數(shù)據(jù)的視頻幀或者音頻例子。更好的方法是通過基于TCP/IP的連接,它可以使用不確定的重試(也會因此需要一個不確定的時間)來獲取丟失的包。
Presentation和SDP文件
在QuickTime中,流媒體傳輸?shù)韧粋€電影――一個電影可以有音軌和視軌,一個元數(shù)據(jù)的聚集將它們?nèi)悸?lián)系在一起,此表示會將一些多種的音頻和視頻流的元數(shù)據(jù)聯(lián)系起來。音頻和視頻你非常喜歡的流媒體種類也是值得關(guān)注的,自從某些其他的媒體類型(sprites,F(xiàn)lash內(nèi)容)被QuickTime支持后并沒有操作好失去的包,并不適合作為流媒體傳播。
你可能被建議去建立一個流媒體,你會需要創(chuàng)建一個Presentation 并開始它。但是現(xiàn)在呢?最普遍的這樣做的方法是創(chuàng)建一個會話描述協(xié)議(SDP)文件,將其放入靜態(tài)工廠方法Presentation.fromFile()。SDP文件以一種適當(dāng)?shù)暮唵蔚奈谋靖袷剑蒖FC 2327和several updates所定義。我發(fā)現(xiàn)這些都是早期的理論而不是實際操作,但是稍后讓我們擔(dān)心的是執(zhí)行詳細信息。這是一個被一些Apple的流媒體使用的例子,在Tim Monroe的QuickTime Toolkit Volume Two中:
v=0
c=IN IP4 224.2.1.2/20/1
m=audio 1000 RTP/AVP 12
m=video 2000 RTP/AVP 101
a=rtpmap:101 H263-1998
以下是每一行的解釋:
·v=0:這是SDP的版本號。在這里版本號是0表示在SDP中沒有次要的號碼。
·c=IN IP4 224.2.1.2/20/1:這是提供在描述中使用的連接的信息。IN IP4 表示是一個IPv4的網(wǎng)絡(luò)地址。224.2.1.2是地址(注意這是一個多點傳輸?shù)刂罚性S多的客戶端能連接到廣播),20是存在時間,1是臨近使用的多點傳輸?shù)刂返臄?shù)量。
·m=audio 1000 RTP/AVP 12:m=這一行定義了用于廣播的流媒體。在這里明顯的是audio,發(fā)送經(jīng)由到RTP到端口1000。12在簡單的QCELP音頻中定義了有效負載類型。這些在RFC 3551中定義了。
·m=video 2000 RTP/AVP 101:這一媒體行定義了一個video媒體流,由RTP傳輸?shù)蕉丝?000。有效負載類型為95,所以使用101表示在原始的RFC中視頻格式?jīng)]有給出負載類型,在SDP中替換它會被映射到一個眾所周知的常量中。
·a=rtpmap:101 H263-1998:這個完成鍵入在前一行指出的動態(tài)負載。使用此類型,你會使用一個在96和127之間的值(本例中是101),然后用一個字符串命名此負載類型(H263-1998)。
這當(dāng)然好,但是當(dāng)在我的例程序中使用它的時候,我只獲得了一個視頻流卻沒有聲音。所以,我使用了一個很不同的SDP,最初在QTJ的DrawableBroadcaster演示中出現(xiàn)。是的,他們不贊同這么做:
m=audio 2656 RTP/AVP 96
c=IN IP4 239.60.60.60
a=rtpmap:96 x-qt
m=video 2700 RTP/AVP 96
a=rtpmap:96 x-qt
這里最大的不同就是音頻和視頻都使用了相同的動態(tài)負載映射,這并不是針對一個真正的編碼器,而是一般的x-qt。在這里勝利的是你能在運行時間上挑選任一QuickTime的音頻和視頻編碼器,而不是在SDP文件中強迫導(dǎo)致。底側(cè)是這些可以不是由非QuickTime客戶端可分析的,反之使用十分標(biāo)準(zhǔn)的和/或者舊的編碼器并且在SDP中指定他們使它更像其他的客戶端(Real, JMF等)能夠操作你的系統(tǒng)。
這就是你的SDP文件。現(xiàn)在不要加入一個Presentation。
創(chuàng)建Presentation
我們的流媒體服務(wù)器程序調(diào)用LittleBroadcast,這并沒有多少代碼,只有不過140行。在本文中,我將一步一步的進行,解釋一般的部分,但提供其全部的清單。在后面的Resources章節(jié)中有可用到一個.tar.gz文件,連同SDP文件和一個Ant構(gòu)建文件。
package com.mac.invalidname.qtjstreaming;import quicktime.*;
import quicktime.std.*;
import quicktime.util.*;
import quicktime.qd.*;
import quicktime.io.*;
import quicktime.streaming.*;
import quicktime.app.time.*;
import java.io.*;import java.awt.*;
import java.awt.event.*;
public class LittleBroadcast extends Tasking implements ActionListener {
這是一長串典型的引入QuickTime,包括了使用其QDGraphics來提供一個攝像機畫面以外的圖形界面的qd,讀取SDP文件的io,用于流媒體API的streaming,以及獲得給予Presentation運行時間的有效任務(wù)的time。最后一點,注意該類擴展直Tasking――提供周期性調(diào)用的task()。本應(yīng)用程序中,它用于不斷的調(diào)用Presentation的idle()方法,并使其循環(huán)工作。你在本書中學(xué)習(xí)到也就是Movies所需要的,但是此任務(wù)幾乎一直都自動的為你所操作。使用Presentation并不好運。(或者為此事而捕獲,但有些離開本主題了。)
boolean broadcasting = false; public static final int BROADCAST_WIDTH = 176; public static final int BROADCAST_HEIGHT = 144; Button startStopButton; Button configButton; Presentation pres; int presenterTimeScale = 600;
這些是服務(wù)器的實例變量。是一個用于指定當(dāng)開始/停止按鈕按下的時候做什么的標(biāo)記。下面是一對廣播視頻大小的常量,緊跟著是服務(wù)器GUI的按鈕。最后是一個Presentation對象,以及它的時間尺度。(媒體的保持時間系統(tǒng),一個600的時間尺度表示一秒種里有600個單位;600也是QuickTime中默認的。)
public static void main (String[] args) {
System.out.println ("main");
try {
QTSession.open();
new LittleBroadcast();
} catch (QTException qte) {
qte.printStackTrace();
}
}
在這個main中并沒有什么獨特的地方。我投入了所有的精力在構(gòu)造函數(shù)上以防止為那些我需要的實例創(chuàng)建一個內(nèi)部類。如果你擴展本代碼,你可能會發(fā)現(xiàn)這很有用。
public LittleBroadcast() throws QTException {
System.out.println ("LittleBroadcast constructor");
QTFile file = new QTFile (new File ("little.sdp"));
try {
MediaParams mediaParams = new MediaParams();
mediaParams.setWidth (BROADCAST_WIDTH);
mediaParams.setHeight (BROADCAST_HEIGHT);
QDGraphics myGWorld =
new QDGraphics (new QDRect (
BROADCAST_WIDTH, BROADCAST_HEIGHT));
mediaParams.setGWorld (myGWorld);
PresParams presParams =
new PresParams( presenterTimeScale,
QTSConstants.kQTSSendMediaFlag |
QTSConstants.kQTSAutoModeFlag |
QTSConstants.kQTSDontShowStatusFlag,
mediaParams );
pres = Presentation.fromFile(file, presParams );
構(gòu)造函數(shù)的第一事是裝載名為little.sdp的SDP文件。 但這并不是所有的都需要創(chuàng)建Presentation ――在調(diào)用Presentation.fromFile()的時候需要服務(wù)器應(yīng)用程序設(shè)置一些必要的參數(shù)。首先,你要創(chuàng)建一個MediaParams對象,這樣你能設(shè)置視頻的高度和寬度。您必須做的其它重要事是提供照相機一個圖形界面,由QDGraphics創(chuàng)建MediaParams設(shè)置。是的, 名字是古怪的, 因為QTJ 設(shè)計員想注重與AWT Graphics對象的相似性, 但得到或設(shè)置這樣的對象的用途的所有方法是使用其本地API名字, GWorld。 最后, 你為所有的Presentation創(chuàng)建一個PresParams來設(shè)置參數(shù)。 這采取一個有些任意的時標(biāo), 一些算術(shù)上的行為標(biāo)記彼此OR'ed, 以及MediaParams。 可能的行為標(biāo)記, 都被定義在QTSConstants, 包括:
·KQTSAutoModeFlag: 都使用默認值。 最重要地, 這些使用默認值Sourcer, Presentation的來源,是從各種各樣的輸入裝置執(zhí)行獲取的SequenceGrabber。 它還可能播放一個在磁盤上或是任意目錄下的的QuickTime 文件; 稍后我將探討這些問題。
·KQTDontShowStatusFlag: 不要創(chuàng)建一個會導(dǎo)致連接數(shù)和狀態(tài)信息總被顯示在客戶端的流媒體狀態(tài)處理程序。
·KQTSSendMediaFlag:發(fā)送,不接收數(shù)據(jù)。
·KQTSReceiveMediaFlag:接收,不發(fā)送數(shù)據(jù)。
在SDP文件說明, 參數(shù), 以及GWorld 設(shè)置下, 創(chuàng)建Presentation和Presentation.fromFile()。
// find audio stream Stream audioStream = null;
for (int i=1; i<=pres.getNumStreams(); i++) {
System.out.println ("stream: " + i + ": " +
pres.getIndStream(i));
Stream aStream = pres.getIndStream (i);
if (pres.hasCharacteristic(aStream,
StdQTConstants.audioMediaCharacteristic)) {
audioStream = aStream;
break;
}
}
System.out.println ("audioStream = " + audioStream);
pres.setVolumes (audioStream, 100, 100);
System.out.println ("created presentation, gworld == " +
pres.getGWorld() + ", size == "+
mediaParams.getWidth() + "x" +
mediaParams.getHeight() + ", streams == " +
pres.getNumStreams());
//*******這不是真正地必要的, 但它將告訴你怎么通過Presentation游覽來挑選各自的流媒體。 Presentation.getIndStream 會由索引返回一個Stream(附注QuickTime 索引都是基于1)。它重復(fù)這些audioMediaCharacteristic 請求查找音頻流 (對于錄影, 您就要請求visualMediaCharacteristic) 。 這個實例在audioStream上為左右聲道設(shè)置音量最大值為100。
最后, println從Presentation和MediaParams轉(zhuǎn)存一些有意義的元數(shù)據(jù)。
配置Presentation
SettingsDialog sd = new SettingsDialog (pres);
System.out.println ("Did settings");
pres.preroll();
broadcasting = false;
這是設(shè)置presentation最后的步驟。 SettingsDialog存在用戶以輸入裝置選擇的音頻和視頻 (二個流媒體SDP 文件被指定在Presentation中) 。每個流媒體都可由一個壓縮格式來定制 (MPEG-4, Sorenson Video 3, H.263, 等) 以及一個分包器(有時由壓縮格式定義; 可觀察它是否隨著壓縮格式的改變而自動改變) 。 這個GUI實例顯示在圖1 。

圖像1. 為一個Presentation SettingsDialog
在此圖中, 音頻默認為計算機連線輸入。更改它為iSight, 您需要點擊Source按鈕,提出的可選設(shè)備列表顯示在圖2上 。

圖2. 來源選擇對話框
最后就是調(diào)用Presentation.preroll(), 如同Movie.preroll(), 提供Presentation一個機會預(yù)先分配資源以及準(zhǔn)備好開始流媒體Presentation。
提供一個控制GUI
// Make monitor window
startStopButton = new Button ("Start");
configButton = new Button ("Configure");
startStopButton.addActionListener (this);
configButton.addActionListener (this);
Frame monitorFrame = new Frame ("QTJ Streaming");
monitorFrame.setLayout (new BorderLayout());
Panel buttonPanel = new Panel();
buttonPanel.add (startStopButton);
buttonPanel.add (configButton);
monitorFrame.add (buttonPanel, BorderLayout.SOUTH);
monitorFrame.pack();
monitorFrame.setVisible(true);
這個為控制和配置Presentation設(shè)置了很小的GUI,提供基本的一個起始/停止鍵和一個配置按鈕。 按鈕作為一個ActionListener提交給this, 意味著這個類將需要提供一個actionPerformed方法來處理按鈕點擊。 控制GUI 的屏幕截圖顯示在圖3 。

圖3. 監(jiān)控/控制窗體
在這點上你也許會問一個有趣的問題: "從什么時候我們開始關(guān)心使用GUI提供server?"據(jù)推測, 這是從Classic Mac OS開始的一個傳統(tǒng), 它沒有一個用命令行啟動和傳遞參數(shù)的程序。但此外, 你通常會希望提供一個流媒體數(shù)據(jù)的預(yù)覽, 并且如果您有一個預(yù)覽視窗, 為什么會也沒有一個配置的GUI?
無論如何, 這是大概的討論, 因為QTJ 6.1 不提供您能使用來預(yù)覽的一個AWT Component。在有些方面有希望的是, QTFactory將得到一個新的超負荷為采用一個Presentation并且返回一個顯示流媒體視頻的Component的makeQTComponent。 它大概可能使用一些QuickDraw voodoo而完全放棄Java的東西。 如果,在各task()中回調(diào)(參見下面) 您采取GWorld被及早創(chuàng)建, 轉(zhuǎn)換它成Pict, 并且作為一份唯一命名的文件, 您會看見每一個都是不同的, 意味GWorld每次都得到新數(shù)據(jù)。 所以如果您替換掉GWorld, 改為能給AWT Component在各通道上定義象素, 您會有銀幕上的預(yù)覽。何人有膽量如此做? 在quicktime-java list上查看。
詳細資料
// add shutdown handler to make sure presentation
// gets stopped
Thread presentationStopper = new Thread() {
public void run() {
try {
pres.stop();
} catch (QTException qte) {}
}
};
Runtime.getRuntime().addShutdownHook (presentationStopper);
這個關(guān)閉異常分支確定Presentation在程序退出之前被終止。 這是重要的原因, 象SequenceGrabber, Presentation愉快繼續(xù)運行在您的應(yīng)用程序退出之后,綁定一個端口,嚴重的循環(huán), 使用您的獲取設(shè)備保留其它應(yīng)用程序, 等。
} catch ( QTException e ) {
e.printStackTrace();
System.exit (-1);
}
}
最后, 構(gòu)造函數(shù)捕捉并拋出所有的QTExceptions。
public void actionPerformed (ActionEvent ae) {
System.out.println ("actionPerformed");
try {
if (ae.getSource() == startStopButton) {
if (broadcasting) {
pres.stop();
stopTasking();
broadcasting = false;
startStopButton.setLabel ("Start");
System.out.println ("Stopped");
} else {
pres.start();
startTasking();
broadcasting = true;
startStopButton.setLabel ("Stop");
System.out.println ("Started");
}
} else if (ae.getSource() == configButton) {
new SettingsDialog (pres);
}
} catch (QTException qte) {
qte.printStackTrace();
}
}
這是非常直接的處理起始/終止和設(shè)置按鈕。如果點擊的按鈕是起始/終止, 設(shè)置GUI 就會調(diào)用在Presentation上的 start() 或stop(), 開始或停止正在執(zhí)行的任務(wù) (定期回調(diào)這個類的task()方法), 為下按鈕點擊目的設(shè)置broadcasting標(biāo)志, 并且更改按鈕標(biāo)簽。如果用戶點擊了配置, 它生成為Presentation新的SettingsDialog。
public synchronized final void task() throws QTException {
pres.idle(null);
}}
最后的這個方法實現(xiàn)了繼承自Tasking的task()方法并且被在操作開始按鈕調(diào)用startTasking()后定時的調(diào)用。使用簡單的調(diào)用Presentation.idle(), 它提供了表達時間來從獲取設(shè)備取得當(dāng)前數(shù)據(jù),對其編碼并傳輸流出去。
運行流媒體客戶端
最簡單的使客戶機看廣播的方法是使用QuickTime 播放器打開服務(wù)器使用并創(chuàng)建Presentation的同樣SDP 文件。這將調(diào)用SDP 輸入程序連接到流媒體并且開始分析這些內(nèi)容。注意客戶機和服務(wù)器不能在同一臺機器上, 明顯地因為服務(wù)器為使用表示而占用端口, 拒絕客戶機對這些端口的使用。 圖4 顯示在我的計算機上的流媒體的外觀(那是正在播放我的Macross和Escaflowne玩具) 。

圖4. QuickTime流媒體客戶端
如果您使用QuickTime 播放器, 您能使用其得到信息指令顯示兩種流媒體以及他們的格式。 在表5, 您能看有二種媒體: 一條未壓縮的44.1kHz 音頻流, 和一條H.263 的視頻流。

圖5. 客戶端信息窗體
結(jié)語
對我來說,播放基于Java的QuickTime流媒體比想象的容易多了。 最簡單的例子, 從獲取設(shè)備播放, 只需要少于150 個代碼行。 顯然, 最困難的部份是了解SDP文件, 它被證明是非常的過分講究并且它的說明文件包含大量應(yīng)用程序級別程序員不會有的知識。同樣不幸的是,QTJ不再提供預(yù)覽組件, 但也許在將來會提供, 以及一小段的GWorld/QuickDraw 堆砌也許會在將來制造出這樣的組件。
本文只包括怎么為實時獲取數(shù)據(jù)設(shè)置廣播。其它可利用的Sourcers, 譬如來自磁盤或任意目錄種的那些廣播QuickTime 文件, 將會在以后的部分中討論。
你看,問題是,在QTJ世界中大多數(shù)的我們都一直假定QTJ中的流媒體API已經(jīng)損壞,我并不是想為此事而掩蓋什么。好的,我繼續(xù)來通過各種各樣的人通過不同的技術(shù)進行工作的掩蓋獲取去這樣做,但是我不想再來一次。此外,流媒體沖突的情況似乎特別的糟糕。沒有人能得到它的演示代碼工作方式――this post to the quicktime-java list 是令許多用戶對獲取蘋果公司的AudioBroadcaster 和DrawableBroadcaster 演示工作方式絕望的典型。讓事情更糟糕,演示之一依靠一個在QTJ 6.1版本中作為退回到原始的GUI提供的已經(jīng)被取消的GUI預(yù)覽組件類,提供組件只對Movies ,MovieControllers 和GraphicsImporters ,而不是流式的Presentations ,視頻捕捉,或者某些圖形美好得像來自多種資源“合成”制作。所以,官方給出的演示它首先看起來是不會工作,和現(xiàn)在的關(guān)鍵類有沖突(如果在Java 1.4中運行會拋出RuntimeExceptions異常)。預(yù)測實際的流式內(nèi)容和QTJ 6.1看上去會非常糟糕。
令我欣喜,甚至是有點吃驚的是,有報道稱流媒體能夠在QTJ 6.1中工作。在本文章中,我將介紹通過QTJ實現(xiàn)簡單的網(wǎng)絡(luò)廣播的基礎(chǔ)
需求
QuickTime的流媒體API,在Java中由只可在Mac OS(Classic和OS X)中運行的包quicktime.streaming聲明。在QTJ中存在Windows版本的類,但是它們卻不能工作。但是,你可以使用Windows版本的QuickTime作為流媒體的客戶端,如果在Java中運行并不是關(guān)鍵的,你可獲取Darwin Streaming Server,一個開源項目可在Windows 2000 Server 和2003 Server上運行如同在Solaris 9 和 Red Hat Linux 9上一樣。
使用QuickTime流媒體最簡單的事情就是我在此說明的生動內(nèi)容。你需要至少一個音頻輸入設(shè)備,如一個內(nèi)置的麥克風(fēng)或者一個耳機。當(dāng)然,擁有一個QuickTime支持的攝像機,如一個iSight,將會更讓人印象深刻。
Streaming 是什么,不是什么
給出了術(shù)語“流”的含義并不容易明確術(shù)語“Streaming ”的正確含義。例如,QuickTime長期支持一種“快速啟動”的特征――如果QuickTime明確擁有足夠的開始播放的資源并且不會用完目前下載速率下的資源一段錄像能開始播放――那是一些用戶將Streaming 的一種形式弄錯了。自然的,這有它自己的優(yōu)勢:容易創(chuàng)建并且確保了所有的包都到達了客戶端。但是真正的 Streaming ,換句話說,Streaming 符合Internet工程工作小組(IETF)的標(biāo)準(zhǔn),這是一個完全不同的問題,直到QuickTime 5才被支持,并且直到QTJ 6才支持Java。
Streaming 的形式允許服務(wù)器控制傳輸,但很難在實時中保證最優(yōu)化運行。客戶端未下載潛在的大文件,這樣的方法是獨特的便利的直接廣播。事實上,QuickTime的流媒體使用兩種“實時”的流媒體傳輸協(xié)議:實時傳輸協(xié)議(RTP)來傳輸媒體數(shù)據(jù)包,實時流協(xié)議(RTSP)用于控制信息。RTP使用潛在的有損UDP連接,所以所有的人都有意的忍耐再傳輸期間的包的丟失。這就意味著客戶端需要友好的操作未獲取所有數(shù)據(jù)的視頻幀或者音頻例子。更好的方法是通過基于TCP/IP的連接,它可以使用不確定的重試(也會因此需要一個不確定的時間)來獲取丟失的包。
Presentation和SDP文件
在QuickTime中,流媒體傳輸?shù)韧粋€電影――一個電影可以有音軌和視軌,一個元數(shù)據(jù)的聚集將它們?nèi)悸?lián)系在一起,此表示會將一些多種的音頻和視頻流的元數(shù)據(jù)聯(lián)系起來。音頻和視頻你非常喜歡的流媒體種類也是值得關(guān)注的,自從某些其他的媒體類型(sprites,F(xiàn)lash內(nèi)容)被QuickTime支持后并沒有操作好失去的包,并不適合作為流媒體傳播。
你可能被建議去建立一個流媒體,你會需要創(chuàng)建一個Presentation 并開始它。但是現(xiàn)在呢?最普遍的這樣做的方法是創(chuàng)建一個會話描述協(xié)議(SDP)文件,將其放入靜態(tài)工廠方法Presentation.fromFile()。SDP文件以一種適當(dāng)?shù)暮唵蔚奈谋靖袷剑蒖FC 2327和several updates所定義。我發(fā)現(xiàn)這些都是早期的理論而不是實際操作,但是稍后讓我們擔(dān)心的是執(zhí)行詳細信息。這是一個被一些Apple的流媒體使用的例子,在Tim Monroe的QuickTime Toolkit Volume Two中:
v=0
c=IN IP4 224.2.1.2/20/1
m=audio 1000 RTP/AVP 12
m=video 2000 RTP/AVP 101
a=rtpmap:101 H263-1998
以下是每一行的解釋:
·v=0:這是SDP的版本號。在這里版本號是0表示在SDP中沒有次要的號碼。
·c=IN IP4 224.2.1.2/20/1:這是提供在描述中使用的連接的信息。IN IP4 表示是一個IPv4的網(wǎng)絡(luò)地址。224.2.1.2是地址(注意這是一個多點傳輸?shù)刂罚性S多的客戶端能連接到廣播),20是存在時間,1是臨近使用的多點傳輸?shù)刂返臄?shù)量。
·m=audio 1000 RTP/AVP 12:m=這一行定義了用于廣播的流媒體。在這里明顯的是audio,發(fā)送經(jīng)由到RTP到端口1000。12在簡單的QCELP音頻中定義了有效負載類型。這些在RFC 3551中定義了。
·m=video 2000 RTP/AVP 101:這一媒體行定義了一個video媒體流,由RTP傳輸?shù)蕉丝?000。有效負載類型為95,所以使用101表示在原始的RFC中視頻格式?jīng)]有給出負載類型,在SDP中替換它會被映射到一個眾所周知的常量中。
·a=rtpmap:101 H263-1998:這個完成鍵入在前一行指出的動態(tài)負載。使用此類型,你會使用一個在96和127之間的值(本例中是101),然后用一個字符串命名此負載類型(H263-1998)。
這當(dāng)然好,但是當(dāng)在我的例程序中使用它的時候,我只獲得了一個視頻流卻沒有聲音。所以,我使用了一個很不同的SDP,最初在QTJ的DrawableBroadcaster演示中出現(xiàn)。是的,他們不贊同這么做:
m=audio 2656 RTP/AVP 96
c=IN IP4 239.60.60.60
a=rtpmap:96 x-qt
m=video 2700 RTP/AVP 96
a=rtpmap:96 x-qt
這里最大的不同就是音頻和視頻都使用了相同的動態(tài)負載映射,這并不是針對一個真正的編碼器,而是一般的x-qt。在這里勝利的是你能在運行時間上挑選任一QuickTime的音頻和視頻編碼器,而不是在SDP文件中強迫導(dǎo)致。底側(cè)是這些可以不是由非QuickTime客戶端可分析的,反之使用十分標(biāo)準(zhǔn)的和/或者舊的編碼器并且在SDP中指定他們使它更像其他的客戶端(Real, JMF等)能夠操作你的系統(tǒng)。
這就是你的SDP文件。現(xiàn)在不要加入一個Presentation。
創(chuàng)建Presentation
我們的流媒體服務(wù)器程序調(diào)用LittleBroadcast,這并沒有多少代碼,只有不過140行。在本文中,我將一步一步的進行,解釋一般的部分,但提供其全部的清單。在后面的Resources章節(jié)中有可用到一個.tar.gz文件,連同SDP文件和一個Ant構(gòu)建文件。
package com.mac.invalidname.qtjstreaming;import quicktime.*;
import quicktime.std.*;
import quicktime.util.*;
import quicktime.qd.*;
import quicktime.io.*;
import quicktime.streaming.*;
import quicktime.app.time.*;
import java.io.*;import java.awt.*;
import java.awt.event.*;
public class LittleBroadcast extends Tasking implements ActionListener {
這是一長串典型的引入QuickTime,包括了使用其QDGraphics來提供一個攝像機畫面以外的圖形界面的qd,讀取SDP文件的io,用于流媒體API的streaming,以及獲得給予Presentation運行時間的有效任務(wù)的time。最后一點,注意該類擴展直Tasking――提供周期性調(diào)用的task()。本應(yīng)用程序中,它用于不斷的調(diào)用Presentation的idle()方法,并使其循環(huán)工作。你在本書中學(xué)習(xí)到也就是Movies所需要的,但是此任務(wù)幾乎一直都自動的為你所操作。使用Presentation并不好運。(或者為此事而捕獲,但有些離開本主題了。)
boolean broadcasting = false; public static final int BROADCAST_WIDTH = 176; public static final int BROADCAST_HEIGHT = 144; Button startStopButton; Button configButton; Presentation pres; int presenterTimeScale = 600;
這些是服務(wù)器的實例變量。是一個用于指定當(dāng)開始/停止按鈕按下的時候做什么的標(biāo)記。下面是一對廣播視頻大小的常量,緊跟著是服務(wù)器GUI的按鈕。最后是一個Presentation對象,以及它的時間尺度。(媒體的保持時間系統(tǒng),一個600的時間尺度表示一秒種里有600個單位;600也是QuickTime中默認的。)
public static void main (String[] args) {
System.out.println ("main");
try {
QTSession.open();
new LittleBroadcast();
} catch (QTException qte) {
qte.printStackTrace();
}
}
在這個main中并沒有什么獨特的地方。我投入了所有的精力在構(gòu)造函數(shù)上以防止為那些我需要的實例創(chuàng)建一個內(nèi)部類。如果你擴展本代碼,你可能會發(fā)現(xiàn)這很有用。
public LittleBroadcast() throws QTException {
System.out.println ("LittleBroadcast constructor");
QTFile file = new QTFile (new File ("little.sdp"));
try {
MediaParams mediaParams = new MediaParams();
mediaParams.setWidth (BROADCAST_WIDTH);
mediaParams.setHeight (BROADCAST_HEIGHT);
QDGraphics myGWorld =
new QDGraphics (new QDRect (
BROADCAST_WIDTH, BROADCAST_HEIGHT));
mediaParams.setGWorld (myGWorld);
PresParams presParams =
new PresParams( presenterTimeScale,
QTSConstants.kQTSSendMediaFlag |
QTSConstants.kQTSAutoModeFlag |
QTSConstants.kQTSDontShowStatusFlag,
mediaParams );
pres = Presentation.fromFile(file, presParams );
構(gòu)造函數(shù)的第一事是裝載名為little.sdp的SDP文件。 但這并不是所有的都需要創(chuàng)建Presentation ――在調(diào)用Presentation.fromFile()的時候需要服務(wù)器應(yīng)用程序設(shè)置一些必要的參數(shù)。首先,你要創(chuàng)建一個MediaParams對象,這樣你能設(shè)置視頻的高度和寬度。您必須做的其它重要事是提供照相機一個圖形界面,由QDGraphics創(chuàng)建MediaParams設(shè)置。是的, 名字是古怪的, 因為QTJ 設(shè)計員想注重與AWT Graphics對象的相似性, 但得到或設(shè)置這樣的對象的用途的所有方法是使用其本地API名字, GWorld。 最后, 你為所有的Presentation創(chuàng)建一個PresParams來設(shè)置參數(shù)。 這采取一個有些任意的時標(biāo), 一些算術(shù)上的行為標(biāo)記彼此OR'ed, 以及MediaParams。 可能的行為標(biāo)記, 都被定義在QTSConstants, 包括:
·KQTSAutoModeFlag: 都使用默認值。 最重要地, 這些使用默認值Sourcer, Presentation的來源,是從各種各樣的輸入裝置執(zhí)行獲取的SequenceGrabber。 它還可能播放一個在磁盤上或是任意目錄下的的QuickTime 文件; 稍后我將探討這些問題。
·KQTDontShowStatusFlag: 不要創(chuàng)建一個會導(dǎo)致連接數(shù)和狀態(tài)信息總被顯示在客戶端的流媒體狀態(tài)處理程序。
·KQTSSendMediaFlag:發(fā)送,不接收數(shù)據(jù)。
·KQTSReceiveMediaFlag:接收,不發(fā)送數(shù)據(jù)。
在SDP文件說明, 參數(shù), 以及GWorld 設(shè)置下, 創(chuàng)建Presentation和Presentation.fromFile()。
// find audio stream Stream audioStream = null;
for (int i=1; i<=pres.getNumStreams(); i++) {
System.out.println ("stream: " + i + ": " +
pres.getIndStream(i));
Stream aStream = pres.getIndStream (i);
if (pres.hasCharacteristic(aStream,
StdQTConstants.audioMediaCharacteristic)) {
audioStream = aStream;
break;
}
}
System.out.println ("audioStream = " + audioStream);
pres.setVolumes (audioStream, 100, 100);
System.out.println ("created presentation, gworld == " +
pres.getGWorld() + ", size == "+
mediaParams.getWidth() + "x" +
mediaParams.getHeight() + ", streams == " +
pres.getNumStreams());
//*******這不是真正地必要的, 但它將告訴你怎么通過Presentation游覽來挑選各自的流媒體。 Presentation.getIndStream 會由索引返回一個Stream(附注QuickTime 索引都是基于1)。它重復(fù)這些audioMediaCharacteristic 請求查找音頻流 (對于錄影, 您就要請求visualMediaCharacteristic) 。 這個實例在audioStream上為左右聲道設(shè)置音量最大值為100。
最后, println從Presentation和MediaParams轉(zhuǎn)存一些有意義的元數(shù)據(jù)。
配置Presentation
SettingsDialog sd = new SettingsDialog (pres);
System.out.println ("Did settings");
pres.preroll();
broadcasting = false;
這是設(shè)置presentation最后的步驟。 SettingsDialog存在用戶以輸入裝置選擇的音頻和視頻 (二個流媒體SDP 文件被指定在Presentation中) 。每個流媒體都可由一個壓縮格式來定制 (MPEG-4, Sorenson Video 3, H.263, 等) 以及一個分包器(有時由壓縮格式定義; 可觀察它是否隨著壓縮格式的改變而自動改變) 。 這個GUI實例顯示在圖1 。

圖像1. 為一個Presentation SettingsDialog
在此圖中, 音頻默認為計算機連線輸入。更改它為iSight, 您需要點擊Source按鈕,提出的可選設(shè)備列表顯示在圖2上 。

圖2. 來源選擇對話框
最后就是調(diào)用Presentation.preroll(), 如同Movie.preroll(), 提供Presentation一個機會預(yù)先分配資源以及準(zhǔn)備好開始流媒體Presentation。
提供一個控制GUI
// Make monitor window
startStopButton = new Button ("Start");
configButton = new Button ("Configure");
startStopButton.addActionListener (this);
configButton.addActionListener (this);
Frame monitorFrame = new Frame ("QTJ Streaming");
monitorFrame.setLayout (new BorderLayout());
Panel buttonPanel = new Panel();
buttonPanel.add (startStopButton);
buttonPanel.add (configButton);
monitorFrame.add (buttonPanel, BorderLayout.SOUTH);
monitorFrame.pack();
monitorFrame.setVisible(true);
這個為控制和配置Presentation設(shè)置了很小的GUI,提供基本的一個起始/停止鍵和一個配置按鈕。 按鈕作為一個ActionListener提交給this, 意味著這個類將需要提供一個actionPerformed方法來處理按鈕點擊。 控制GUI 的屏幕截圖顯示在圖3 。

圖3. 監(jiān)控/控制窗體
在這點上你也許會問一個有趣的問題: "從什么時候我們開始關(guān)心使用GUI提供server?"據(jù)推測, 這是從Classic Mac OS開始的一個傳統(tǒng), 它沒有一個用命令行啟動和傳遞參數(shù)的程序。但此外, 你通常會希望提供一個流媒體數(shù)據(jù)的預(yù)覽, 并且如果您有一個預(yù)覽視窗, 為什么會也沒有一個配置的GUI?
無論如何, 這是大概的討論, 因為QTJ 6.1 不提供您能使用來預(yù)覽的一個AWT Component。在有些方面有希望的是, QTFactory將得到一個新的超負荷為采用一個Presentation并且返回一個顯示流媒體視頻的Component的makeQTComponent。 它大概可能使用一些QuickDraw voodoo而完全放棄Java的東西。 如果,在各task()中回調(diào)(參見下面) 您采取GWorld被及早創(chuàng)建, 轉(zhuǎn)換它成Pict, 并且作為一份唯一命名的文件, 您會看見每一個都是不同的, 意味GWorld每次都得到新數(shù)據(jù)。 所以如果您替換掉GWorld, 改為能給AWT Component在各通道上定義象素, 您會有銀幕上的預(yù)覽。何人有膽量如此做? 在quicktime-java list上查看。
詳細資料
// add shutdown handler to make sure presentation
// gets stopped
Thread presentationStopper = new Thread() {
public void run() {
try {
pres.stop();
} catch (QTException qte) {}
}
};
Runtime.getRuntime().addShutdownHook (presentationStopper);
這個關(guān)閉異常分支確定Presentation在程序退出之前被終止。 這是重要的原因, 象SequenceGrabber, Presentation愉快繼續(xù)運行在您的應(yīng)用程序退出之后,綁定一個端口,嚴重的循環(huán), 使用您的獲取設(shè)備保留其它應(yīng)用程序, 等。
} catch ( QTException e ) {
e.printStackTrace();
System.exit (-1);
}
}
最后, 構(gòu)造函數(shù)捕捉并拋出所有的QTExceptions。
public void actionPerformed (ActionEvent ae) {
System.out.println ("actionPerformed");
try {
if (ae.getSource() == startStopButton) {
if (broadcasting) {
pres.stop();
stopTasking();
broadcasting = false;
startStopButton.setLabel ("Start");
System.out.println ("Stopped");
} else {
pres.start();
startTasking();
broadcasting = true;
startStopButton.setLabel ("Stop");
System.out.println ("Started");
}
} else if (ae.getSource() == configButton) {
new SettingsDialog (pres);
}
} catch (QTException qte) {
qte.printStackTrace();
}
}
這是非常直接的處理起始/終止和設(shè)置按鈕。如果點擊的按鈕是起始/終止, 設(shè)置GUI 就會調(diào)用在Presentation上的 start() 或stop(), 開始或停止正在執(zhí)行的任務(wù) (定期回調(diào)這個類的task()方法), 為下按鈕點擊目的設(shè)置broadcasting標(biāo)志, 并且更改按鈕標(biāo)簽。如果用戶點擊了配置, 它生成為Presentation新的SettingsDialog。
public synchronized final void task() throws QTException {
pres.idle(null);
}}
最后的這個方法實現(xiàn)了繼承自Tasking的task()方法并且被在操作開始按鈕調(diào)用startTasking()后定時的調(diào)用。使用簡單的調(diào)用Presentation.idle(), 它提供了表達時間來從獲取設(shè)備取得當(dāng)前數(shù)據(jù),對其編碼并傳輸流出去。
運行流媒體客戶端
最簡單的使客戶機看廣播的方法是使用QuickTime 播放器打開服務(wù)器使用并創(chuàng)建Presentation的同樣SDP 文件。這將調(diào)用SDP 輸入程序連接到流媒體并且開始分析這些內(nèi)容。注意客戶機和服務(wù)器不能在同一臺機器上, 明顯地因為服務(wù)器為使用表示而占用端口, 拒絕客戶機對這些端口的使用。 圖4 顯示在我的計算機上的流媒體的外觀(那是正在播放我的Macross和Escaflowne玩具) 。

圖4. QuickTime流媒體客戶端
如果您使用QuickTime 播放器, 您能使用其得到信息指令顯示兩種流媒體以及他們的格式。 在表5, 您能看有二種媒體: 一條未壓縮的44.1kHz 音頻流, 和一條H.263 的視頻流。

圖5. 客戶端信息窗體
結(jié)語
對我來說,播放基于Java的QuickTime流媒體比想象的容易多了。 最簡單的例子, 從獲取設(shè)備播放, 只需要少于150 個代碼行。 顯然, 最困難的部份是了解SDP文件, 它被證明是非常的過分講究并且它的說明文件包含大量應(yīng)用程序級別程序員不會有的知識。同樣不幸的是,QTJ不再提供預(yù)覽組件, 但也許在將來會提供, 以及一小段的GWorld/QuickDraw 堆砌也許會在將來制造出這樣的組件。
本文只包括怎么為實時獲取數(shù)據(jù)設(shè)置廣播。其它可利用的Sourcers, 譬如來自磁盤或任意目錄種的那些廣播QuickTime 文件, 將會在以后的部分中討論。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=530469
posted on 2007-04-01 18:22 javaboys 閱讀(181) 評論(0) 編輯 收藏 所屬分類: j2se