Receiving and Presenting RTP Media Streams

接收和播放RTP媒体流

 

JMF  PlayersProcessors为RTP流提供播放,拍摄,和数据转换机置。


Figure 9-1: RTP reception data flow.

 

一个单独的播放器通过session管理器被用于每个接收到的流。你可以通过标准Manager  createPlayer机置为一个RTP流创建一个Player。你也可以:

 

l  使用一个具有RTP session参数的MediaLocator,并且通过调用Manager.createPlayer(MediaLocator)创建一个Player

l  通过从流中找回的数据源为一个特殊的ReceiveStream创建一个Player,然后把它传入Manager.createPlayer(DataSource)

 

如果你使用一个MediaLocator创建一个Player,你可以只播放第一个在session中被发现的RTP流。如果你想在session中回放多个RTP流,你需要直接使用SessionManager并为每个ReceiveStream创建一个Player

 

为一个RTP session创建一个Player

 

你使用一个MediaLocator为一个RTP session创建一个Player时,Manager为第一次在session中被发现的流创建了一个Player。当数据已经被探测到时,Player提交一个RealizeCompleteEvent事件。

 

通过监听RealizeCompleteEvent,你可以确定是否有数据到达,并且Player是否能够播放任何数据。一旦Player提交这个事件,你可以重新找回它的可视和控制组件。

 

注意:因为一个播放RTP媒体流的Playersession探测到数据以前不能准确完成,你就不应该试着用Manager.createRealizedPlayer为一个RTP媒体流创建一个Player。在数据到达时Player才会返回,然后如果没有数据被探测到,试着去创建一个Realized  Player将发生不确定的阻塞。

 

一个Player能输出一个RTP-speciifc控制,提供全部会议统计表和能通过SessionManager被用于注册动态有效载荷的RTPControl

 

Example 9-1: Creating a Player for an RTP session (1 of 2)

        String url= "rtp://224.144.251.104:49150/audio/1";

 

         MediaLocator mrl= new MediaLocator(url);

        

         if (mrl == null) {

             System.err.println("Can't build MRL for RTP");

             return false;

         }

        

         // Create a player for this rtp session

         try {

             player = Manager.createPlayer(mrl);

         } catch (NoPlayerException e) {

             System.err.println("Error:" + e);

             return false;

         } catch (MalformedURLException e) {

             System.err.println("Error:" + e);

             return false;

         } catch (IOException e) {

             System.err.println("Error:" + e);

             return false;

         }

        

         if (player != null) {

             if (this.player == null) {

                 this.player = player;

                 player.addControllerListener(this);

                 player.realize();

             }

         }

 

监听格式改变

 

当一个Player提交一个FormatChangeEvent事件时,它可能显示发生了一个有效载荷的改变。 用一个MediaLocator自动创建的Players处理有效载荷的改变。最多的情况,这个过程包含创建一个新的Player去控制一个新的格式。播放RTP媒体流的应用程序需要为FormatChangeEvents监听,以便他们在一个新的Player被创建时可以回应。

 

当一个FormatChangeEvent被提交时,检查这个Player对象是否被控制并且可视组件是否改变。如果是的话,一个新的Player已经被创建,你需要移除在旧Player对象组件上的引用,然后得到新Player对象的组件。

 

Example 9-2: Listening for RTP format changes (1 of 2)

     public synchronized void controllerUpdate(ControllerEvent ce) {

         if (ce instanceof FormatChangeEvent) {

             Dimension vSize = new Dimension(320,0);

             Component oldVisualComp = visualComp;

            

             if ((visualComp = player.getVisualComponent()) != null) {

                 if (oldVisualComp != visualComp) {

                     if (oldVisualComp != null) {

                         oldVisualComp.remove(zoomMenu);

                     }

                    

                     framePanel.remove(oldVisualComp);

                    

                     vSize = visualComp.getPreferredSize();

                     vSize.width = (int)(vSize.width * defaultScale);

                     vSize.height = (int)(vSize.height * defaultScale);

                    

                     framePanel.add(visualComp);

                    

                     visualComp.setBounds(0,

                                          0,

                                          vSize.width,

                                          vSize.height);

                     addPopupMenu(visualComp);

                 }

             }

 

             Component oldComp = controlComp;

            

             controlComp = player.getControlPanelComponent();

 

             if (controlComp != null)

             {

                 if (oldComp != controlComp)

                 {

                     framePanel.remove(oldComp);

                     framePanel.add(controlComp);

                                         

                     if (controlComp != null) {

                         int prefHeight = controlComp

                                          .getPreferredSize()

                                          .height;

                        

                         controlComp.setBounds(0,

                                               vSize.height,

                                               vSize.width,

                                               prefHeight);

                     }

                 }

             }

         }

     }

 

 

