001: /*
002: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License version
007: * 2 only, as published by the Free Software Foundation.
008: *
009: * This program is distributed in the hope that it will be useful, but
010: * WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * General Public License version 2 for more details (a copy is
013: * included at /legal/license.txt).
014: *
015: * You should have received a copy of the GNU General Public License
016: * version 2 along with this work; if not, write to the Free Software
017: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
018: * 02110-1301 USA
019: *
020: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
021: * Clara, CA 95054 or visit www.sun.com if you need additional
022: * information or have any questions.
023: */
024: package com.sun.mmedia.rtsp;
025:
026: import java.io.IOException;
027: import java.util.Random;
028: import java.util.Vector;
029: import javax.microedition.media.MediaException;
030:
031: import com.sun.mmedia.rtsp.protocol.*;
032: import com.sun.mmedia.rtsp.sdp.*;
033:
034: /**
035: * Manager for RTSP connections.
036: *
037: * @author Marc Owerfeldt
038: * @created September 11, 2002
039: */
040: public class RtspManager {
041: // timer (in ms):
042: private final int TIMER_1 = 5000;
043: // used for describe msg
044: private final int TIMER_2 = 3000;
045: // used for all other messages
046:
047: private String mediaTypes[];
048: private long sequenceNumber;
049: private int numberOfTracks;
050: private String userAgent;
051: private RtspUrl rtspUrl;
052: private String mediaControls[];
053: private int server_ports[];
054: private int client_ports[];
055: private String session_ids[];
056: private Message message;
057: private int connectionId;
058: private double startPos;
059: private String processError;
060: private long duration;
061: private String vol;
062: private Vector listeners;
063: private Connection connection;
064: private int videoWidth;
065: private int videoHeight;
066:
067: /**
068: * Highest port value allowed for RTP port pairs.
069: */
070: private final int MAX_PORT = 65535;
071:
072: private boolean responseReceived;
073: private boolean dataReceived;
074:
075: private Object responseSync = new Object();
076:
077: /**
078: * Constructor for the RtspManager.
079: *
080: * @param url The RTSP URL, for example "rtsp://rio:1554/br.mov".
081: * @exception MediaException Description of the Exception
082: */
083: public RtspManager(String url) throws MediaException {
084: try {
085: rtspUrl = new RtspUrl(url);
086: } catch (IOException e) {
087: throw new MediaException("Invalid URL: " + url);
088: }
089:
090: listeners = new Vector();
091:
092: sequenceNumber = new Random().nextInt();
093:
094: userAgent = "User-Agent: MMAPI RTSP Player Version 1.0 Personal Profile";
095: }
096:
097: /**
098: * Sets the media starting position in the next RTSP PLAY
099: * request. .
100: *
101: * @param startPos The new start position in microseconds.
102: */
103: public void setStartPos(double startPos) {
104: this .startPos = startPos;
105: }
106:
107: /**
108: * Gets the media type for the specified track.
109: *
110: * @param track The track index, i.e. track 0, 1,....
111: * @return Returns a string containing the media type.
112: */
113: public String getMediaType(int track) {
114: return mediaTypes[track];
115: }
116:
117: /**
118: * Gets an array of media types.
119: *
120: * @return Returns an array of media types.
121: */
122: public String[] getMediaTypes() {
123: return mediaTypes;
124: }
125:
126: /**
127: * Gets the duration of the media stream.
128: *
129: * @return The duration value in microseconds.
130: */
131: public long getDuration() {
132: return duration;
133: }
134:
135: /**
136: * Gets the video width.
137: *
138: * @return The video width.
139: */
140: public int getVideoWidth() {
141: return videoWidth;
142: }
143:
144: /**
145: * Gets the video height.
146: *
147: * @return The video height.
148: */
149: public int getVideoHeight() {
150: return videoHeight;
151: }
152:
153: /**
154: * Creates a new connection to the specified RTSP server.
155: *
156: * @return Returns true if the connection was established
157: * successfully, otherwise false.
158: */
159: public boolean createConnection() {
160: try {
161: connection = new Connection(this , rtspUrl.getHost(),
162: rtspUrl.getPort());
163: } catch (IOException e) {
164: return false;
165: }
166:
167: return true;
168: }
169:
170: /**
171: * Closes the TCP/IP connection to the RTSP server.
172: */
173: public void closeConnection() {
174: if (connection != null) {
175: connection.close();
176: }
177: }
178:
179: /**
180: * Sends an RTSP SETUP request to the RTSP server and
181: * processes its response.
182: *
183: * @return Returns true if the session was set-up successfully,
184: * otherwise false.
185: */
186: public boolean rtspSetup() {
187: String msg = "DESCRIBE rtsp://" + rtspUrl.getHost() + "/"
188: + rtspUrl.getFile() + " RTSP/1.0\r\n" + "CSeq: "
189: + sequenceNumber + "\r\n"
190: + "Accept: application/sdp\r\n" + userAgent
191: + "\r\n\r\n";
192:
193: sendMessage(msg);
194:
195: boolean timeout = waitForResponse(TIMER_1);
196:
197: if (timeout) {
198: processError = "Server not responding!";
199: return false;
200: }
201:
202: if (!responseOk()) {
203: return false;
204: }
205:
206: setDuration();
207:
208: numberOfTracks = getNumTracks();
209:
210: client_ports = new int[numberOfTracks];
211:
212: for (int i = 0; i < numberOfTracks; i++) {
213: client_ports[i] = allocateClientPort();
214: }
215:
216: // get control strings and media types:
217: mediaControls = new String[numberOfTracks];
218: mediaTypes = new String[numberOfTracks];
219: String dynamicPayloads[] = new String[numberOfTracks];
220:
221: for (int i = 0; i < numberOfTracks; i++) {
222: mediaTypes[i] = getCurMediaType(i);
223: mediaControls[i] = getMediaAttributeValue(i, "control");
224: dynamicPayloads[i] = getMediaAttributeValue(i, "rtpmap");
225:
226: setVideoSize(i);
227:
228: // Add the dynamic payloads if there's one.
229: // if (mediaTypes[i] != null && dynamicPayloads[i] != null) {
230: // addDynamicPayload(mgrs[i], mediaTypes[i], dynamicPayloads[i]);
231: // }
232: }
233:
234: // get the content base:
235: String contentBase = getContentBase();
236:
237: session_ids = new String[numberOfTracks];
238:
239: server_ports = new int[numberOfTracks];
240:
241: // setup the individual tracks:
242: for (int i = 0; i < numberOfTracks; i++) {
243: if (i == 0) {
244: msg = "SETUP " + contentBase + mediaControls[i]
245: + " RTSP/1.0\r\n" + "CSeq: " + sequenceNumber
246: + "\r\n"
247: + "Transport: RTP/AVP;unicast;client_port="
248: + client_ports[i] + "-" + (client_ports[i] + 1)
249: + "\r\n" + userAgent + "\r\n\r\n";
250: } else {
251: msg = "SETUP " + contentBase + mediaControls[i]
252: + " RTSP/1.0\r\n" + "CSeq: " + sequenceNumber
253: + "\r\n"
254: + "Transport: RTP/AVP;unicast;client_port="
255: + client_ports[i] + "-" + (client_ports[i] + 1)
256: + "\r\n" + "Session: " + session_ids[0]
257: + "\r\n" + userAgent + "\r\n\r\n";
258: }
259:
260: sendMessage(msg);
261:
262: timeout = waitForResponse(TIMER_2);
263:
264: if (timeout) {
265: processError = "Server not responding";
266: return false;
267: }
268:
269: if (!responseOk()) {
270: return false;
271: }
272:
273: String sessionId = getSessionId();
274:
275: if (sessionId == null) {
276: processError = "Invalid session ID";
277: return false;
278: }
279:
280: session_ids[i] = sessionId;
281:
282: int pos = session_ids[i].indexOf(';');
283:
284: if (pos > 0) {
285: session_ids[i] = session_ids[i].substring(0, pos);
286: }
287:
288: int serverPort = getServerDataPort();
289:
290: if (serverPort == -1) {
291: processError = "Invalid server data port";
292: return false;
293: }
294:
295: server_ports[i] = serverPort;
296: }
297:
298: return true;
299: }
300:
301: /**
302: * Checks, if the RTSP server's response returned with OK.
303: *
304: * @return Returns true if the request was processed sucessfully by
305: * the RTSP server, otherwise false.
306: */
307: private boolean responseOk() {
308: boolean result = false;
309:
310: int statusCode = getStatusCode();
311:
312: if (statusCode == com.sun.mmedia.rtsp.protocol.StatusCode.OK) {
313: result = true;
314: } else {
315: processError = "Error #" + statusCode + " - "
316: + getStatusText(statusCode);
317: }
318:
319: return result;
320: }
321:
322: /**
323: * Gets the session ID.
324: *
325: * @return The session ID value.
326: */
327: private String getSessionId() {
328: String id = null;
329:
330: try {
331: ResponseMessage responseMsg = (ResponseMessage) message
332: .getParameter();
333:
334: SessionHeader hdr = (SessionHeader) responseMsg
335: .getResponse().getHeader(Header.SESSION).parameter;
336:
337: id = hdr.getSessionId();
338: } catch (Exception e) {
339: }
340:
341: return id;
342: }
343:
344: /**
345: * Gets the server data port.
346: *
347: * @return The server data port.
348: */
349: private int getServerDataPort() {
350: int port = -1;
351:
352: try {
353: ResponseMessage responseMsg = (ResponseMessage) message
354: .getParameter();
355:
356: TransportHeader transport_hdr = (TransportHeader) responseMsg
357: .getResponse().getHeader(Header.TRANSPORT).parameter;
358:
359: port = transport_hdr.getServerDataPort();
360: } catch (Exception e) {
361: }
362:
363: return port;
364: }
365:
366: /**
367: * Sends an RTSP START message.
368: *
369: * @return Returns true, if the RTSP session was started successfully, otherwise false.
370: */
371: public boolean rtspStart() {
372: String msg;
373:
374: if (numberOfTracks == 0) {
375: // processError = "Cannot play any track of this stream";
376: return false;
377: }
378:
379: // start all tracks in one compound statement:
380: msg = "PLAY rtsp://" + rtspUrl.getHost() + "/"
381: + rtspUrl.getFile() + " RTSP/1.0\r\n" + "CSeq: "
382: + sequenceNumber + "\r\n" + "Range: npt="
383: + (int) (startPos / 1000000) + "-\r\n" + "Session: "
384: + session_ids[0] + "\r\n" + userAgent + "\r\n\r\n";
385:
386: sendMessage(msg);
387:
388: boolean timeout = waitForResponse(TIMER_2);
389:
390: if (timeout) {
391: processError = "Server is not responding";
392: return false;
393: }
394:
395: int code = getStatusCode();
396:
397: if (code == -1) {
398: processError = "Received invalid status code";
399: return false;
400: }
401:
402: if (getStatusCode() == com.sun.mmedia.rtsp.protocol.StatusCode.SESSION_NOT_FOUND) {
403: for (int i = 0; i < numberOfTracks; i++) {
404: // mgrs[ i].removeTargets( "session not found");
405: // mgrs[ i].dispose();
406:
407: return false;
408: }
409: }
410:
411: return true;
412: }
413:
414: /**
415: * Sends an RTSP STOP message.
416: */
417: public void rtspStop() {
418: String msg = "PAUSE rtsp://" + rtspUrl.getHost() + "/"
419: + rtspUrl.getFile() + " RTSP/1.0\r\n" + "CSeq: "
420: + sequenceNumber + "\r\n" + "Session: "
421: + session_ids[0] + "\r\n" + userAgent + "\r\n\r\n";
422:
423: sendMessage(msg);
424:
425: boolean timeout = waitForResponse(TIMER_2);
426:
427: if (timeout) {
428: processError = "Server is not responding";
429: return;
430: }
431: }
432:
433: /**
434: * Sends an RTSP TEARDOWN message.
435: */
436: public void rtspTeardown() {
437: if (session_ids != null) {
438: String msg = "TEARDOWN rtsp://" + rtspUrl.getHost() + "/"
439: + rtspUrl.getFile() + " RTSP/1.0\r\n" + "CSeq: "
440: + sequenceNumber + "\r\n" + "Session: "
441: + session_ids[0] + "\r\n" + userAgent + "\r\n\r\n";
442:
443: sendMessage(msg);
444:
445: boolean timeout = waitForResponse(TIMER_2);
446:
447: if (timeout) {
448: processError = "Server is not responding";
449: return;
450: }
451: }
452: }
453:
454: /**
455: * Gets the status code of the RTSP response message.
456: *
457: * @return The status code if the message can be parsed successfully,
458: * otherwise -1.
459: */
460: public int getStatusCode() {
461: int code = -1;
462:
463: try {
464: ResponseMessage responseMsg = (ResponseMessage) message
465: .getParameter();
466: code = responseMsg.getResponse().getStatusLine().getCode();
467: } catch (Exception e) {
468: }
469:
470: return code;
471: }
472:
473: /**
474: * Gets the status description for the specified RTSP status code.
475: *
476: * @param code The RTSP status code.
477: * @return The status description.
478: */
479: private String getStatusText(int code) {
480: return StatusCode.getStatusText(code);
481: }
482:
483: /**
484: * Parses the SDP range attribute and sets the duration for this
485: * media stream.
486: */
487: private void setDuration() {
488: duration = 0;
489: ResponseMessage msg = (ResponseMessage) message.getParameter();
490: int start_time = 0;
491: // in milliseconds
492: int end_time = 0;
493: // in milliseconds
494:
495: SdpParser sdp = msg.getResponse().sdp;
496:
497: if (sdp != null) {
498: MediaAttribute attribute = sdp.getSessionAttribute("range");
499:
500: if (attribute != null) {
501: String value = attribute.getValue();
502: duration = 0;
503: if (value.startsWith("npt")) {
504:
505: try {
506: int start = value.indexOf('=') + 1;
507: int end = value.indexOf('-');
508: String startTime = value.substring(start, end)
509: .trim();
510: String endTime = value.substring(end + 1)
511: .trim();
512:
513: int dotIndex = endTime.indexOf(".");
514:
515: if (dotIndex < 0) {
516: end_time = Integer.parseInt(endTime) * 1000;
517: } else {
518: String p1 = "0"
519: + endTime.substring(0, dotIndex);
520: String p2 = endTime.substring(dotIndex + 1);
521: end_time = Integer.parseInt(p1) * 1000;
522: end_time += Integer.parseInt((p2 + "000")
523: .substring(0, 3));
524: }
525:
526: duration = (long) ((end_time - start_time) * 1000);
527: } catch (Exception e) {
528: duration = 0;
529: }
530: }
531: }
532: }
533: }
534:
535: /**
536: * Sets the video size attribute of the RtspManager object
537: *
538: * @param track The new videoSize value
539: */
540: private void setVideoSize(int track) {
541: String value = getMediaAttributeValue(track, "cliprect");
542:
543: if (value != null) {
544: try {
545: int start = value.indexOf(',') + 1;
546:
547: start = value.indexOf(',', start) + 1;
548:
549: int end = value.indexOf(',', start);
550:
551: String heightStr = value.substring(start, end);
552: String widthStr = value.substring(end + 1).trim();
553:
554: videoWidth = Integer.parseInt(widthStr);
555: videoHeight = Integer.parseInt(heightStr);
556: } catch (Exception e) {
557: }
558: }
559: }
560:
561: /**
562: * Gets the number of tracks from the SDP media description.
563: *
564: * @return The number of tracks.
565: */
566: private int getNumTracks() {
567: int numTracks = 0;
568:
569: ResponseMessage msg = (ResponseMessage) message.getParameter();
570:
571: SdpParser sdp = msg.getResponse().sdp;
572:
573: if (sdp != null) {
574: numTracks = sdp.getMediaDescriptions().size();
575: }
576:
577: return numTracks;
578: }
579:
580: /**
581: * Gets the media type for the specified track.
582: *
583: * @param index The track index.
584: * @return The media type.
585: */
586: private String getCurMediaType(int index) {
587: String type = null;
588:
589: try {
590: ResponseMessage msg = (ResponseMessage) message
591: .getParameter();
592:
593: SdpParser sdp = msg.getResponse().sdp;
594:
595: MediaDescription md = (MediaDescription) sdp
596: .getMediaDescriptions().elementAt(index);
597:
598: type = md.name;
599: } catch (Exception e) {
600: }
601:
602: return type;
603: }
604:
605: /**
606: * Gets the mediaAttribute attribute of the RtspManager class
607: *
608: * @param md Description of the Parameter
609: * @param attribute Description of the Parameter
610: * @return The mediaAttribute value
611: */
612: public static String getMediaAttribute(MediaDescription md,
613: String attribute) {
614:
615: String mediaAttribute = "";
616:
617: if (md != null) {
618: MediaAttribute ma = md.getMediaAttribute("control");
619:
620: if (ma != null) {
621: mediaAttribute = ma.getValue();
622: }
623: }
624:
625: return mediaAttribute;
626: }
627:
628: /**
629: * Gets the mediaAttributeValue attribute of the RtspManager object
630: *
631: * @param i Description of the Parameter
632: * @param attribute Description of the Parameter
633: * @return The mediaAttributeValue value
634: */
635: private String getMediaAttributeValue(int i, String attribute) {
636: String value = null;
637:
638: try {
639: ResponseMessage msg = (ResponseMessage) message
640: .getParameter();
641:
642: SdpParser sdp = msg.getResponse().sdp;
643:
644: MediaDescription md = (MediaDescription) sdp
645: .getMediaDescriptions().elementAt(i);
646:
647: MediaAttribute ma = md.getMediaAttribute(attribute);
648:
649: value = ma.getValue();
650: } catch (Exception e) {
651: }
652:
653: return value;
654: }
655:
656: /**
657: * Gets the content base specified in the SDP portion of the DESCRIBE response.
658: *
659: * @return The content base.
660: */
661: private String getContentBase() {
662: String contentBase = "";
663:
664: try {
665: ResponseMessage responseMsg = (ResponseMessage) message
666: .getParameter();
667:
668: Header header = responseMsg.getResponse().getHeader(
669: Header.CONTENT_BASE);
670:
671: ContentBaseHeader cbh = (ContentBaseHeader) header.parameter;
672:
673: contentBase = cbh.getContentBase();
674: } catch (Exception e) {
675: }
676:
677: return contentBase;
678: }
679:
680: /**
681: * Sends a message to the RTSP server.
682: *
683: * @param message The RTSP message to be sent.
684: */
685: private void sendMessage(String message) {
686: responseReceived = false;
687:
688: if (!connection.connectionIsAlive()) {
689: createConnection();
690: }
691:
692: if (connection.sendData(message.getBytes())) {
693: // System.out.println(message);
694: }
695: }
696:
697: /**
698: * Waits for a response from the RTSP server.
699: *
700: * @param time Timout value in milliseconds.
701: * @return Returns true, if a timeout event occurred.
702: */
703: private synchronized boolean waitForResponse(int time) {
704: boolean timeout = false;
705:
706: try {
707: synchronized (responseSync) {
708: if (!responseReceived) {
709: responseSync.wait(time);
710: }
711:
712: if (responseReceived) {
713: sequenceNumber++;
714: } else {
715: timeout = true;
716: }
717: }
718: } catch (InterruptedException e) {
719: timeout = true;
720: }
721:
722: return timeout;
723: }
724:
725: /**
726: * Processes a request from the RTSP server.
727: *
728: * @param connectionId The connection ID.
729: * @param message The request message.
730: */
731: private void processRtspRequest(int connectionId, Message message) {
732: if (message.getType() == MessageType.OPTIONS) {
733: OptionsMessage msg = (OptionsMessage) message
734: .getParameter();
735:
736: sendResponse(connectionId, msg.getRequest());
737: }
738: }
739:
740: /**
741: * Processes a response from the RTSP server.
742: *
743: * @param connectionId The connection ID.
744: * @param message The response message.
745: */
746: private void processRtspResponse(int connectionId, Message message) {
747: this .message = message;
748:
749: responseReceived = true;
750:
751: synchronized (responseSync) {
752: responseSync.notify();
753: }
754: }
755:
756: /**
757: * Sends a response to the RTSP server.
758: *
759: * @param connectionId The connection ID.
760: * @param msg The original request message.
761: */
762: private void sendResponse(int connectionId, Request msg) {
763: String type = null;
764:
765: Header header = msg.getHeader(Header.CSEQ);
766:
767: if (header != null) {
768: CSeqHeader cSeqHeader = (CSeqHeader) header.parameter;
769:
770: String message = "RTSP/1.0 200 OK\r\n" + "CSeq: "
771: + cSeqHeader.getSequenceNumber() + "\r\n\r\n";
772:
773: sendMessage(message);
774: }
775: }
776:
777: /**
778: * Gets the number of tracks of this media presentation.
779: *
780: * @return The number of tracks.
781: */
782: public int getNumberOfTracks() {
783: return numberOfTracks;
784: }
785:
786: /**
787: * Gets the server ports pair (RTP/RTCP).
788: *
789: * @return The server ports.
790: */
791: public int[] getServerPorts() {
792: return server_ports;
793: }
794:
795: /**
796: * Gets the clientPorts attribute of the RtspManager object
797: *
798: * @return The clientPorts value
799: */
800: public int[] getClientPorts() {
801: return client_ports;
802: }
803:
804: /**
805: * Callback method indicating that an RTSP message has
806: * been received.
807: *
808: * @param message The RTSP message.
809: */
810: public void rtspMessageIndication(Message message) {
811: if (message.getType() == MessageType.RESPONSE) {
812: processRtspResponse(connectionId, message);
813: } else {
814: processRtspRequest(connectionId, message);
815: }
816: }
817:
818: /**
819: * Gets the address of the RTSP server.
820: *
821: * @return The server address value.
822: */
823: public String getServerAddress() {
824: return rtspUrl.getHost();
825: }
826:
827: /**
828: * Description of the Method
829: *
830: * @param connectionId Description of the Parameter
831: */
832: public void rtspConnectionTerminated(int connectionId) {
833: // System.out.println( "RtspPlayer::rtspConnectionTerminated");
834: }
835:
836: /**
837: * Sets the detailed error description in case an error occurred while
838: * parsing or processing RTSP messages.
839: *
840: * @param error The error description.
841: */
842: public void setProcessError(String error) {
843: processError = error;
844: }
845:
846: /**
847: * Retrieves the error description if an error occurred during
848: * parsing or processing of RTSP messages.
849: *
850: * @return The error description.
851: */
852: public String getProcessError() {
853: return processError;
854: }
855:
856: /**
857: * Finds a pair of RTP/RTCP ports.
858: *
859: * The port returned is an even numbered port.to be used as
860: * the RTP data port. Port + 1 is allocated as the RTCP port.
861: *
862: * @return Returns an RTP data port.
863: */
864: private int allocateClientPort() {
865: boolean found = false;
866:
867: int port = -1;
868:
869: Random random = new Random();
870:
871: while (!found) {
872: do {
873: port = random.nextInt();
874:
875: if (port % 2 != 0) {
876: port++;
877: }
878: } while (port < 1024 || (port > MAX_PORT - 1));
879:
880: // needs to test if the client can actually bind to
881: // these port....
882:
883: found = true;
884: }
885:
886: return port;
887: }
888: }
|