织梦CMS - 轻松建站从此开始!

罗索

Java ME下的流媒体开发体验(三):RTP处理

落鹤生 发布于 2011-10-23 19:34 点击:次 
Experiments in Streaming Content in Java ME(三)-----Back to RTPSourceStream and StreamingDataSource
TAG:

本文相关的完整源码下载:http://bbs.rosoo.net/forum.php?mod=viewthread&tid=8535

With the protocol handler in place, let's revisit the RTPSourceStream and StreamingDataSource classes from earlier, where they contained only place-holder methods. The StreamingDataSource is simple to code:

  1. import java.io.IOException; 
  2. import javax.microedition.media.Control; 
  3. import javax.microedition.media.protocol.DataSource; 
  4. import javax.microedition.media.protocol.SourceStream; 
  5.  
  6. public class StreamingDataSource extends DataSource { 
  7.  
  8.   // the full URL like locator to the destination 
  9.   private String locator; 
  10.  
  11.   // the internal streams that connect to the source 
  12.   // in this case, there is only one 
  13.   private SourceStream[] streams; 
  14.  
  15.   // is this connected to its source? 
  16.   private Boolean connected = false
  17.  
  18.   public StreamingDataSource(String locator) { 
  19.       super(locator); 
  20.       setLocator(locator); 
  21.   } 
  22.  
  23.   public void setLocator(String locator) { this.locator = locator; } 
  24.  
  25.   public String getLocator() { return locator; } 
  26.  
  27.   public void connect() throws IOException { 
  28.  
  29.     // if already connected, return 
  30.     if (connected) return
  31.  
  32.     // if locator is null, then can't actually connect 
  33.     if (locator == null) 
  34.       throw new IOException("locator is null"); 
  35.  
  36.     // now populate the sourcestream array 
  37.     streams = new RTPSourceStream[1]; 
  38.  
  39.     // with a new RTPSourceStream 
  40.     streams[0] = new RTPSourceStream(locator); 
  41.  
  42.     // set flag 
  43.     connected = true
  44.  
  45.     } 
  46.  
  47.   public void disconnect() { 
  48.  
  49.     // if there are any streams 
  50.     if (streams != null) { 
  51.  
  52.       // close the individual stream 
  53.         try { 
  54.           ((RTPSourceStream)streams[0]).close(); 
  55.         } catch(IOException ioex) {} // silent 
  56.     } 
  57.  
  58.     // and set the flag 
  59.     connected = false
  60.   } 
  61.  
  62.   public void start() throws IOException { 
  63.  
  64.     if(!connected) return
  65.  
  66.     // start the underlying stream 
  67.     ((RTPSourceStream)streams[0]).start(); 
  68.  
  69.   } 
  70.  
  71.   public void stop() throws IOException { 
  72.  
  73.     if(!connected) return
  74.  
  75.     // stop the underlying stream 
  76.     ((RTPSourceStream)streams[0])Close(); 
  77.  
  78.   } 
  79.  
  80.   public String getContentType() { 
  81.     // for the purposes of this article, it is only video/mpeg 
  82.     return "video/mpeg"
  83.   } 
  84.  
  85.   public Control[] getControls() { return new Control[0]; } 
  86.  
  87.   public Control getControl(String controlType) { return null; } 
  88.  
  89.   public SourceStream[] getStreams() {    return streams; } 
  90.  

The main work takes place in the connect() method. It creates a new RTPSourceStream with the requested address. Notice that the getContentType() method returns video/mpeg as the default content type, but change it to the supported content type for your system. Of course, this should not be hard-coded; it should be based on the actual support for different media types.

The next listing shows the complete RTPSourceStream class, which, along with RTSPProtocolHandler, does the bulk of work in connecting getting the RTP packets of the server:

  1. import java.io.IOException; 
  2. import java.io.InputStream; 
  3. import java.io.OutputStream; 
  4. import javax.microedition.io.Datagram; 
  5. import javax.microedition.io.Connector; 
  6. import javax.microedition.media.Control; 
  7. import javax.microedition.io.SocketConnection; 
  8. import javax.microedition.io.DatagramConnection; 
  9. import javax.microedition.media.protocol.SourceStream; 
  10. import javax.microedition.media.protocol.ContentDescriptor; 
  11.  
  12. public class RTPSourceStream implements SourceStream { 
  13.  
  14.     private RTSPProtocolHandler handler; 
  15.  
  16.     private InputStream is; 
  17.     private OutputStream Os; 
  18.  
  19.     private DatagramConnection socket; 
  20.  
  21.     public RTPSourceStream(String address) throws IOException { 
  22.  
  23.         // create the protocol handler and set it up so that the 
  24.         // application is ready to read data 
  25.  
  26.         // create a socketconnection to the remote host 
  27.         // (in this case I have set it up so that its localhost, you can 
  28.         // change it to wherever your server resides) 
  29.         SocketConnection sc = 
  30.           (SocketConnection)Connector.open("socket://localhost:554"); 
  31.  
  32.         // open the input and output streams 
  33.         is = sc.openInputStream(); 
  34.         Os = sc.openOutputStream(); 
  35.  
  36.         // and initialize the handler 
  37.         handler = new RTSPProtocolHandler(address, is, Os); 
  38.  
  39.         // send the basic signals to get it ready 
  40.         handler.doDescribe(); 
  41.         handler.doSetup(); 
  42.     } 
  43.  
  44.     public void start() throws IOException { 
  45.  
  46.       // open a local socket on port 8080 to read data to 
  47.       socket = (DatagramConnection)Connector.open("datagram://:8080"); 
  48.  
  49.       // and send the PLAY command 
  50.       handler.doPlay(); 
  51.     } 
  52.  
  53.     public void close() throws IOException { 
  54.  
  55.         if(handler != null) handler.doTeardown(); 
  56.  
  57.         is.close(); 
  58.         os.close(); 
  59.     } 
  60.  
  61.     public int read(byte[] buffer, int offset, int length) 
  62.       throws IOException { 
  63.  
  64.       // create a byte array which will be used to read the datagram 
  65.       byte[] fullPkt = new byte[length]; 
  66.  
  67.       // the new Datagram 
  68.       Datagram packet = socket.newDatagram(fullPkt, length); 
  69.  
  70.       // receive it 
  71.       socket.receive(packet); 
  72.  
  73.       // extract the actual RTP Packet's media data in the requested buffer 
  74.       RTPPacket rtpPacket = getRTPPacket(packet, packet.getData()); 
  75.       buffer = rtpPacket.getData(); 
  76.  
  77.       // debug 
  78.       System.err.println(rtpPacket + " with media length: " + buffer.length); 
  79.  
  80.       // and return its length 
  81.       return buffer.length; 
  82.     } 
  83.  
  84.     // extracts the RTP packet from each datagram packet received 
  85.     private RTPPacket getRTPPacket(Datagram packet, byte[] buf) { 
  86.  
  87.       // SSRC 
  88.       long SSRC = 0; 
  89.  
  90.         // the payload type 
  91.         byte PT = 0; 
  92.  
  93.       // the time stamp 
  94.         int timeStamp = 0; 
  95.  
  96.         // the sequence number of this packet 
  97.         short seqNo = 0; 
  98.  
  99.  
  100.         // see http://www.networksorcery.com/enp/protocol/rtp.htm 
  101.         // for detailed description of the packet and its data 
  102.         PT = 
  103.           (byte)((buf[1] & 0xff) & 0x7f); 
  104.  
  105.         seqNo = 
  106.           (short)((buf[2] << 8) | ( buf[3] & 0xff)); 
  107.  
  108.         timeStamp = 
  109.           (((buf[4] & 0xff) << 24) | ((buf[5] & 0xff) << 16) | 
  110.             ((buf[6] & 0xff) << 8) | (buf[7] & 0xff)) ; 
  111.  
  112.         SSRC = 
  113.           (((buf[8] & 0xff) << 24) | ((buf[9] & 0xff) << 16) | 
  114.             ((buf[10] & 0xff) << 8) | (buf[11] & 0xff)); 
  115.  
  116.  
  117.         // create an RTPPacket based on these values 
  118.         RTPPacket rtpPkt = new RTPPacket(); 
  119.  
  120.         // the sequence number 
  121.         rtpPkt.setSequenceNumber(seqNo); 
  122.  
  123.         // the timestamp 
  124.         rtpPkt.setTimeStamp(timeStamp); 
  125.  
  126.         // the SSRC 
  127.         rtpPkt.setSSRC(SSRC); 
  128.  
  129.         // the payload type 
  130.         rtpPkt.setPayloadType(PT); 
  131.  
  132.         // the actual payload (the media data) is after the 12 byte header 
  133.         // which is constant 
  134.         byte payload[] = new byte [packet.getLength() - 12]; 
  135.  
  136.         for(int i=0; i < payload.length; i++) payload [i] = buf[i+12]; 
  137.  
  138.         // set the payload on the RTP Packet 
  139.         rtpPkt.setData(payload); 
  140.  
  141.         // and return the payload 
  142.         return rtpPkt; 
  143.  
  144.     } 
  145.  
  146.     public long seek(long where) throws IOException { 
  147.      throw new IOException("cannot seek"); 
  148.     } 
  149.  
  150.     public long tell() { return -1; } 
  151.  
  152.     public int getSeekType() { return NOT_SEEKABLE;    } 
  153.  
  154.     public Control[] getControls() { return null; } 
  155.  
  156.     public Control getControl(String controlType) { return null; } 
  157.  
  158.     public long getContentLength() { return -1;    } 
  159.  
  160.     public int getTransferSize() { return -1;    } 
  161.  
  162.     public ContentDescriptor getContentDescriptor() { 
  163.         return new ContentDescriptor("audio/rtp"); 
  164.     } 

The constructor for the

RTPSourceStream

creates a

SocketConnection

to the remote server (hard-coded to the local server and port here, but you can change this to accept any server or port). It then opens the input and output streams, which it uses to create the

RTSPProtocolHandler

. Finally, using this handler, it sends the

DESCRIBE

and

SETUP

commands to the remote server to get the server ready to send the packets. The actual delivery doesn't start until the

start()

method is called by the

StreamingDataSource

, which opens up a local port (hard-coded to

8081

in this case) for receiving the packets and sends the

PLAY

command to start receiving these packets. The actual reading of the packets is done in the

read()

method, which receives the individual packets, strips them to create the

RTPPacket

instances (with the

getRTPPacket()

method), and returns the media data in the buffer supplied while calling the

read()

method.

A MIDlet to see if it works

With all the classes in place, let's write a simple MIDlet to first create a Player instance that will use the StreamingDataSource to connect to the server and then get media packets from it. The Player interface is defined by the MMAPI and allows you to control the playback (or recording) of media. Instances of this interface are created by using the Manager class from the MMAPI javax.microedition.media package (see the MMAPI tutorial). The following shows this rudimentary MIDlet:

  1. import javax.microedition.media.Player; 
  2. import javax.microedition.midlet.MIDlet; 
  3. import javax.microedition.media.Manager; 
  4.  
  5. public class StreamingMIDlet extends MIDlet { 
  6.  
  7.   public void startApp() { 
  8.  
  9.     try { 
  10.  
  11.       // create Player instance, realize it and then try to start it 
  12.       Player player = 
  13.         Manager.createPlayer( 
  14.           new StreamingDataSource( 
  15.             "rtsp://localhost:554/sample_100kbit.mp4")); 
  16.  
  17.       player.realize(); 
  18.  
  19.       player.start(); 
  20.  
  21.     } catch(Exception e) { 
  22.             e.printStackTrace(); 
  23.     } 
  24.   } 
  25.  
  26.   public void pauseApp() {} 
  27.  
  28.   public void destroyApp(boolean unconditional) {} 

So what should happen when you run this MIDlet in the Wireless toolkit? I have on purpose left out any code to display the resulting video on screen. When I run it in the toolkit, I know that I am receiving the packets because I see the debug statements as shown in Figure 2.

Running StreamingMIDlet output
Figure 2. Running StreamingMIDlet output

The RTP packets as sent by the server are being received. The StreamingDataSource along with the RTSPProtocolHandler and RTPSourceStream are doing their job of making the streaming server send these packets. This is confirmed by looking at the streaming server's admin console as shown in Figure 3.

Figure 3 - Darwin's admin console
shows that the file is being streamed
Figure 3. Darwin's admin console shows that the file is being streamed (click for full-size image).

Unfortunately, the player constructed by the Wireless toolkit is trying to read the entire content at one go. Even if I were to make a StreamingVideoControl, it will not display the video until it has read the whole file, therefore defeating the purpose of the streaming aspect of this whole experiment. So what needs to be done to achieve the full streaming experience?

Ideally, MMAPI should provide the means for developers to register the choice of Player for the playback of certain media. This is easily achieved by providing a new method in the Manager class for registering (or overriding) MIME types or protocols with developer-made Player instances. For example, let's say I create a Player instance that reads streaming data called StreamingMPEGPlayer. With the Manager class, I should be able to say Manager.registerPlayer("video/mpeg", StreamingMPEGPlayer.class) or Manager.registerPlayer("rtsp", StreamingMPEGPlayer.class). MMAPI should then simply load this developer-made Player instance and use this as the means to read data from the developer-made datasource.

In a nutshell, you need to be able to create an independent media player and register it as the choice of instance for playing the desired content. Unfortunately, this is not possible with the current MMAPI implementation, and this is the data consumption conundrum that I had talked about earlier.

Of course, if you can test this code in a toolkit that does not need to read the complete data before displaying it (or for audio files, playing them), then you have achieved the aim of streaming data using the existing MMAPI implementation.

This experiment should prove that you can stream data with the current MMAPI implementation, but you may not be able to manipulate it in a useful manner until you have better control over the Manager and Player instances. I look forward to your comments and experiments using this code.

Resources

Vikram Goyal is the author of Pro Java ME MMAPI.

View all java.net Articles.

(feidragon319)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201110/15183.html]
本文出处:blog.csdn.net/feidragon319 作者:feidragon319
顶一下
(3)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容