CONTENTS | PREV | NEXT | INDEX JMF 2.0 API Guide



10

Transmitting RTP Media Streams

To transmit an RTP stream, you use a Processor to produce an RTP-encoded DataSource and construct either a SessionManager or DataSink to control the transmission.

The input to the Processor can be either stored or live captured data. For stored data, you can use a MediaLocator to identify the file when you create the Processor. For captured data, a capture DataSource is used as the input to the Processor, as described in Capturing Media Data.

There are two ways to transmit RTP streams:

If you use a MediaLocator to construct an RTP DataSink, you can only transmit the first stream in the DataSource. If you want to transmit multiple RTP streams in a session or need to monitor session statistics, you need to use the SessionManager directly.

Regardless of how you choose to transmit the RTP stream, you need to:

  1. Create a Processor with a DataSource that represents the data you want to transmit.
  2. Configure the Processor to output RTP-encoded data.
  3. Get the output from the Processor as a DataSource.

Configuring the Processor

To configure the Processor to generate RTP-encoded data, you set RTP-specific formats for each track and specify the output content descriptor you want.

The track formats are set by getting the TrackControl for each track and calling setFormat to specify an RTP-specific format. An RTP-specific format is selected by setting the encoding string of the format to an RTP-specific string such as "AudioFormat.GSM_RTP". The Processor attempts to load a plug-in that supports this format. If no appropriate plug-in is installed, that particular RTP format cannot be supported and an UnSupportedFormatException is thrown.

The output format is set with the setOutputContentDescriptor method. If no special multiplexing is required, the output content descriptor can be set to "ContentDescriptor.RAW". Audio and video streams should not be interleaved. If the Processor's tracks are of different media types, each media stream is transmitted in a separate RTP session.

Retrieving the Processor Output

Once the format of a Processor's track has been set and the Processor has been realized, the output DataSource of the Processor can be retrieved. You retrieve the output of the Processor as a DataSource by calling getDataOutput. The returned DataSource can be either a PushBufferDataSource or a PullBufferDataSource, depending on the source of the data.

The output DataSource is connected to the SessionManager using the createSendStream method. The session manager must be initialized before you can create the send stream.

If the DataSource contains multiple SourceStreams, each SourceStream is sent out as a separate RTP stream, either in the same session or a different session. If the DataSource contains both audio and video streams, separate RTP sessions must be created for audio and video. You can also clone the DataSource and send the clones out as different RTP streams in either the same session or different sessions.

Controlling the Packet Delay

The packet delay, also known as the packetization interval, is the time represented by each RTP packet as it is transmitted over the network. The packetization interval determines the minimum end-to-end delay; longer packets introduce less header overhead but higher delay and make packet loss more noticeable. For non-interactive applications such as lectures, or for links with severe bandwidth constraints, a higher packetization delay might be appropriate.

A receiver should accept packets representing between 0 and 200 ms of audio data. (For framed audio encodings, a receiver should accept packets with 200 ms divided by the frame duration, rounded up.) This restriction allows reasonable buffer sizing for the receiver. Each packetizer codec has a default packetization interval appropriate for its encoding.

If the codec allows modification of this interval, it exports a corresponding PacketSizeControl. The packetization interval can be changed or set by through the setPacketSize method.

For video streams, a single video frame is transmitted in multiple RTP packets. The size of each packet is limited by the Maximum Transmission Unit (MTU) of the underlying network. This parameter is also set using the setPacketSize method of the packetizer codec's PacketSizeControl.

Transmitting RTP Data With a Data Sink

The simplest way to transmit RTP data is to construct an RTP DataSink using the Manager.createDataSink method. You pass in the output DataSource from the Processor and a MediaLocator that describes the RTP session to which the DataSource is to be streamed. (The MediaLocator provides the address and port of the RTP session.)

To control the transmission, you call start and stop on the DataSink. Only the first stream in the DataSource is transmitted.

In Example 10-1, live audio is captured and then transmitted using a DataSink.