为每个新接收流创建一个RTP Player

 

要在一个session中播放所有的ReceiveStreams,你需要为每个流创建一个单独的Player。当一个新流被创建时,会议管理器提交一个NewReceiveStreamEvent事件。通常,你注册为一个ReceiveStreamListener,然后为每个新的ReceiveStream创建一个Player。要创建一个Player,你从ReceiveStream中找回数据源,再把它传入Manager.createPlayer

 

要在一个session中为每个新接收流创建一个Player

1.架起一个RTP session:

a.       创建一个SessionManager。例如,创建com.sun.media.rtp.RTPSessionMgr的实例。(RTPSessionMgr是一个用JMF引用执行提供的SessionManager的实现)

b.       调用RTPSessionMgs  addReceiveStreamListener注册为一个监听器。

c.       通过调用RTPSessionMgr  initSession初始化RTP.

d.       通过调用RTPSessionMgr  startSession启动RTP session

 

Example 9-3: Setting up an RTP session (1 of 2)

     public SessionManager createManager(String address,

                                         int port,

                                         int ttl,

                                         boolean listener,

                                         boolean sendlistener)

     {

         mgr = (SessionManager)new com.sun.media.rtp.RTPSessionMgr();

        

         if (mgr == null) return null;

 

         mgr.addFormat(new AudioFormat(AudioFormat.DVI_RTP,

                                       44100,

                                       4,

                                       1),

                       18);

 

         if (listener) mgr.addReceiveStreamListener(this);

         if (sendlistener) new RTPSendStreamWindow(mgr);

        

         // ask session mgr to generate the local participant's CNAME

         String cname = mgr.generateCNAME();

         String username = null;

 

         try {

             username = System.getProperty("user.name");

         } catch (SecurityException e){

             username = "jmf-user";

         }

        

         // create our local Session Address

         SessionAddress localaddr = new SessionAddress();

        

         try{

             InetAddress destaddr = InetAddress.getByName(address);

 

             SessionAddress sessaddr = new SessionAddress(destaddr,

                                                          port,

                                                          destaddr,

                                                          port + 1);

             SourceDescription[] userdesclist= new SourceDescription[]

             {

                 new SourceDescription(SourceDescription

                                       .SOURCE_DESC_EMAIL,

                                       "jmf-user@sun.com",

                                       1,

                                       false),

 

                 new SourceDescription(SourceDescription

                                       .SOURCE_DESC_CNAME,

                                       cname,

                                       1,

                                       false),

 

                 new SourceDescription(SourceDescription

                                       .SOURCE_DESC_TOOL,

                                       "JMF RTP Player v2.0",

                                       1,

                                       false)

             };

 

             mgr.initSession(localaddr,

                             userdesclist,

                             0.05,

                             0.25);

            

             mgr.startSession(sessaddr,ttl,null);

         } catch (Exception e) {

             System.err.println(e.getMessage());

             return null;

         }

        

         return mgr;

     }

 

2.在你的ReceiveStreamListener  update方法中,注意NewReceiveStreamEvent事件,它显示了一个新的被探测到的数据流。

3.当一个NewReceiveStreamEvnet被发现,通过调用getReceiveStreamNewReceiveStreamEvnet中重新得到ReceiveStream

4.通过调用getDataSourceReceiveStream中重新得到RTP数据源。这是一个带着一个RTP-specific格式的PushBufferDataSource。例如,编码为一个DVI音频的播放器将会是DVI_RTP

5.传递DataSourceManager.createPlayer创建一个Player。因为Player将被成功的创建,所以解码和解包RTP格式的数据有效的插件是必须的。

 

