Experiments in Streaming Content in Java ME(二)----Creating an RTSP Protocol Handler

Recall that RTSP is the actual protocol over which streaming commands are initiated, through which the RTP packets are received. The RTSP protocol is like a command initiator, a bit like HTTP. For a really good explanation of a typical RTSP session, please see these specifications for a simple RTSP client. For the purposes of this article, I am going to oversimplify the protocol implementation. Figure 1 shows the typical RTSP session between a client and a streaming server.

Figure 1 - A typical RTSP session
between a RTSP client and a streaming server
Figure 1. A typical RTSP session between a RTSP client and a streaming server (click for full-size image).

In a nutshell, an RTSP client initiates a session by sending a DESCRIBE request to the streaming server which means that the client wants more information about a media file. An example DESCRIBE request may look like this:

DESCRIBE rtsp://localhost:554/media.3gp rtsp/1.0
CSeq: 1

The URL for the media file is followed by the RTSP version that the client is following, and a carriage return/line feed (CRLF). The next line contains the sequence number of this request and increments for each subsequent request sent to the server. The command is terminated by a single line on its own (as are all RTSP commands).

All client commands that are successful receive a response that starts with RTSP/1.0 200 OK. For the DESCRIBE request, the server responds with several parameters, and if the file is present and streamable, this response contains any information for any tracks in special control strings that start with a a=control:trackID= String. The trackID is important and is used to create the next requests to the server.

Once described, the media file's separate tracks are set up for streaming using the SETUP command, and these commands should indicate the transport properties for the subsequent RTP packets. This is shown here:

SETUP rtsp://localhost:554/media.3gp/trackID=3 rtsp/1.0
CSeq: 2
TRANSPORT: UDP;unicast;client_port=8080-8081

The previous command indicates to the server to set up to stream trackID 3 of the media.3gp file, to send the packets via UDP, and to send them to port 8080 on the client (8081 is for RTCP commands). The response to the first SETUP command (if it is okay) will contain the session information for subsequent commands and must be included as shown here:

SETUP rtsp://localhost:554/media.3gp/trackID=3 rtsp/1.0
CSeq: 3
Session: 556372992204
TRANSPORT: UDP;unicast;client_port=8080-8081

An OK response from the server indicates that you can send the PLAY command, which will make the server start sending the RTP packets:

PLAY rtsp://localhost:554/media.3gp rtsp/1.0
CSeq: 3
Session: 556372992204

Notice that the PLAY command is issued only on the main media file, and not on any individual tracks. The same is true for the PAUSE and TEARDOWN commands, which are identical to the PLAY command, except for the command itself.

The following listing contains the RTSPProtocolHandler class. The comments in the code and the brief information so far should help with understanding how this protocol handler works:

 
 
import  java.util.Vector;
import  java.io.InputStream;
import  java.io.IOException;
import  java.io.OutputStream;