Example 10-1: Transmitting RTP Data using a DataSink (1 of 2)
         // First find a capture device that will capture linear audio
         // data at 8bit 8Khz 
         
         AudioFormat format= new AudioFormat(AudioFormat.LINEAR, 
                                             8000, 
                                             8, 
                                             1); 
 
         Vector devices= CaptureDeviceManager.getDeviceList( format);
 
         CaptureDeviceInfo di= null;
 
         if (devices.size() > 0) {
              di = (CaptureDeviceInfo) devices.elementAt( 0);
         }
         else {
             // exit if we could not find the relevant capturedevice. 
             System.exit(-1); 
         }
        
         // Create a processor for this capturedevice & exit if we 
         // cannot create it 
         try { 
             Processor p = Manager.createProcessor(di.getLocator()); 
         } catch (IOException e) { 
             System.exit(-1); 
         } catch (NoProcessorException e) { 
             System.exit(-1); 
         } 
 
        // configure the processor  
        processor.configure(); 
        
        // 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
        // output gsm data. 
        
         for (int i = 0; i < track.length; i++) { 
             if (!encodingOk && track[i] instanceof FormatControl) {  
                 if (((FormatControl)track[i]).
                     setFormat( new AudioFormat(AudioFormat.GSM_RTP, 
                                                8000, 
                                                8, 
                                                1)) == null) {
 
                    track[i].setEnabled(false); 
                 }
                 else {
                     encodingOk = true; 
                 }
             } else { 
                 // 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 
         // gsm data or not. 
         // realize the processor 
         if (encodingOk) { 
             processor.realize(); 
             // block until realized. 
             // get the output datasource of the processor and exit 
             // if we fail 
             DataSource ds = null;
             
             try { 
                 ds = processor.getDataOutput(); 
             } catch (NotRealizedError e) { 
                 System.exit(-1);
             }
 
             // hand this datasource to manager for creating an RTP 
             // datasink our RTP datasimnk will multicast the audio 
             try {
                 String url= "rtp://224.144.251.104:49150/audio/1";
 
                 MediaLocator m = new MediaLocator(url);
 
                 DataSink d = Manager.createDataSink(ds, m);
 
                 d.open();
                 d.start(); 
             } catch (Exception e) {
                 System.exit(-1);
             }     
         }    

Transmitting RTP Data with the Session Manager

The basic process for transmitting RTP data with the session manager is:

  1. Create a JMF Processor and set each track format to an RTP-specific format.
  2. Retrieve the output DataSource from the Processor.
  3. Call createSendStream on a previously created and initialized SessionManager, passing in the DataSource and a stream index. The session manager creates a SendStream for the specified SourceStream.
  4. Start the session manager by calling SessionManager startSession.
  5. Control the transmission through the SendStream methods. A SendStreamListener can be registered to listen to events on the SendStream.

Creating a Send Stream

Before the session manager can transmit data, it needs to know where to get the data to transmit. When you construct a new SendStream, you hand the SessionManager the DataSource from which it will acquire the data. Since a DataSource can contain multiple streams, you also need to specify the index of the stream to be sent in this session. You can create multiple send streams by passing different DataSources to createSendStream or by specifying different stream indexes.

The session manager queries the format of the SourceStream to determine if it has a registered payload type for this format. If the format of the data is not an RTP format or a payload type cannot be located for the RTP format, an UnSupportedFormatException is thrown with the appropriate message. Dynamic payloads can be associated with an RTP format using the SessionManager addFormat method

Using Cloneable Data Sources

Many RTP usage scenarios involve sending a stream over multiple RTP sessions or encoding a stream into multiple formats and sending them over multiple RTP sessions. When a stream encoded in a single format has to be sent over multiple RTP sessions, you need to clone the DataSource output from the Processor from which data is being captured. This is done by creating a cloneable DataSource through the Manager and calling getClone on the cloneable DataSource. A new Processor can be created from each cloned DataSource, its tracks encoded in the desired format, and the stream sent out over an RTP session.

Using Merging Data Sources

If you want to mix multiple media streams of the same type (such as audio) into a single stream going out from one source, you need to use an RTP mixer. If the streams to be mixed originate from multiple DataSources, you can create a MergingDataSource from the separate DataSources and hand it to the SessionManager to create the stream.

Controlling a Send Stream

You use the RTPStream start and stop methods to control a SendStream. Starting a SendStream begins data transfer over the network and stopping a SendStream indicates halts the data transmission. To begin an RTP transmission, each SendStream needs to be started.

Starting or stopping a send stream triggers the corresponding action on its DataSource. However, if the DataSource is started independently while the SendStream is stopped, data will be dropped (PushBufferDataSource) or not pulled (PullBufferDataSource) by the session manager. During this time, no data will be transmitted over the network.

Sending Captured Audio Out in a Single Session

Example 10-2 captures mono audio data and sends it out on an 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, 
                                              8000, 
                                              8, 
                                              1); 
  
         Vector devices= CaptureDeviceManager.getDeviceList( format);
 
         CaptureDeviceInfo di= null;
         if (devices.size() > 0) {
              di = (CaptureDeviceInfo) devices.elementAt( 0);
         }
         else {
             // exit if we could not find the relevant capture device.
             System.exit(-1); 
         }
         // Create a processor for this capture device & exit if we 
         // cannot create it 
         try { 
             Processor p = Manager.createProcessor(di.getLocator()); 
         } catch (IOException e) { 
             System.exit(-1); 
         } catch (NoProcessorException e) { 
             System.exit(-1); 
         } 
 
         // at this point, we have succesfully created the processor. 
         // Realize it and block until it is configured. 
        
         processor.configure(); 
        
         // 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
         // output ULAW_RTP data.
         for (int i = 0; i < track.length; i++) { 
             if (!encodingOk && track[i] instanceof FormatControl) {
 
                 if (((FormatControl)track[i]).
                     setFormat( new AudioFormat(AudioFormat.ULAW_RTP, 
                                                8000, 
                                                8, 
                                                1)) == null) {
 
                     track[i].setEnabled(false); 
                 }
                 else {
                     encodingOk = true; 
                 }
             } 
             else {  
                 // we could not set this track to gsm, so disable it
                 track[i].setEnabled(false); 
             } 
         }
         // Realize it and block until it is realized.
         processor.realize();    
        
         // block until realized. 
         // get the output datasource of the processor  and exit 
         // if we fail 
        
         DataSource ds = null; 
        
         try { 
             ds = processor.getDataOutput(); 
         } catch (NotRealizedError e){ 
             System.exit(-1); 
         } 
         
         // 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(...); 
 
         try {
             rtpsm.createSendStream(ds, 0);
         } catch (IOException e)	 {
             e.printStackTrace();
         } catch( UnsupportedFormatException e) {
             e.printStackTrace();
         }
 

Sending Captured Audio Out in Multiple Sessions

Example 10-3 and Example 10-4 both encode the captured audio and send it out in multiple RTP sessions. In Example 10-3, the data is encoded in gsm; in Example 10-4, the data is encoded in several different formats.

Example 10-3: Sending RTP data out in multiple sessions (1 of 4)
         // First find a capture device that will capture linear audio
         // data at 8bit 8Khz 
        
         AudioFormat format= new AudioFormat(AudioFormat.LINEAR, 
                                             8000, 
                                             8, 
                                             1); 
         Vector devices= CaptureDeviceManager.getDeviceList( format);
 
         CaptureDeviceInfo di= null;
 
         if (devices.size() > 0) {
              di = (CaptureDeviceInfo) devices.elementAt( 0);
         }
         else {
             // exit if we could not find the relevant capturedevice. 
             System.exit(-1); 
         }
         
         // Now create a processor for this capturedevice & exit if we 
         // cannot create it 
         try { 
             Processor p = Manager.createProcessor(di.getLocator()); 
         } catch (IOException e) { 
             System.exit(-1); 
         } catch (NoProcessorException e) { 
             System.exit(-1); 
         } 
        
         // configure the processor 
         processor.configure(); 
        
         // 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 
         // output gsm data. 
        
         for (int i = 0; i < track.length; i++) { 
             if (!encodingOk && track[i] instanceof FormatControl) { 
 
                 if (((FormatControl)track[i]).
                     setFormat( new AudioFormat(AudioFormat.GSM_RTP, 
                                                8000, 
                                                8, 
                                                1)) == null) {
 
                     track[i].setEnabled(false); 
                 }
                 else {
                     encodingOk = true; 
                 }
             }  
             else { 
                 // 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 
         // gsm data or not. 
         // realize the processor 
        
         if (encodingOk) { 
             processor.realize(); 
        
             // block until realized. 
        
             // get the output datasource of the processor  and exit 
             // if we fail 
        
             DataSource origDataSource = null; 
        
             try { 
                 origDataSource = processor.getDataOutput(); 
             } catch (NotRealizedError e) { 
                 System.exit(-1); 
             } 
        
             // We want to send the stream of this datasource over two 
             // RTP sessions. 
        
             // So we need to clone the output datasource of the  
             // processor and hand the clone over to the second 
             // SessionManager 
        
             DataSource cloneableDataSource = null; 
             DataSource clonedDataSource = null; 
        
             cloneableDataSource 
               = Manager.createCloneableDataSource(origDataSource); 
 
             clonedDataSource 
               = ((SourceCloneable)cloneableDataSource).createClone(); 
        
             // Now create the first SessionManager and hand over the
             // first datasource for SendStream creation. 
        
             SessionManager rtpsm1 
               = new com.sun.media.rtp.RTPSessionMgr();  
 
             // The session manager then needs to be 
             // initialized and started:
             // rtpsm1.initSession(...); 
             // rtpsm1.startSession(...); 
             try {
                 rtpsm1.createSendStream(cloneableDataSource, // Datasource 1 
                                     0);      
                                                  
             } catch (IOException e) {
                 e.printStackTrace();
             } catch( UnsupportedFormatException e) {
                 e.printStackTrace();
             }
 
             try {
                 cloneableDataSource.connect();
                 cloneableDataSource.start();
             } catch (IOException e) {
                 e.printStackTrace();
             }
        
             // create the second RTPSessionMgr and hand over the 
             // cloned datasource 
             if (clonedDataSource != null) { 
                 SessionManager rtpsm2 
                   = new com.sun.media.rtp.RTPSessionMgr(); 
 
                 // rtpsm2.initSession(...); 
                 // rtpsm2.startSession(...); 
 
                 try {
                     rtpsm2.createSendStream(clonedDataSource,0); 
                 } catch (IOException e) {
                     e.printStackTrace();
                 } catch( UnsupportedFormatException e) {
                     e.printStackTrace();
                 }
             } 
         } 
         else { 
             // we failed to set the encoding to gsm. So deallocate 
             // and close the processor before we leave. 
        
             processor.deallocate(); 
             processor.close(); 
         }

Example 10-4 encodes captured audio in several formats and then sends it out in multiple RTP sessions. It assumes that there is one stream in the input DataSource.

The input DataSource is cloned and a second processor is created from the clone. The tracks in the two Processors are individually set to gsm and dvi and the output DataSources are sent to two different RTP session managers. If the number of tracks is greater than 1, this example attempts to set the encoding of one track to gsm and the other to dvi. The same DataSource is handed to two separate RTP session managers with the index of the first stream set to 0 and the index of the second stream set to 1 (for heterogeneous receivers).

Example 10-4: Encoding and sending data in multiple formats (1 of 3)
         // Find a capture device that will capture linear 8bit 8Khz 
         // audio 
 
         AudioFormat format = new AudioFormat(AudioFormat.LINEAR, 
                                              8000, 
                                              8, 
                                              1); 
  
         Vector devices= CaptureDeviceManager.getDeviceList( format);
 
         CaptureDeviceInfo di= null;
 
         if (devices.size() > 0) {
              di = (CaptureDeviceInfo) devices.elementAt( 0);
         }
         else {
             // exit if we could not find the relevant capture device.
             System.exit(-1); 
         }
  
         // Since we have located a capturedevice, create a data 
         // source for it. 
 
         DataSource origDataSource= null;
 
         try { 
             origDataSource = Manager.createDataSource(di.getLocator()); 
         } catch (IOException e) { 
             System.exit(-1); 
         } catch (NoDataSourceException e) { 
            System.exit(-1); 
         }
          
         SourceStream streams[] = ((PushDataSource)origDataSource)
                                    .getStreams(); 
        
         DataSource cloneableDataSource = null;
         DataSource clonedDataSource = null;
 
         if (streams.length == 1) { 
             cloneableDataSource 
               = Manager.createCloneableDataSource(origDataSource);   
             clonedDataSource 
               = ((SourceCloneable)cloneableDataSource).createClone(); 
         }
         else { 
             // 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. 
        
        Processor p1 = null;
 
         try { 
             p1 = Manager.createProcessor(cloneableDataSource); 
         } catch (IOException e) { 
             System.exit(-1); 
         } catch (NoProcessorException e) { 
             System.exit(-1); 
         } 
        
         p1.configure(); 
        
         // block until configured. 
 
         TrackControl track[] = p1.getTrackControls(); 
         boolean encodingOk = false; 
        
         // Go through the tracks and try to program one of them 
         // to output gsm data 
         for (int i = 0; i < track.length; i++) { 
             if (!encodingOk && track[i] instanceof FormatControl) { 
                 if (((FormatControl)track[i]).
                     setFormat( new AudioFormat(AudioFormat.GSM_RTP, 
                                                8000, 
                                                8, 
                                                1)) == null) {
 
                     track[i].setEnabled(false); 
                 }
                 else {
                     encodingOk = true; 
                 }
             } 
             else { 
                 track[i].setEnabled(false); 
             } 
         }
         if (encodingOk) { 
             processor.realize(); 
             // block until realized. 
             // ...
             // get the output datasource of the processor 
             DataSource ds = null; 
 
             try { 
                 ds = processor.getDataOutput(); 
             } catch (NotRealizedError e) { 
                 System.exit(-1); 
             } 
            
             // Now create the first SessionManager and hand over the 
             // first datasource for SendStream creation . 
        
             SessionManager rtpsm1 
               = new com.sun.media.rtp.RTPSessionMgr(); 
 
             // rtpsm1.initSession(...); 
             // rtpsm1.startSession(...); 
 
             try {
                 rtpsm1.createSendStream(ds, // first datasource 
                                         0); // first sourcestream of 
                                             // first datasource 
             } catch (IOException e) {
                 e.printStackTrace();
             } catch( UnsupportedFormatException e) {
                 e.printStackTrace();
             }
         }
        
         // 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 
         // this processor.  

Transmitting RTP Streams with RTPSocket

You can also use RTPSocket to transmit RTP media streams. To use RTPSocket for transmission, you create an RTP DataSink with createDataSink by passing in a a MediaLocator with a new protocol that is a variant of RTP, "Ratibor". Manager attempts to construct a DataSink from:

 <protocol package-prefix>.media.datasink.rtpraw.Handler

The session manager prepares individual RTP packets that are ready to be transmitted across the network and sends them to the RTPSocket created from:

 <protocol package-prefix>.media.protocol.rtpraw.DataSource

The RTPSocket created at <protocol-prefix>.media.protocol.rtpraw.DataSource is your own implementation of RTPSocket. The JMF API does not define a default implementation of RTPSocket. The implementation of RTPSocket is dependent on the underlying transport protocol that you are using. Your RTPSocket class must be located at <protocol-prefix>.media.protocol.rtpraw.DataSource.

You're responsible for transmitting the RTP packets out on the underlying network

In the following example, an RTPSocket is used to transmitting captured audio:

Example 10-5: Transmitting RTP data with RTPSocket (1 of 3)
         // Find a capture device that will capture linear audio 
         // data at 8bit 8Khz 
         
         AudioFormat format = new AudioFormat(AudioFormat.LINEAR, 
                                              8000, 
                                              8, 
                                              1); 
  
         Vector devices= CaptureDeviceManager.getDeviceList( format);
 
         CaptureDeviceInfo di= null;
         if (devices.size() > 0) {
              di = (CaptureDeviceInfo) devices.elementAt( 0);
         }
         else {
             // exit if we could not find the relevant capture device.
             System.exit(-1); 
         }
 
         // Create a processor for this capturedevice & exit if we 
         // cannot create it 
         
         try { 
             processor = Manager.createProcessor(di.getLocator()); 
         } catch (IOException e) { 
             System.exit(-1); 
         } catch (NoProcessorException e) { 
             System.exit(-1); 
         }  
         // configure the processor  
         processor.configure(); 
        
         // 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
         // output gsm data. 
         for (int i = 0; i < track.length; i++) { 
             if (!encodingOk && track[i] instanceof FormatControl) { 
 
                 if (((FormatControl)track[i]).
                     setFormat( new AudioFormat(AudioFormat.GSM_RTP, 
                                                8000, 
                                                8, 
                                                1)) == null) {
 
                     track[i].setEnabled(false); 
                 }
                 else {
                     encodingOk = true; 
                 }
             } 
             else { 
                 // 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 
         // gsm data or not. 
         // realize the processor 
         if (encodingOk) { 
             processor.realize(); 
             // block until realized. 
             // get the output datasource of the processor and exit 
             // if we fail 
             DataSource ds = null;
             try { 
                 ds = processor.getDataOutput(); 
             } catch (NotRealizedError e) { 
                 System.exit(-1);
             } 
 
             // hand this datasource to manager for creating an RTP 
             // datasink
             // our RTP datasimnk will multicast the audio 
             try {
                 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);
        
                 d.open();
                 d.start(); 
             } catch (Exception e) {
                 System.exit(-1);
             }   
         }    



CONTENTS | PREV | NEXT | INDEX

Copyright © 1998-1999 Sun Microsystems, Inc. All Rights Reserved.