/** * */ package video.clientProxy; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IoSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import video.lib.Configuration; import video.lib.Constants; import video.lib.NumberConvertor; import video.lib.RtspMessage; import video.lib.RtspMethod; import video.lib.RtspRequest; import video.lib.RtspResponse; import video.lib.RtspStatusCode; import video.lib.RtspTransportHeader; import video.lib.RtspTransportHeader.RtspTransport; import video.transport.TransportService; /** * @author yuezhu * */ public class RtspClientHandler { private static final Logger LOGGER = LoggerFactory.getLogger(RtspClientHandler.class); protected static final AttributeKey TOKEN = new AttributeKey(RtspClientHandler.class, "token"); private final IoSession session; private Long clientSessionId = -1L; private String rtspSessionId = null; private static TransportService service; private final Map<String, RtspMethod> cseqMap = new ConcurrentHashMap<String, RtspMethod>(); private final Map<URI, ClientTrack> trackList = new ConcurrentHashMap<URI, ClientTrack>(); private static Map<String, RtspClientHandler> rtspSessionIdMap = new ConcurrentHashMap<String, RtspClientHandler>(); private static Map<Long, RtspClientHandler> clientSessionIdMap = new ConcurrentHashMap<Long, RtspClientHandler>(); public static RtspClientHandler getByClientSessionId(long id) { LOGGER.debug("Get by client session ID=" + id); return clientSessionIdMap.get(Long.valueOf(id)); } private byte[] insertClientSessionId(byte[] in) { byte[] byteArray = new byte[in.length + (Long.SIZE / Byte.SIZE)]; System.arraycopy(NumberConvertor.toByteArray(session.getId()), 0, byteArray, 0, Long.SIZE / Byte.SIZE); System.arraycopy(in, 0, byteArray, Long.SIZE / Byte.SIZE, in.length); LOGGER.debug("Insert client session ID=" + session.getId()); return byteArray; } public static byte[] getMessageBytes(byte[] in) { int length = in.length - (Long.SIZE / Byte.SIZE); byte[] byteArray = new byte[length]; System.arraycopy(in, Long.SIZE / Byte.SIZE, byteArray, 0, length); return byteArray; } public static long getClientSessionId(byte[] in) { byte[] longBytes = new byte[Long.SIZE / Byte.SIZE]; System.arraycopy(in, 0, longBytes, 0, Long.SIZE / Byte.SIZE); return NumberConvertor.toLongValue(longBytes); } /** * Get handler by RTSP session ID * @param id * @return */ public static RtspClientHandler getByRtspSessionId(String id) { return rtspSessionIdMap.get(id); } public static void putRtspSessionId(String id, RtspClientHandler handler) { rtspSessionIdMap.put(id, handler); } public RtspClientHandler(IoSession session, TransportService transportService) { this.session = session; service = transportService; // Save session ID for de-multiplexing. clientSessionId = Long.valueOf(session.getId()); clientSessionIdMap.put(clientSessionId, this); LOGGER.debug("Client session ID put: " + session.getId()); } public void onMessageReceivedFromRemote(RtspMessage message) { LOGGER.debug("RtspMessage received from remote:\n" + "--------------------------------------------------------------------------------\n" + message.toString() + "--------------------------------------------------------------------------------"); RtspMethod method = null; switch (message.getType()) { case REQUEST: // It's a request from the server. We save its request method. RtspRequest request = (RtspRequest) message; method = request.getMethod(); saveRequestMethod(request); // Then dispatch the response and pass it to remote. switch (method) { case OPTIONS: LOGGER.debug("OPTIONS request."); break; case GET_PARAMETER: LOGGER.debug("GET_PARAMETER request."); break; case SET_PARAMETER: LOGGER.debug("SET_PARAMETER request."); break; default: LOGGER.warn("Server shouldn't issue such a request of method " + method); break; } break; // End of REQUEST case. case RESPONSE: // It's a response from the server. We find the method of its // corresponding request. RtspResponse response = (RtspResponse) message; method = retrieveRequestMethod(response); if (method == null) { LOGGER.error("Cannot retrieve corresponding RTSP method with CSeq " + response.getField("CSeq")); return; } // Then dispatch the response and pass it to remote. switch (method) { case SETUP: LOGGER.debug("SETUP response."); processSetupResponse(response); break; case OPTIONS: LOGGER.debug("OPTIONS response."); break; case DESCRIBE: LOGGER.debug("DESCRIBE response."); break; case PLAY: LOGGER.debug("PLAY response."); break; case PAUSE: LOGGER.debug("PAUSE response."); break; case TEARDOWN: LOGGER.debug("TEARDOWN response."); processTeardownResponse(response); break; case GET_PARAMETER: LOGGER.debug("GET_PARAMETER response."); break; case SET_PARAMETER: LOGGER.debug("SET_PARAMETER response."); break; case UNKNOWN: LOGGER.debug("UNKNOWN response."); break; default: LOGGER.warn("Error since its corresponding request method " + method + " is not supported."); break; } break; // End of RESPONSE case. } sendRtspMessageToClient(message); } public void onMessageReceivedFromClient(RtspMessage message) { LOGGER.debug("RtspMessage received from the client:\n" + "--------------------------------------------------------------------------------\n" + message.toString() + "--------------------------------------------------------------------------------"); RtspMethod method = null; switch (message.getType()) { case REQUEST: // It's a request from the client. We save its request method. RtspRequest request = (RtspRequest) message; method = request.getMethod(); saveRequestMethod(request); // Then dispatch the response and pass it to the client. switch (method) { case SETUP: LOGGER.debug("SETUP request."); processSetupRequest(request); break; case DESCRIBE: LOGGER.debug("DESCRIBE request."); break; case GET_PARAMETER: LOGGER.debug("GET_PARAMETER request."); break; case SET_PARAMETER: LOGGER.debug("SET_PARAMETER request."); break; case OPTIONS: LOGGER.debug("OPTIONS request."); break; case PAUSE: LOGGER.debug("PAUSE request."); break; case PLAY: LOGGER.debug("PLAY request."); break; case TEARDOWN: LOGGER.debug("TEARDOWN request."); break; default: LOGGER.warn("Request method " + method + " is not supported."); return; } break; // End of REQUEST case. case RESPONSE: // It's a response from the server. We find the method of its // corresponding request. RtspResponse response = (RtspResponse) message; method = retrieveRequestMethod(response); // Then dispatch the response and pass it to remote. switch (method) { case OPTIONS: LOGGER.debug("OPTIONS request."); break; case GET_PARAMETER: LOGGER.debug("GET_PARAMETER response."); break; case SET_PARAMETER: LOGGER.debug("SET_PARAMETER response."); break; case UNKNOWN: LOGGER.debug("UNKNOWN response."); break; default: LOGGER.warn("Server shouldn't issue such a request of method " + method); return; } break; // End of RESPONSE case. } sendRtspMessageToRemote(message); } /** * Save the request URI specified in the SETUP request into this RTSP session * @param request */ private void processSetupRequest(RtspRequest request) { LOGGER.debug("Got SETUP request."); session.setAttribute("requestUri", request.getRequestUri()); } /** * Check if the transport type is supported by this proxy. If not supported, then just pass it to player. * Otherwise, initialize track for this SETUP and modify the server ports and address. * @param response */ private void processSetupResponse(RtspResponse response) { LOGGER.debug("Got SETUP response."); if (response.getStatusCode() == RtspStatusCode.NotImplemented) { // If the server side proxy returns a not implemented response, then just pass it the player. return; } RtspTransportHeader transportHeader = new RtspTransportHeader(response.getField("Transport")); // Get the transport header. RtspTransport rtspTransport = transportHeader.get(0); // Create a new Track object. // SSRC must be available because the server side proxy will generate a proxy SSRC. ClientTrack serverTrack = addClientTrack((URI) session.getAttribute("requestUri"), rtspTransport.getSsrc()); InetAddress clientAddress = null; try { clientAddress = InetAddress.getByName(((InetSocketAddress) session.getRemoteAddress()).getHostName()); } catch (UnknownHostException e) { LOGGER.warn("Unknown host"); } // ///////////////////// // Set response transport header // ///////////////////// if (rtspTransport.getLowerTransport() == RtspTransportHeader.LowerTransport.UDP) { serverTrack.setClientSocketAddress(clientAddress, rtspTransport.getClientPort()[0], rtspTransport.getClientPort()[1]); // Get client source ports. // Client side proxy will send RTP/RTCP packets on these ports. int rtpServerPort = Configuration.getInt("client.rtp.port", Constants.CLIENT_RTP_PORT); int rtcpServerPort = Configuration.getInt("client.rtcp.port", Constants.CLIENT_RTCP_PORT); rtspTransport.setServerPort(new int[]{rtpServerPort, rtcpServerPort}); // Client side proxy will send RTP/RTCP packets on this address. rtspTransport.setSource(((InetSocketAddress) session.getLocalAddress()).getAddress().getHostAddress()); } else if (rtspTransport.getLowerTransport() == RtspTransportHeader.LowerTransport.TCP) { LOGGER.error("Transport is TCP based, which is not supported currently."); } else { LOGGER.error("Transport is unspecified."); } transportHeader.set(0, rtspTransport); //LOGGER.debug("Transport header to be sent: " + transportHeader.toString()); response.setField("Transport", transportHeader.toString()); rtspSessionId = response.getField("Session"); if (rtspSessionId != null && !rtspSessionIdMap.containsKey(rtspSessionId)) { LOGGER.debug("Got RTSP session ID, put it into rtspSessionIDMap"); rtspSessionIdMap.put(rtspSessionId, this); } } /** * Sometimes this method cannot be invoked because clients close the connection immediately * right after sending TEARDOWN request. This causes sessionClosed() to be invoked. * @param response */ private void processTeardownResponse(RtspResponse response) { if (response.getStatusCode() == RtspStatusCode.OK) { close(); } } private synchronized ClientTrack addClientTrack(URI uri, String proxySsrc) { LOGGER.debug("Client track added for SSRC: " + proxySsrc); ClientTrack clientTrack = new ClientTrack(service, uri); if (proxySsrc != null) { LOGGER.debug("proxySsrc is available: " + proxySsrc); clientTrack.setProxySsrc(proxySsrc); } trackList.put(uri, clientTrack); return clientTrack; } // //////////////////////////// private void saveRequestMethod(RtspRequest request) { cseqMap.put(request.getField("CSeq"), request.getMethod()); LOGGER.debug("Save CSeq: " + request.getField("CSeq")); } private synchronized RtspMethod retrieveRequestMethod(RtspResponse response) { RtspMethod rtn = null; String cseq = response.getField("CSeq"); if (cseq != null) { if (cseqMap.containsKey(cseq)) { rtn = cseqMap.get(cseq); cseqMap.remove(cseq); return rtn; } else { LOGGER.error("CSeq is illegal."); return null; } } else { LOGGER.debug("CSeq is not specified."); return RtspMethod.UNKNOWN; } } // //////////////////////////// private void sendRtspMessageToClient(RtspMessage message) { if (message == null) { LOGGER.error("RtspMessage is null."); return; } switch (message.getType()) { case REQUEST: RtspRequest request = (RtspRequest) message; session.write(request); break; case RESPONSE: RtspResponse response = (RtspResponse) message; session.write(response); break; } LOGGER.debug("RTSP message sent to the client:\n" + "--------------------------------------------------------------------------------\n" + message.toString() + "--------------------------------------------------------------------------------"); } private void sendRtspMessageToRemote(RtspMessage message) { if (message == null) { LOGGER.error("RtspMessage is null."); return; } byte[] byteArray; switch (message.getType()) { case REQUEST: RtspRequest request = (RtspRequest) message; try { byteArray = insertClientSessionId(request.toString().getBytes()); service.getRTSPTransportChannel().send(byteArray); } catch (IOException e) { LOGGER.error("Failed to send RTSP request via transport service: " + e.toString()); } break; case RESPONSE: RtspResponse response = (RtspResponse) message; try { byteArray = insertClientSessionId(response.toString().getBytes()); service.getRTSPTransportChannel().send(byteArray); } catch (IOException e) { LOGGER.error("Failed to send RTSP response via transport service: " + e.toString()); } break; } } /** * Synchronized close. */ public synchronized void close() { if ((session != null) && session.isConnected()) { session.close(true); // session.getService().dispose(); // CloseFuture future = session.close(true); // future.awaitUninterruptibly(); } // close all associated tracks for (Map.Entry<URI, ClientTrack> entry : trackList.entrySet()) { entry.getValue().close(); } if (clientSessionIdMap.containsKey(session.getId())) { clientSessionIdMap.remove(session.getId()); } if (rtspSessionId != null && rtspSessionIdMap.containsKey(rtspSessionId)) { rtspSessionIdMap.remove(rtspSessionId); } } }