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](http://today.java.net/images/2006/08/experiments-figure1-sm.gif)
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{
//
theaddressofthemediafileasanrtsp:
//
...String
private
Stringaddress;
//
theinputstreamtoreceiveresponsefromtheserver
private
InputStreamis;
//
theoutputstreamtowritetotheserver
private
OutputStreamos;
//
theincrementingsequencenumberforeachrequest
//
sentbytheclient
private
static
int
CSeq
=
1
;
//
thesessionidsentbytheserverafteraninitialsetup
private
StringsessionId;
//
thenumberoftracksinamediafile
private
Vectortracks
=
new
Vector(
2
);
//
flagstoindicatethestatusofasession
private
boolean
described,setup,playing;
private
Booleanstopped
=
true
;
//
constants
private
static
final
StringCRLF
=
"
"
;
private
static
final
StringVERSION
=
"
rtsp/1.0
"
;
private
static
final
StringTRACK_LINE
=
"
a=control:trackID=
"
;
private
static
final
StringTRANSPORT_DATA
=
"
TRANSPORT:UDP;unicast;client_port=8080-8081
"
;
private
static
final
StringRTSP_OK
=
"
RTSP/1.0200OK
"
;
//
baseconstructor,takesthemediaaddress,inputandoutputstreams
public
RTSPProtocolHandler( Stringaddress,InputStreamis,OutputStreamOs){
this
.address
=
address;
this
.is
=
is;
this
.os
=
Os; }
//
creates,sendsandparsesaDESCRIBEclientrequest
public
void
doDescribe()
throws
IOException{
//
ifalreadydescribed,return
if
(described)
return
;
//
createthebasecommand
StringbaseCommand
=
getBaseCommand(
"
DESCRIBE
"
+
address);
//
executeitandreadtheresponse
Stringresponse
=
doCommand(baseCommand);
//
theresponsewillcontaintrackinformation,amongstotherthings
parseTrackInformation(response);
//
setflag
described
=
true
; }
//
creates,sendsandparsesaSETUPclientrequest
public
void
doSetup()
throws
IOException{
//
ifnotdescribed
if
(
!
described)
throw
new
IOException(
"
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
)
throw
new
IOException(
"
Couldnotfindsessioninfo
"
);
//
now,sendSETUPcommandsforeachofthetracks
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); }
//
thisisnowsetup
setup
=
true
; }
//
issuesaPLAYcommand
public
void
doPlay()
throws
IOException{
//
mustbefirstsetup
if
(
!
setup)
throw
new
IOException(
"
NotSetup!
"
);
//
createbasecommand
StringbaseCommand
=
getBaseCommand(
"
PLAY
"
+
address);
//
addsessioninformation
baseCommand
+=
CRLF
+
"
Session:
"
+
sessionId;
//
executeit
doCommand(baseCommand);
//
setflags
playing
=
true
; stopped
=
false
; }
//
issuesaPAUSEcommand
public
void
doPause()
throws
IOException{
//
ifitisnotplaying,donothing
if
(
!
playing)
return
;
//
createbasecommand
StringbaseCommand
=
getBaseCommand(
"
PAUSE
"
+
address);
//
addsessioninformation
baseCommand
+=
CRLF
+
"
Session:
"
+
sessionId;
//
executeit
doCommand(baseCommand);
//
setflags
stopped
=
true
; playing
=
false
; }
//
issuesaTEARDOWNcommand
public
void
doTeardown()
throws
IOException{
//
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
private
StringgetBaseCommand(Stringcommand){
return
( command
+
"
"
+
VERSION
+
//
version
CRLF
+
"
CSeq:
"
+
(CSeq
++
)
//
incrementingsequence
); }
//
executesacommandandreceivesresponsefromserver
private
StringdoCommand(StringfullCommand)
throws
IOException{
//
toreadtheresponsefromtheserver
byte
[]buffer
=
new
byte
[
2048
];
//
debug
System.err.println(
"
======CLIENTREQUEST======
"
); System.err.println(fullCommand
+
CRLF
+
CRLF); System.err.println(
"
============================
"
);
//
sendacommand
os.write((fullCommand
+
CRLF
+
CRLF).getBytes());
//
readresponse
int
length
=
is.read(buffer); Stringresponse
=
new
String(buffer,
0
,length);
//
emptythebuffer
buffer
=
null
;
//
iftheresponsedoesn'tstartwithanallclear
if
(
!
response.startsWith(RTSP_OK))
throw
new
IOException(
"
Serverreturnedinvalidcode:
"
+
response);
//
debug
System.err.println(
"
======SERVERRESPONSE======
"
); System.err.println(response.trim()); System.err.println(
"
=============================
"
);
return
response; }
//
conveniencemethodtoparseaserverresponsetoDESCRIBEcommand
//
fortrackinformation
private
void
parseTrackInformation(Stringresponse){ StringlocalRef
=
response; StringtrackId
=
""
;
int
index
=
localRef.indexOf(TRACK_LINE);
//
iteratethroughtheresponsetofindallinstancesofthe
//
TRACK_LINE,whichindicatesallthetracks.Addallthe
//
trackid'stothetracksvector
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); } }
//
findoutthesessioninformationfromthefirstSETUPcommand
private
void
parseSessionInfo(Stringresponse){ sessionId
=
response.substring( response.indexOf(
"
Session:
"
)
+
"
Session:
"
.length(), response.indexOf(
"
Date:
"
)).trim(); } }