/* * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.mmedia.rtsp; import java.io.IOException; import java.io.InputStream; import java.util.Random; import javax.microedition.media.Player; import javax.microedition.media.MediaException; import javax.microedition.media.protocol.SourceStream; import com.sun.j2me.log.Logging; import com.sun.j2me.log.LogChannels; import com.sun.mmedia.protocol.BasicDS; import com.sun.mmedia.sdp.*; public class RtspDS extends BasicDS { private static final int RESPONSE_TIMEOUT = 5000; private static final int MIN_PORT = 1024; // inclusive private static final int MAX_PORT = 65536; // exclusive private static Random rnd = new Random(System.currentTimeMillis()); private Object msgWaitEvent = new Object(); private RtspIncomingMessage response = null; private RtspConnection connection = null; private boolean started = false; private RtspUrl url = null; private int seqNum = 0; private String sessionId = null; private String contentBase = null; private RtspRange range = null; private int sessionTimeout = 60; // in seconds, 60 is default according to the spec private RtspSS[] streams = null; private static int nextPort = MIN_PORT; public RtspUrl getUrl() { return url; } public void setLocator(String ml) throws MediaException { try { url = new RtspUrl(ml); super.setLocator(ml); } catch (IOException ioe) { throw new MediaException(ioe.toString()); } } public synchronized void connect() throws IOException { if (null == connection) { try { connection = new RtspConnection(this); seqNum = rnd.nextInt(); if (!sendRequest(RtspOutgoingRequest.DESCRIBE(seqNum, url))) { throw new IOException("RTSP DESCRIBE request failed"); } contentBase = response.getContentBase(); SdpSessionDescr sdp = response.getSdp(); if (null == sdp) throw new IOException("no SDP data received"); SdpMediaAttr range_attr = sdp.getSessionAttribute("range"); if (null != range_attr) { try { range = new RtspRange(range_attr.getValue()); } catch (NumberFormatException e) { range = null; } } int num_tracks = sdp.getMediaDescriptionsCount(); if (0 == num_tracks) num_tracks = 1; streams = new RtspSS[num_tracks]; String mediaControl; RtspOutgoingRequest setup_request; // sessionId is null at this point for (int i = 0; i < num_tracks; i++) { int first_client_data_port = allocPort(); int client_data_port = first_client_data_port; RtpConnection conn = null; String cdescr = null; do { try { conn = new RtpConnection(client_data_port); conn.startListening(); } catch (IOException e) { client_data_port = allocPort(); } } while (null == conn && client_data_port != first_client_data_port); if (null == conn) { throw new IOException("Unable to allocate client UDP port"); } if (i < sdp.getMediaDescriptionsCount()) { SdpMediaDescr md = sdp.getMediaDescription(i); mediaControl = md.getMediaAttribute("control").getValue(); SdpMediaAttr rtpmap = md.getMediaAttribute("rtpmap"); if (null != rtpmap) { new RtpPayloadType(rtpmap.getValue()); } if (-1 != md.payload_type) { RtpPayloadType pt = RtpPayloadType.get(md.payload_type); if (null != pt) { cdescr = pt.getDescr(); } } } else { mediaControl = null; } if (null != contentBase || null != mediaControl) { setup_request = RtspOutgoingRequest.SETUP(seqNum, (null == contentBase) ? "" : contentBase, (null == mediaControl) ? "" : mediaControl, sessionId, client_data_port); } else { setup_request = RtspOutgoingRequest.SETUP(seqNum, url, sessionId, client_data_port); } if (!sendRequest(setup_request)) { throw new IOException("SETUP request failed"); } if (0 == i) { sessionId = response.getSessionId(); Integer timeout = response.getSessionTimeout(); if (null != timeout) { sessionTimeout = timeout.intValue(); } } RtspTransportHeader th = response.getTransportHeader(); if (th.getClientDataPort() != client_data_port) { // Returned value for client data port is different. // An attempt is made to re-allocate UDP port accordingly. if (null != conn) { conn.stopListening(); } client_data_port = th.getClientDataPort(); conn = new RtpConnection(client_data_port); conn.startListening(); } streams[i] = new RtspSS(conn); if (null != cdescr) { streams[i].setContentDescriptor(cdescr); } } start(); } catch (InterruptedException e) { connection = null; Thread.currentThread().interrupt(); throw new IOException("connect to " + locator + " aborted: " + e.getMessage()); } catch (IOException e) { connection = null; throw new IOException("failed to connect to " + locator + " : " + e.getMessage()); } } } public synchronized void disconnect() { if( null != connection ) { try { sendRequest( RtspOutgoingRequest.TEARDOWN( seqNum, url, sessionId ) ); } catch( InterruptedException e ) { Thread.currentThread().interrupt(); } catch (IOException e) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, "IOException in RtspDS.disconnect(): " + e.getMessage()); } } finally { connection.close(); connection = null; } } } public synchronized void start() throws IOException { if (null == connection) throw new IllegalStateException("RTSP: Not connected"); if (!started) { try { started = sendRequest(RtspOutgoingRequest.PLAY(seqNum, url, sessionId)); } catch (InterruptedException e) { throw new IOException("start aborted: " + e.getMessage()); } } } public synchronized void stop() throws IOException { if (null == connection || !started) return; try { sendRequest(RtspOutgoingRequest.PAUSE(seqNum, url, sessionId)); } catch (InterruptedException e) { throw new IOException("stop aborted: " + e.getMessage()); } } public synchronized SourceStream[] getStreams() { if (null == connection) throw new IllegalStateException("RTSP: Not connected"); return streams; } public synchronized long getDuration() { if (null != range) { float from = range.getFrom(); float to = range.getTo(); if (RtspRange.NOW != from && RtspRange.END != to) { return (long)((to - from) * 1000.0); } } return Player.TIME_UNKNOWN; } public String getContentType() { if (null == connection) throw new IllegalStateException("RTSP: Not connected"); return null; } //========================================================================= private static int allocPort() { int retVal = nextPort; nextPort += 2; if (nextPort >= MAX_PORT) { nextPort = MIN_PORT; } return retVal; } //========================================================================= /** * This method is called by RtspConnection when message is received */ protected void processIncomingMessage(byte[] bytes) { synchronized (msgWaitEvent) { RtspIncomingMessage msg; try { msg = new RtspIncomingMessage(bytes); } catch (Exception e) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, "Exception in RtspDS.processIncomingMessage(): " + e); } msg = null; } Integer cseq = msg.getCSeq(); // response may not have CSeq defined if status is not '200 OK'. if (null == cseq || cseq.intValue() == seqNum) { response = msg; msgWaitEvent.notifyAll(); seqNum++; } } } /** * blocks until response is received or timeout period expires */ private boolean sendRequest(RtspOutgoingRequest request) throws InterruptedException, IOException { boolean ok = false; synchronized (msgWaitEvent) { response = null; if (connection.sendData(request.getBytes())) { try { msgWaitEvent.wait(RESPONSE_TIMEOUT); ok = (null != response); } catch (InterruptedException e) { throw e; } } } if (ok && !response.getStatusCode().equals("200")) { throw new IOException("RTSP error " + response.getStatusCode() + ": '" + response.getStatusText() + "'"); } return ok; } }