public   class  RTSPProtocolHandler {

    
//  the address of the media file as an rtsp: // ... String
     private  String address;

    
//  the inputstream to receive response from the server
     private  InputStream is;

    
//  the outputstream to write to the server
     private  OutputStream os;

    
//  the incrementing sequence number for each request
    
//  sent by the client
     private   static   int  CSeq  =   1 ;

    
//  the session id sent by the server after an initial setup
     private  String sessionId;

    
//  the number of tracks in a media file
     private  Vector tracks  =   new  Vector( 2 );

    
//  flags to indicate the status of a session
     private   boolean  described, setup, playing;
    
private  Boolean stopped  =   true ;

    
//  constants
     private   static   final  String CRLF  =   " " ;
    
private   static   final  String VERSION  =   " rtsp/1.0 " ;
    
private   static   final  String TRACK_LINE  =   " a=control:trackID= " ;
    
private   static   final  String TRANSPORT_DATA  =
      
" TRANSPORT: UDP;unicast;client_port=8080-8081 " ;
    
private   static   final  String RTSP_OK  =   " RTSP/1.0 200 OK " ;

    
//  base constructor, takes the media address, input and output streams
     public  RTSPProtocolHandler(
        String address, InputStream is, OutputStream Os) {

        
this .address  =  address;
        
this .is  =  is;
        
this .os  =  Os;
    }

    
//  creates, sends and parses a DESCRIBE client request
     public   void  doDescribe()  throws  IOException {

        
//  if already described, return
         if (described)  return ;

        
//  create the base command
        String baseCommand  =  getBaseCommand( " DESCRIBE  "   +  address);

        
//  execute it and read the response
        String response  =  doCommand(baseCommand);

        
//  the response will contain track information, amongst other things
        parseTrackInformation(response);

        
//  set flag
        described  =   true ;
    }

    
//  creates, sends and parses a SETUP client request
     public   void  doSetup()  throws  IOException {

        
//  if not described
         if ( ! described)  throw   new  IOException( " Not Described! " );

        
//  create the base command for the first SETUP track
        String baseCommand  =
          getBaseCommand(
                
" SETUP  "   +  address  +   " /trackID= "   +  tracks.elementAt( 0 ));

        
//  add the static transport data
        baseCommand  +=  CRLF  +  TRANSPORT_DATA;

        
//  read response
        String response  =  doCommand(baseCommand);

        
//  parse it for session information
        parseSessionInfo(response);

        
//  if session information cannot be parsed, it is an error
         if (sessionId  ==   null )
          
throw   new  IOException( " Could not find session info " );

        
//  now, send SETUP commands for each of the tracks
         int  cntOfTracks  =  tracks.size();
        
for ( int  i  =   1 ; i  <  cntOfTracks; i ++ ) {
            baseCommand 
=
                getBaseCommand(
                    
" SETUP  "   +  address  +   " /trackID= "   +  tracks.elementAt(i));
            baseCommand 
+=  CRLF  +   " Session:  "   +  sessionId  +  CRLF  +  TRANSPORT_DATA;
            doCommand(baseCommand);
        }

        
//  this is now setup
        setup  =   true ;
    }

    
//  issues a PLAY command
     public   void  doPlay()  throws  IOException {

        
//  must be first setup
         if ( ! setup)  throw   new  IOException( " Not Setup! " );

        
//  create base command
        String baseCommand  =  getBaseCommand( " PLAY  "   +  address);

        
//  add session information
        baseCommand  +=  CRLF  +   " Session:  "   +  sessionId;

        
//  execute it
        doCommand(baseCommand);

        
//  set flags
        playing  =   true ;
        stopped 
=   false ;
    }

    
//  issues a PAUSE command
     public   void  doPause()  throws  IOException {

        
//  if it is not playing, do nothing
         if ( ! playing)  return ;

        
//  create base command
        String baseCommand  =  getBaseCommand( " PAUSE  "   +  address);

        
//  add session information
        baseCommand  +=  CRLF  +   " Session:  "   +  sessionId;

        
//  execute it
        doCommand(baseCommand);

        
//  set flags
        stopped  =   true ;
        playing 
=   false ;
    }

    
//  issues a TEARDOWN command
     public   void  doTeardown()  throws  IOException {

        
//  if not setup, nothing to teardown
         if ( ! setup)  return ;

        
//  create base command
        String baseCommand  =  getBaseCommand( " TEARDOWN  "   +  address);

        
//  add session information
        baseCommand  +=  CRLF  +   " Session:  "   +  sessionId;

        
//  execute it
        doCommand(baseCommand);

        
//  set flags
        described  =  setup  =  playing  =   false ;
        stopped 
=   true ;
    }

    
//  this method is a convenience method to put a RTSP command together
     private  String getBaseCommand(String command) {

        
return (
            command 
+
            
"   "   +
            VERSION 
+   //  version
            CRLF  +
            
" CSeq:  "   +  (CSeq ++ //  incrementing sequence
        );
    }

    
//  executes a command and receives response from server
     private  String doCommand(String fullCommand)  throws  IOException {

        
//  to read the response from the server
         byte [] buffer  =   new   byte [ 2048 ];

        
//  debug
        System.err.println( "  ====== CLIENT REQUEST ======  " );
        System.err.println(fullCommand 
+  CRLF  +  CRLF);
        System.err.println(
"  ============================  " );

        
//  send a command
        os.write((fullCommand  +  CRLF  +  CRLF).getBytes());

        
//  read response
         int  length  =  is.read(buffer);

        String response 
=   new  String(buffer,  0 , length);

        
//  empty the buffer
        buffer  =   null ;

        
//  if the response doesn't start with an all clear
         if ( ! response.startsWith(RTSP_OK))
          
throw   new  IOException( " Server returned invalid code:  "   +  response);

        
//  debug
        System.err.println( "  ====== SERVER RESPONSE ======  " );
        System.err.println(response.trim());
        System.err.println(
"  ============================= " );

        
return  response;
    }

    
//  convenience method to parse a server response to DESCRIBE command
    
//  for track information
     private   void  parseTrackInformation(String response) {

        String localRef 
=  response;
        String trackId 
=   "" ;
        
int  index  =  localRef.indexOf(TRACK_LINE);

        
//  iterate through the response to find all instances of the
        
//  TRACK_LINE, which indicates all the tracks. Add all the
        
//  track id's to the tracks vector
         while (index  !=   - 1 ) {
            
int  baseIdx  =  index  +  TRACK_LINE.length();
            trackId 
=  localRef.substring(baseIdx, baseIdx  +   1 );
            localRef 
=  localRef.substring(baseIdx  +   1 , localRef.length());
            index 
=  localRef.indexOf(TRACK_LINE);
            tracks.addElement(trackId);
        }

    }

    
//  find out the session information from the first SETUP command
     private   void  parseSessionInfo(String response) {

        sessionId 
=
          response.substring(
                response.indexOf(
" Session:  " +   " Session:  " .length(),
                response.indexOf(
" Date: " )).trim();

    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值