传输RTP媒体流
要传输一个RTP媒体流,你使用一个Processor生成一个RTP解码数据源,然后要末创建一个SessionManager,要末创建一个DataSink来控制传输。
输入到Processor不仅能保存,也能实况转播拍摄到的数据。要保存数据,你使用一个MediaLocator在你要创建Processor时确定文件。要得到拍摄到的数据,用一个拍摄数据源作为输入传入Processor,就像在Capturing Media Data中描述的一样。
有两种方式可以传输RTP流:
l 使用一个具有RTP session参数的MediaLocator,通过调用Manager.createDataSink创建一个RTP DataSink。
l 使用一个session管理器创建发送流发送内容并控制传输。
如果你使用一个MediaLocator创建一个RTP DataSink,你可以只传输数据源中的第一个流。如果你想传输session中的多个RTP流,或者需要监听session统计表,你需要直接使用SessionManager。
不管你选择怎样去传输RTP流,你都需要:
1.用一个DataSource创建一个Processor去播放你想传输的数据。
2.配置Processor输出RTP编码数据。
3.把Processor作为一个DataSource从中得到输出数据。
配置Processor
要配置Processor产生RTP编码数据,你为每个轨迹设置RTP-specific格式,然后指定你要想要的输出内容描述符。
通过得到每个轨迹的TrackControl来设置轨迹格式,然后调用setFormat指定一个RTP-specific格式。通过设定格式的编码字符串到一个RTP-specific字符串,比如”AudioFormat.GSM_RTP”, 选定一个RTP-specific格式。这个Processor试图去加载一个支持这种格式的插件。如果没有安装合适的插件,有可能不支持特殊的RTP格式并抛出一个UnSupportFormatException异常。
用setOutputContentDescriptor设置输出格式。如果需要专门的多路技术,输出内容描述符能被设置为”ContentDescriptor.RAW”。音频和视频流不应该被交叉存取。如果Processor的轨迹不同于媒体类型,每个媒体流都在一个单独的RTP session中被传输。
重新得到Processor
输出
一旦设置了一个Processor轨迹的格式并且实现了Processor,Processor的输出数据源能够被重新得到。通过调用getDataSource重新得到Processor的输出作为一个DataSource。这个返回的DataSource不仅是一个PushBufferDataSource,也是一个PullBufferDataSource,这取决于数据源。
输出DataSource使用createSendStream方法被连接到SessionManager。这个session 管理器必须在你创建发送流之前被初始化。
如果DataSource包含多个SourceStreams,每个SourceStream都被作为一个单独的RTP流被发送出去,不管是在同一个session还是在一个不同的session中。如果DataSource不仅包含音频流,也包含视频流,那么必须为音频和视频创建单独的RTP sessions。你也可以复制DataSource,然后在同一个session或不同的session中,发送复制的输出作为不同的RTP流。
控制包的延迟
包的延迟,也是被熟知的包化的时间间隔,是定期的播放网络中传输的每个RTP包。包化的时间间隔定义了最小的点对点的延迟;比较大的包在最上面引入少量报头而不是长时间的延迟并丢失重要信息。为非交互的应用程序比如演讲,或用一些宽带约束的链接,一个更高级的包化的延迟可能是合适的。
一个接收包应该接收包,播放音频数据的范围在0到200毫秒。(因为帧的音频编码,一个接收端通过帧的持续时间收集,应该用200毫秒来接收包。)这个约束允许接收端的合理的缓冲大小。每个包化的多媒体数字信号编解码器都有适合于它自己编码的一个默认的包时间间隔。
如果这个多媒体数字信号编解码器允许修改这个时间间隔,它有一个相应的PacketSizeControl。通过使用setPacketSize方法,包的时间间隔能够被改变或设置。
在多个RTP包中,为视频流要传输一个单独的视频帧。通过最大化网络的传输单元,每个包的大小都是有限的。这个参数也可以使用包的codec的PacketSizeControl的setPacketSize来设定。
用一个数据池来传输RTP数据
传输一个RTP数据最简单的途径是使用Manager.createDataSink方法创建一个RTP DataSink。你从Processor中传入输出DataSource和一个MediaLocator,这个MediaLocator描述了RTP session要流化的DataSource。(这个MediaLocator提供了RTP session的地址和端口)。
要控制传输,你在DataSink上调用start和stop方法。只有DataSource 中的第一个流被传输。
在Example 10-1中,拍摄了实况转播的视频而后使用一个DataSink来传输。
Example 10-1: Transmitting RTP Data using a DataSink (1 of 2)
|
//
首先找到摄像驱动,它将在8bit 8Khz拍摄线性的音频数据
AudioFormat format= new AudioFormat(AudioFormat.LINEAR,
Vector devices= CaptureDeviceManager.getDeviceList( format);
CaptureDeviceInfo di= null;
if (devices.size() > 0) {
di = (CaptureDeviceInfo) devices.elementAt( 0);
// exit if we could not find the relevant capturedevice.
// Create a processor for this capturedevice & exit if we
Processor p = Manager.createProcessor(di.getLocator());
} catch (IOException e) {
} catch (NoProcessorException e) {
// configure the processor
// block until it has been configured
processor.setContentDescriptor(
new ContentDescriptor( ContentDescriptor.RAW));
TrackControl track[] = processor.getTrackControls();
boolean encodingOk = false;
// Go through the tracks and try to program one of them to
for (int i = 0; i < track.length; i++) {
if (!encodingOk && track[i] instanceof FormatControl) {
|
if (((FormatControl)track[i]).
setFormat( new AudioFormat(AudioFormat.GSM_RTP,
track[i].setEnabled(false);
// we could not set this track to gsm, so disable it
track[i].setEnabled(false);
// At this point, we have determined where we can send out
// get the output datasource of the processor and exit
ds = processor.getDataOutput();
} catch (NotRealizedError e) {
// hand this datasource to manager for creating an RTP
// datasink our RTP datasimnk will multicast the audio
String url= "rtp://224.144.251.104:49150/audio/1";
MediaLocator m = new MediaLocator(url);
DataSink d = Manager.createDataSink(ds, m);
|
用Session Manager
来传输RTP数据
1.创建一个JMF Processor,然后设置每个轨迹的格式到RTP指定的格式。
2.从Processor中重新得到输出DataSource。
3.在一个先前创建和初始化的SessionManager上调用createSendStream,传进DataSource和一个流的索引。这个session管理器为指定的SourceStream创建一个SendStream。
4.通过调用SessionManager startSession启动session管理器。
5.通过SendStream方法控制传输。一个SendStreamListener能够被注册为监听SendStream上的事件。
创建一个发送流
在session管理器传输数据以前,它需要知道从哪里得到要传输的数据。当你构造一个新的SendStream时,从它将得到数据的DataSource来支持SessionManager。因为一个DataSource能包含多个流,你也需要指定这个session中要发送流的索引。通过传递不同的DataSource到createSendStream或通过指定不同的流的索此,你可以创建多个发送流。
Session管理器查询SourceStream的格式来确定这个格式中是否有一个已注册的有效载荷。如果数据的格式不是一个RTP格式,或者一个有效载荷类型不能为RTP格式定位,带有适当消息的UnSupportedFormatException将被抛出。使用SessionManager addFormat方法,动态的有效载荷可以被关联到一个RTP格式。
使用克隆的数据源
很多RTP用法的说明都包括在多个RTP session上发送一个流,或把一个流编码进多个格式然后在多个RTP session上发送他们。当一个已编码进一个单独格式中的流在多个RTP上被发送,从被拍摄的数据中,你需要从Processor中克隆DataSource的输出。通过创建一个克隆的DataSource经由Manager,然后在克隆的DataSource上调用getClone可以做到这一切。一个新的Processor能够通过每个克隆的DataSource创建,它的编码轨道是要求的格式,并且在一个RTP session上发送流。
使用合并的数据源
如果你想混合多个相同类型的媒体流(比如音频)到一个单独的源的输出流,你必须使用一个RTP混合器。如果这个流是开始从多个DataSource中被混合的,你开始从单独的DataSources创建一个MergingDataSource,然后把它放入SessionManager中创建流。
控制一个发送流
你使用RTPStream start和stop方法来控制一个SendStream。启动一个SendStream开始数据在网络中的传输,并且停止一个SendStream指示要中止数据的传输。要开始一个RTP传输,每个SendStream都需要被启动。
启动或停止一个发送流,在它的DataSource上会触发相应的事件。然而,当SendStream被停止时,如果这个DataSource被独立的启动,通过session管理器,数据将会被删除(PushBufferDataSource)或不pulled(PullBufferDataSource)。在这段时间,没有数据会在网络上被传输。
在一个单独的Session
中发送出捕获到的音频
例10-2捕获到了一个mono音频数据并在一个RTP session发送出去。
Example 10-2: Sending captured audio out on a single session (1 of 3)
|
// First, we'll need a DataSource that captures live audio:
AudioFormat format = new AudioFormat(AudioFormat.ULAW,
Vector devices= CaptureDeviceManager.getDeviceList( format);
CaptureDeviceInfo di= null;
if (devices.size() > 0) {
di = (CaptureDeviceInfo) devices.elementAt( 0);
|
// exit if we could not find the relevant capture device.
// Create a processor for this capture device & exit if we
Processor p = Manager.createProcessor(di.getLocator());
} catch (IOException e) {
} catch (NoProcessorException e) {
// at this point, we have succesfully created the processor.
// Realize it and block until it is configured.
// block until it has been configured
processor.setContentDescriptor(
new ContentDescriptor( ContentDescriptor.RAW));
TrackControl track[] = processor.getTrackControls();
boolean encodingOk = false;
// Go through the tracks and try to program one of them to
for (int i = 0; i < track.length; i++) {
if (!encodingOk && track[i] instanceof FormatControl) {
if (((FormatControl)track[i]).
setFormat( new AudioFormat(AudioFormat.ULAW_RTP,
track[i].setEnabled(false);
// we could not set this track to gsm, so disable it
track[i].setEnabled(false);
|
// Realize it and block until it is realized.
// get the output datasource of the processor and exit
ds = processor.getDataOutput();
} catch (NotRealizedError e){
// Create a SessionManager and hand over the
// datasource for SendStream creation.
SessionManager rtpsm = new com.sun.media.rtp.RTPSessionMgr();
// The session manager then needs to be initialized and started:
// rtpsm.initSession(...);
// rtpsm.startSession(...);
rtpsm.createSendStream(ds, 0);
} catch (IOException e) {
} catch( UnsupportedFormatException e) {
|
例10-3和例10-4都编码了捕获的音频并且在多个RTP sessions中发送了出去。在例10-3中,数据被编码为gsm ;在例10-4中,数据被编码为一些不同的格式。
Example 10-3: Sending RTP data out in multiple sessions (1 of 4)
|
// First find a capture device that will capture linear audio
AudioFormat format= new AudioFormat(AudioFormat.LINEAR,
|
Vector devices= CaptureDeviceManager.getDeviceList( format);
CaptureDeviceInfo di= null;
if (devices.size() > 0) {
di = (CaptureDeviceInfo) devices.elementAt( 0);
// exit if we could not find the relevant capturedevice.
// Now create a processor for this capturedevice & exit if we
Processor p = Manager.createProcessor(di.getLocator());
} catch (IOException e) {
} catch (NoProcessorException e) {
// configure the processor
// block until it has been configured
processor.setContentDescriptor(
new ContentDescriptor( ContentDescriptor.RAW));
TrackControl track[] = processor.getTrackControls();
boolean encodingOk = false;
// Go through the tracks and try to program one of them to
for (int i = 0; i < track.length; i++) {
if (!encodingOk && track[i] instanceof FormatControl) {
if (((FormatControl)track[i]).
setFormat( new AudioFormat(AudioFormat.GSM_RTP,
track[i].setEnabled(false);
|
// we could not set this track to gsm, so disable it
track[i].setEnabled(false);
// At this point, we have determined where we can send out
// get the output datasource of the processor and exit
DataSource origDataSource = null;
origDataSource = processor.getDataOutput();
} catch (NotRealizedError e) {
// We want to send the stream of this datasource over two
// So we need to clone the output datasource of the
// processor and hand the clone over to the second
DataSource cloneableDataSource = null;
DataSource clonedDataSource = null;
= Manager.createCloneableDataSource(origDataSource);
= ((SourceCloneable)cloneableDataSource).createClone();
// Now create the first SessionManager and hand over the
// first datasource for SendStream creation.
= new com.sun.media.rtp.RTPSessionMgr();
// The session manager then needs to be
// initialized and started:
// rtpsm1.initSession(...);
// rtpsm1.startSession(...);
|
rtpsm1.createSendStream(cloneableDataSource, // Datasource 1
} catch (IOException e) {
} catch( UnsupportedFormatException e) {
cloneableDataSource.connect();
cloneableDataSource.start();
} catch (IOException e) {
// create the second RTPSessionMgr and hand over the
if (clonedDataSource != null) {
= new com.sun.media.rtp.RTPSessionMgr();
// rtpsm2.initSession(...);
// rtpsm2.startSession(...);
rtpsm2.createSendStream(clonedDataSource,0);
} catch (IOException e) {
} catch( UnsupportedFormatException e) {
// we failed to set the encoding to gsm. So deallocate
// and close the processor before we leave.
|
例10-4编码捕获的音频为一些格式,然后在多个RTP sessions中将它发送出去。它假定在输入DataSource中有一个流。
输入DataSource被克隆并且另一个processor被从这个克隆中创建。两个 Processors中的轨迹都单独的设置gsm和dvi,并且输出DataSources被发送到两个不同的RTP session管理器。如果轨道的数量多于1,这个例子试图设置一个轨道的编码为gsm而另一个为dvi。同一个DataSource被交给两个单独的RTPsession管理器,第一个流的索引设为0而第二个索引设为1(为了不同种类的接收器)。
Example 10-4: Encoding and sending data in multiple formats (1 of 3)
|
// Find a capture device that will capture linear 8bit 8Khz
AudioFormat format = new AudioFormat(AudioFormat.LINEAR,
Vector devices= CaptureDeviceManager.getDeviceList( format);
CaptureDeviceInfo di= null;
if (devices.size() > 0) {
di = (CaptureDeviceInfo) devices.elementAt( 0);
// exit if we could not find the relevant capture device.
// Since we have located a capturedevice, create a data
DataSource origDataSource= null;
origDataSource = Manager.createDataSource(di.getLocator());
} catch (IOException e) {
} catch (NoDataSourceException e) {
SourceStream streams[] = ((PushDataSource)origDataSource)
DataSource cloneableDataSource = null;
DataSource clonedDataSource = null;
if (streams.length == 1) {
= Manager.createCloneableDataSource(origDataSource);
|
= ((SourceCloneable)cloneableDataSource).createClone();
// DataSource has more than 1 stream and we should try to
// set the encodings of these streams to dvi and gsm
// at this point, we have a cloneable data source and its clone,
// Create one processor from each of these datasources.
p1 = Manager.createProcessor(cloneableDataSource);
} catch (IOException e) {
} catch (NoProcessorException e) {
// block until configured.
TrackControl track[] = p1.getTrackControls();
boolean encodingOk = false;
// Go through the tracks and try to program one of them
for (int i = 0; i < track.length; i++) {
if (!encodingOk && track[i] instanceof FormatControl) {
if (((FormatControl)track[i]).
setFormat( new AudioFormat(AudioFormat.GSM_RTP,
track[i].setEnabled(false);
track[i].setEnabled(false);
|
// get the output datasource of the processor
ds = processor.getDataOutput();
} catch (NotRealizedError e) {
// Now create the first SessionManager and hand over the
// first datasource for SendStream creation .
= new com.sun.media.rtp.RTPSessionMgr();
// rtpsm1.initSession(...);
// rtpsm1.startSession(...);
rtpsm1.createSendStream(ds, // first datasource
0); // first sourcestream of
} catch (IOException e) {
} catch( UnsupportedFormatException e) {
// Now repeat the above with the cloned data source and
// set the encoding to dvi. i.e create a processor with
// inputdatasource clonedDataSource
// and set encoding of one of its tracks to dvi.
// create SessionManager giving it the output datasource of
|
用RTPSocket
传输RTP流
你也可以使用RTPSocket来传输RTP媒体流。要使用RTPSocket来传输,要用createDataSink通过传入一个MediaLocator来创建一个RTP DataSink,这个MediaLocator用一种RTP变量的新端口,”Ratibor”。管理器试图创建一个DataSink,从:
<protocol package-prefix>.media.datasink.rtpraw.Handler
这个管理器预备单独的正要在网络中传输的单独的包,然后发送他们到创建的RTPSocket,从:
<protocol package-prefix>.media.protocol.rtpraw.DataSource
这个在<protocol package-prefix>.media.protocol.rtpraw.DataSource上创建的RTPSocket是你自己实现的RTPSocket。JMF API没有定义一个默认的RTPSocket的实现。RTPSocket的实现依赖于你正使用的底层传输协议。你的RTPSocket必须位于<protocol package-prefix>.media.protocol.rtpraw.DataSource。
你有责任在底层网络上传输RTP包。
在以下的例子中,一个RTPSocket被用于传输捕获到的音频:
Example 10-5: Transmitting RTP data with RTPSocket (1 of 3)
|
// Find a capture device that will capture linear audio
AudioFormat format = new AudioFormat(AudioFormat.LINEAR,
Vector devices= CaptureDeviceManager.getDeviceList( format);
CaptureDeviceInfo di= null;
if (devices.size() > 0) {
di = (CaptureDeviceInfo) devices.elementAt( 0);
// exit if we could not find the relevant capture device.
// Create a processor for this capturedevice & exit if we
processor = Manager.createProcessor(di.getLocator());
} catch (IOException e) {
} catch (NoProcessorException e) {
|
// configure the processor
// block until it has been configured
processor.setContentDescriptor(
new ContentDescriptor( ContentDescriptor.RAW));
TrackControl track[] = processor.getTrackControls();
boolean encodingOk = false;
// Go through the tracks and try to program one of them to
for (int i = 0; i < track.length; i++) {
if (!encodingOk && track[i] instanceof FormatControl) {
if (((FormatControl)track[i]).
setFormat( new AudioFormat(AudioFormat.GSM_RTP,
track[i].setEnabled(false);
// we could not set this track to gsm, so disable it
track[i].setEnabled(false);
// At this point, we have determined where we can send out
// get the output datasource of the processor and exit
ds = processor.getDataOutput();
} catch (NotRealizedError e) {
// hand this datasource to manager for creating an RTP
// our RTP datasimnk will multicast the audio
|
MediaLocator m = new MediaLocator("rtpraw://");
// here, manager will look for a datasink in
// <protocol.prefix>.media.protocol.rtpraw.DataSink
// the datasink will create an RTPSocket at
// <protocol.prefix>.media.protocol.rtpraw.DataSource
// and sink all RTP data to this socket.
DataSink d = Manager.createDataSink(ds, m);
|