Example 9-4: Listening for NewReceiveStreamEvents  

     public void update( ReceiveStreamEvent event)

     {

         Player newplayer = null;

         RTPPlayerWindow playerWindow = null;

 

         // find the sourceRTPSM for this event

         SessionManager source = (SessionManager)event.getSource();

 

         // create a new player if a new recvstream is detected

         if (event instanceof NewReceiveStreamEvent)

         {

             String cname = "Java Media Player";

             ReceiveStream stream = null;

            

             try

             {

                 // get a handle over the ReceiveStream

                 stream =((NewReceiveStreamEvent)event)

                         .getReceiveStream();

 

                 Participant part = stream.getParticipant();

 

                 if (part != null) cname = part.getCNAME();

 

                 // get a handle over the ReceiveStream datasource

                 DataSource dsource = stream.getDataSource();

                

                 // create a player by passing datasource to the

                 // Media Manager

                 newplayer = Manager.createPlayer(dsource);

                 System.out.println("created player " + newplayer);

             } catch (Exception e) {

                 System.err.println("NewReceiveStreamEvent exception "

                                    + e.getMessage());

                 return;

             }

 

             if (newplayer == null) return;

 

             playerlist.addElement(newplayer);

             newplayer.addControllerListener(this);

           

             // send this player to player GUI

             playerWindow = new RTPPlayerWindow( newplayer, cname);

         }

     }

 

See RTPUtil in RTPUtil for a complete example.

处理RTP有效载荷的改变

 

如果RTP Session中流的有效载荷改变了,ReceiveStream将提交一个RemotePayloadChangeEvent事件。一般说来,当有效载荷改变时,现有的Player将不能处理新的格式,而且当你试图表现新的有效载荷时,JMF会抛出一个异常。要避免这种情况,你的ReceiveStreamListener需要观察RemotePayloadChangeEvents事件。当一个RemotePayloadChangeEvent被侦测到时,你需要:

1.关闭现有的Player

2.移除已删除的Player上的所有监听。

3.用同一个RTP数据源创建一个新的Player

4.为新的Player得到可视和控制组件。

5.为新的Player添加必要的监听。

 

Example 9-5: Handling RTP payload changes (1 of 2)

     public void update(ReceiveStreamEvent event) {

         if (event instanceof RemotePayloadChangeEvent) {

             // payload has changed. we need to close the old player

             // and create a new player 

            

             if (newplayer != null) {

                 // stop player and wait for stop event

                 newplayer.stop();

               

                 // block until StopEvent received...

       

                 // remove controllerlistener

                 newplayer.removeControllerListener(listener);

               

                 // remove any visual and control components

                 // attached to this application

                 // close the player and wait for close event   

                 newplayer.close();

               

                 // block until ControllerClosedEvent received...

       

                 try {

                     // when the player was closed, its datasource was

                     // disconnected. Now we must reconnect the data-

                     // source before a player can be created for it.

                     // This is the same datasource received from

                     // NewReceiveStreamEvent and used to create the

                     // initial rtp player

                    

                     rtpsource.connect();

                     newplayer = Manager.createPlayer(rtpsource);

               

                     if (newplayer == null) {

                         System.err.println("Could not create player");

                         return;

                     }

                    

                     newplayer.addControllerListener(listener);

                     newplayer.realize();

       

                     // when the new player is realized, retrieve its

                     // visual and control components

                 } catch (Exception e) {

                     System.err.println("could not create player");

                 }

             }

         }

     }

 

控制输入RTP流的缓冲

 

通过SessionManager,你可以控制RTP接收端穿过输出的BufferControl。你能够用这个控制设置两个参数,缓冲长度和开端。

 

缓冲长度是接收器维护的缓冲区的大小。开端是数据总量的最小值,控制压入数据之前,它将被缓冲,或者允许数据被压入(jitter  buffer)。当这个开端的最小值传入时,对象中的数据将唯一变得有效。如果被缓冲的数据数量少于开端,数据将再次被缓冲直到开端被传入。

 

缓冲的长度和开端值被指定为毫秒。缓冲的音频包的数量或者视频帧依靠输入流的格式。每个接收流包含它自己的默认值和所有缓冲长度的最大值和最小开端。(这个默认值和最大缓冲长度被依赖的执行。)

 

要得到一个sessionBufferControl,你调用SessionManager上的getControl。你可以通过调用getControlComponentBufferControl重新得到一个GUI 组件。

 

RTPSocket 播放RTP流

 

RTP不依赖于传输协议。通用使用RTPSocket,你可以在任何一种网络上流化RTP。RTP socket的格式被设计成既有数据也有控制通道。每一个通道都有一个输入和输出流来流化数据在网络上的进出。

 

SessionManager期望从RTPSocket中接收单独的RTP包。用户有责任流化RTP包到RTPSocket

 

要从RTPSocket中播放一个RTP流,传递这个socketManager.createPlayer创建Player.二选一的,你可以通过调用createPlayer(MediaLocator)创建一个Player,然后用一个RTP变量的新协议”rtpraw”传递一个MediaLocator。例如:

 

