日期:2014-05-20  浏览次数:20740 次

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:

importjava.util.Vector; importjava.io.InputStream; importjava.io.IOException; importjava.io.OutputStream; publicclassRTSPProtocolHandler{ //theaddressofthemediafileasanrtsp://...String privateStringaddress; //theinputstreamtoreceiveresponsefromtheserver privateInputStreamis; //theoutputstreamtowritetotheserver privateOutputStreamos; //theincrementingsequencenumberforeachrequest //sentbytheclient privatestaticintCSeq=1; //thesessionidsentbytheserverafteraninitialsetup privateStringsessionId; //thenumberoftracksinamediafile privateVectortracks=newVector(2); //flagstoindicatethestatusofasession privatebooleandescribed,setup,playing; privateBooleanstopped=true; //constants privatestaticfinalStringCRLF=" "; privatestaticfinalStringVERSION="rtsp/1.0"; privatestaticfinalStringTRACK_LINE="a=control:trackID="; privatestaticfinalStringTRANSPORT_DATA= "TRANSPORT:UDP;unicast;client_port=8080-8081"; privatestaticfinalStringRTSP_OK="RTSP/1.0200OK"; //baseconstructor,takesthemediaaddress,inputandoutputstreams publicRTSPProtocolHandler( Stringaddress,InputStreamis,OutputStreamOs){ this.address=address; this.is=is; this.os=Os; } //creates,sendsandparsesaDESCRIBEclientrequest publicvoiddoDescribe()throwsIOException{ //ifalreadydescribed,return if(described)return; //createthebasecommand StringbaseCommand=getBaseCommand("DESCRIBE"+address); //executeitandreadtheresponse Stringresponse=doCommand(baseCommand); //theresponsewillcontaintrackinformation,amongstotherthings parseTrackInformation(response); //setflag described=true; } //creates,sendsandparsesaSETUPclientrequest publicvoiddoSetup()throwsIOException{ //ifnotdescribed if(!described)thrownewIOException("NotDescribed!"); //createthebasecommandforthefirstSETUPtrack StringbaseCommand= getBaseCommand( "SETUP"+address+"/trackID="+tracks.elementAt(0)); //addthestatictransportdata baseCommand+=CRLF+TRANSPORT_DATA; //readresponse Stringresponse=doCommand(baseCommand); //parseitforsessioninformation parseSessionInfo(response); //ifsessioninformationcannotbeparsed,itisanerror if(sessionId==null) thrownewIOException("Couldnotfindsessioninfo"); //now,sendSETUPcommandsforeachofthetracks intcntOfTracks=tracks.size(); for(inti=1;i<cntOfTracks;i++){ baseCommand= getBaseCommand( "SETUP"+address+"/trackID="+tracks.elementAt(i)); baseCommand+=CRLF+"Session:"+sessionId+CRLF+TRANSPORT_DATA; doCommand(baseCommand); } //thisisnowsetup setup=true; } //issuesaPLAYcommand publicvoiddoPlay()throwsIOException{ //mustbefirstsetup if(!setup)thrownewIOException("NotSetup!"); //createbasecommand StringbaseCommand=getBaseCommand("PLAY"+address); //addsessioninformation baseCommand+=CRLF+"Session:"+sessionId; //executeit doCommand(baseCommand); //setflags playing=true; stopped=false; } //issuesaPAUSEcommand publicvoiddoPause()throwsIOException{ //ifitisnotplaying,donothing if(!playing)return; //createbasecommand StringbaseCommand=getBaseCommand("PAUSE"+address); //addsessioninformation baseCommand+=CRLF+"Session:"+sessionId; //executeit doCommand(baseCommand); //setflags stopped=true; playing=false; } //issuesaTEARDOWNcommand publicvoiddoTeardown()throwsIOException{ //ifnotsetup,nothingtoteardown if(!setup)return; //createbasecommand StringbaseCommand=getBaseCommand("TEARDOWN"+address); //addsessioninformation baseCommand+=CRLF+"Session:"+sessionId; //executeit doCommand(baseCommand); //setflags described=setup=playing=false; stopped=true; } //thismethodisaconveniencemethodtoputaRTSPcommandtogether privateStringgetBaseCommand(Stringcommand){ return( command+ ""+ VERSION+//version CRLF+ "CSeq:"+(CSeq++)//incrementingsequence ); } //executesacommandandreceivesresponsefromserver privateStringdoCommand(StringfullCommand)throwsIOException{ //toreadtheresponsefromtheserver byte[]buffer=newbyte[2048]; //debug System.err.println("======CLIENTREQUEST======"); System.err.println(fullCommand+CRLF+CRLF); System.err.println("============================"); //sendacommand os.write((fullCommand+CRLF+CRLF).getBytes()); //readresponse intlength=is.read(buffer); Stringresponse=newString(buffer,0,length); //emptythebuffer buffer=null; //iftheresponsedoesn'tstartwithanallclear if(!response.startsWith(RTSP_OK)) thrownewIOException("Serverreturnedinvalidcode:"+response); //debug System.err.println("======SERVERRESPONSE======"); System.err.println(response.trim()); System.err.println("============================="); returnresponse; } //conveniencemethodtoparseaserverresponsetoDESCRIBEcommand //fortrackinformation privatevoidparseTrackInformation(Stringresponse){ StringlocalRef=response; StringtrackId=""; intindex=localRef.indexOf(TRACK_LINE); //iteratethroughtheresponsetofindallinstancesofthe //TRACK_LINE,whichindicatesallthetracks.Addallthe //trackid'stothetracksvector while(index!=-1){ intbaseIdx=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); } } //findoutthesessioninformationfromthefirstSETUPcommand privatevoidparseSessionInfo(Stringresponse){ sessionId= response.substring( response.indexOf("Session:")+"Session:".length(), response.indexOf("Date:")).trim(); } }