Manager.createPlayer( new  MediaLocator(“rtpraw:// ”));

 

根据JMF播放器的创建机置,Manager将试图创建数据源定义在:

 

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

 

这必须是RTPSocketRTPSocket的内容将被设置到rtprawManager随后将试图创建一个<content-prefix>.media.content.rptraw.Handler类型的player,然后在其上设置RTPSocket

 

注意:在<protocol  package-prefix>.media.protocol.rtpraw.DataSource上创建的RTPSocket是你自己的RTPSocket的实现。JMF  API没有定义一个默认的RTPSocket 的实现。这个RTPSocket的实现依赖于你所使用的底层传输协议。你的RTPSocket类必须位于<protocol  package-prefix>.media.protocol.rtpraw.DataSource,并且它的控制和数据管道流必须被设置成接下来的例子的样子。

 

作为RTPSocketRTPControl接口将被用于添加动态的有效载荷信息到RTP会议管理器。

 

接下来的例子实现了在UDP  player上接收RTP  UDP包和流化他们到Playersession管理器,而不被底层的网络/传输协议知道。这个例子使用定义在javax.media.rtp.RTPSocket中的接口和相关类。

 

Example 9-6: RTPSocketPlayer (1 of 6)

 import java.io.*;

 import java.net.*;

 import java.util.*;

 

 import javax.media.*;

 import javax.media.format.*;

 import javax.media.protocol.*;

 import javax.media.rtp.*;

 import javax.media.rtp.event.*;

 import javax.media.rtp.rtcp.*;

 

 public class RTPSocketPlayer implements ControllerListener {

     // ENTER THE FOLLOWING SESSION PARAMETERS FOR YOUR RTP SESSION

    

     // RTP Session address, multicast, unicast or broadcast address

     String address = "224.144.251.245";

    

     // RTP Session port

     int port = 49150;

    

     // Media Type i.e. one of audio or video

     String media = "audio";

    

     // DO NOT MODIFY ANYTHING BELOW THIS LINE 

         

     // The main rtpsocket abstraction which we will create and send

     // to the Manager for appropriate handler creation

     RTPSocket rtpsocket = null;

       

     // The control RTPPushDataSource of the above RTPSocket

     RTPPushDataSource rtcpsource = null;

    

     // The GUI to handle the player

     // PlayerWindow playerWindow;

    

     // The handler created for the RTP session,

     // as returned by the Manager

     Player player;

 

     // maximum size of buffer for UDP receive from the sockets

     private  int maxsize = 2000;

 

     UDPHandler rtp = null;

     UDPHandler rtcp = null;

      

     public RTPSocketPlayer() {

         // create the RTPSocket

         rtpsocket = new RTPSocket();

         // set its content type :

         // rtpraw/video for a video session

         // rtpraw/audio for an audio session

         String content = "rtpraw/" + media;

         rtpsocket.setContentType(content);

        

         // set the RTP Session address and port of the RTP data

         rtp = new UDPHandler(address, port);

        

         // set the above UDP Handler to be the

         // sourcestream of the rtpsocket

         rtpsocket.setOutputStream(rtp);

        

         // set the RTP Session address and port of the RTCP data

         rtcp = new UDPHandler(address, port +1);

        

         // get a handle over the RTCP Datasource so that we can

         // set the sourcestream and deststream of this source

         // to the rtcp udp handler we created above.

         rtcpsource = rtpsocket.getControlChannel();

        

         // Since we intend to send RTCP packets from the        

         // network to the session manager and vice-versa, we need

         // to set the RTCP UDP handler as both the input and output

         // stream of the rtcpsource.

         rtcpsource.setOutputStream(rtcp);

         rtcpsource.setInputStream(rtcp);

         

         // connect the RTP socket data source before

         // creating the player

         try {

             rtpsocket.connect();

             player = Manager.createPlayer(rtpsocket);

             rtpsocket.start();

         } catch (NoPlayerException e) {

             System.err.println(e.getMessage());

             e.printStackTrace();

             return;

         }

         catch (IOException e) {

             System.err.println(e.getMessage());

             e.printStackTrace();

             return;

         }

 

         if (player != null) {

             player.addControllerListener(this);

             // send this player to out playerwindow

             // playerWindow = new PlayerWindow(player);

         }

     }

 

     public synchronized void controllerUpdate(ControllerEvent ce) {

         if ((ce instanceof DeallocateEvent) ||

             (ce instanceof ControllerErrorEvent)) {

        

             // stop udp handlers

             if (rtp != null) rtp.close();

            

             if (rtcp != null) rtcp.close();

         }

     }

    

     // method used by inner class UDPHandler to open a datagram or

     // multicast socket as the case maybe

    

     private DatagramSocket InitSocket(String sockaddress,

                                      int     sockport)

     {

         InetAddress addr = null;

         DatagramSocket sock = null;

 

         try {

             addr = InetAddress.getByName(sockaddress);

            

             if (addr.isMulticastAddress()) {

                 MulticastSocket msock;

                

                 msock = new MulticastSocket(sockport);

 

                 msock.joinGroup(addr);

 

                 sock = (DatagramSocket)msock;          

             }

             else {             

                 sock = new DatagramSocket(sockport,addr);

             }

            

             return sock;

         }

         catch (SocketException e) {

             e.printStackTrace();

             return null;

         }

         catch (UnknownHostException e) {

             e.printStackTrace();

             return null;

         }

         catch (IOException e) {

             e.printStackTrace();

             return null;

         }

     }

 

     // INNER CLASS UDP Handler which will receive UDP RTP Packets and

     // stream them to the handler of the sources stream. IN case of

     // RTCP, it will also accept RTCP packets and send them on the

     // underlying network.

 

     public class UDPHandler extends Thread implements PushSourceStream,

                                                       OutputDataStream

     {

         DatagramSocket        mysock;

         DatagramPacket        dp;

         SourceTransferHandler outputHandler;

         String                myAddress;

         int                   myport;

         boolean               closed = false;

 

 

         // in the constructor we open the socket and create the main

         // UDPHandler thread.

        

         public UDPHandler(String haddress, int hport) {

             myAddress = haddress;

             myport = hport;

             mysock = InitSocket(myAddress,myport);                 

             setDaemon(true);

             start();

         }

 

         // the main thread receives RTP data packets from the

         // network and transfer's this data to the output handler of

         // this stream.

        

         public void run() {

             int len;

 

             while(true) {

                 if (closed) {

                     cleanup();

                     return;

                 }

                 try {

                     do {

                         dp = new DatagramPacket( new byte[maxsize],

                                                  maxsize);

                        

                         mysock.receive(dp);

 

                         if (closed){

                             cleanup();

                             return;

                         }

                        

                         len = dp.getLength();

                         if (len > (maxsize >> 1)) maxsize = len << 1;

                     }

                     while (len >= dp.getData().length);

                 }catch (Exception e){

                     cleanup();

                     return;

                 }

                

                 if (outputHandler != null) {

                     outputHandler.transferData(this);

                 }

             }

         }

 

         public void close() {

             closed = true;

         }

 

         private void cleanup() {

             mysock.close();

             stop();

         }

        

         // methods of PushSourceStream

         public Object[] getControls() {

             return new Object[0];

         }

        

         public Object getControl(String controlName) {

             return null;

         }

 

         public ContentDescriptor getContentDescriptor() {

             return null;

         }

 

         public long getContentLength() {

             return SourceStream.LENGTH_UNKNOWN;

         }

 

         public boolean endOfStream() {

             return false;

         }

 

         // method by which data is transferred from the underlying

         // network to the session manager.

        

         public int read(byte buffer[],

                         int offset,

                         int length)

         {

             System.arraycopy(dp.getData(),

                              0,

                              buffer,

                              offset,

                              dp.getLength());

            

             return dp.getData().length;

         }               

        

         public int getMinimumTransferSize(){

             return dp.getLength();

         }

        

         public void setTransferHandler(SourceTransferHandler

                                        transferHandler)

         {

             this.outputHandler = transferHandler;

         }

        

         // methods of OutputDataStream used by the session manager to

         // transfer data to the underlying network.

        

         public int write(byte[] buffer,

                          int offset,

                          int length)

         {

             InetAddress addr = null;

        

             try {

                 addr = InetAddress.getByName(myAddress);

             } catch (UnknownHostException e) {

                 e.printStackTrace();

             }

 

             DatagramPacket dp = new DatagramPacket( buffer,

                                                     length,

                                                     addr,

                                                     myport);

             try {

                 mysock.send(dp);

             } catch (IOException e){

                 e.printStackTrace();

             }

            

             return dp.getLength();

         }

     }

 

     public static void main(String[] args) {

         new RTPSocketPlayer();

     }

 }

 

   

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值