/** * */ package video.serverProxy; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.mina.core.session.IoSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import video.lib.RtcpPacket; import video.lib.RtpPacket; import video.lib.Statistic; import video.lib.TrackStatistic; import video.lib.UnsignedInt; import video.transport.TransportService; /** * A Track is a part of a RTSP session. A typical RTSP session for a video * stream transmission is composed of 2 tracks: a track for video data and * another track for audio data. * <p> * These two stream are independent and usually are activated by the same * <code>PLAY</code> and <code>TEARDOWN</code> requests. * * @author Matteo Merli */ public class ServerTrack { private static final Logger LOGGER = LoggerFactory.getLogger(ServerTrack.class); private static TransportService service; /** Maps a server SSRC id to a Track */ private static Map<UnsignedInt, ServerTrack> serverSsrcMap = new ConcurrentHashMap<UnsignedInt, ServerTrack>(); /** Maps a proxy SSRC id to a Track */ private static Map<UnsignedInt, ServerTrack> proxySsrcMap = new ConcurrentHashMap<UnsignedInt, ServerTrack>(); /** Maps a server address to a Track */ private static Map<InetSocketAddress, ServerTrack> serverAddressMap = new ConcurrentHashMap<InetSocketAddress, ServerTrack>(); /** Keeps track of the SSRC IDs used by the proxy, to avoid collisions. */ private static Set<UnsignedInt> proxySsrcSet = Collections.synchronizedSet(new HashSet<UnsignedInt>()); private Statistic trackStatistic; /** * Control Url of the track. This is the url handle given by the server to * control different tracks in a RTSP session. */ private URI uri; /** SSRC id given by the server */ private UnsignedInt serverSsrc = new UnsignedInt(0); /** SSRC id selected by the proxy */ private UnsignedInt proxySsrc = new UnsignedInt(0); /** * Cached references to IoSession objects used to send packets to server and * client. */ private IoSession rtpServerSession = null; private IoSession rtcpServerSession = null; private InetAddress serverAddress; private int serverRtpPort; private int serverRtcpPort; /** * Construct a new Track. * Proxy SSRC is automatically assigned. * * @param url * the control name for this track. */ public ServerTrack(TransportService transportService, URI uri) { service = transportService; this.uri = uri; setProxySsrc(newSsrc()); proxySsrcMap.put(this.proxySsrc, this); trackStatistic = new TrackStatistic("[server]" + uri.toString().replaceAll("\\/", "-")); try { trackStatistic.start(); } catch (IOException e) { e.printStackTrace(); } } /** * Get the track by looking at server socket address. * <p> * Used as a workaround for streaming servers which do not hand out a ssrc in * the setup handshake. * * @return a Track instance if a matching pair is found or null */ public static ServerTrack getByServerSocketAddress(InetSocketAddress serverSocketAddress) { return serverAddressMap.get(serverSocketAddress); } /** * Get the track by looking at server SSRC id. * * @return a Track instance if a matching SSRC is found or null */ public static ServerTrack getByServerSsrc(UnsignedInt serverSsrc) { return serverSsrcMap.get(serverSsrc); } public static ServerTrack getByProxySsrc(UnsignedInt proxySsrc) { return proxySsrcMap.get(proxySsrc); } // /// Member methods /** * @return the SSRC id used byt the proxy */ public UnsignedInt getProxySsrc() { return proxySsrc; } /** * Sets the proxy SSRC id. * * @param proxySsrc */ public void setProxySsrc(String proxySsrc) { try { this.proxySsrc = UnsignedInt.fromString(proxySsrc, 16); proxySsrcSet.add(this.proxySsrc); } catch (NumberFormatException nfe) { LOGGER.debug("Cannot convert " + proxySsrc + " to integer."); throw nfe; } } /** * @return the server SSRC id */ public UnsignedInt getServerSsrc() { return serverSsrc; } /** * Sets the server SSRC id. * * @param serverSsrc */ public void setServerSsrc(String serverSsrc) { this.serverSsrc = UnsignedInt.fromString(serverSsrc, 16); serverSsrcMap.put(this.serverSsrc, this); } /** * Sets the server SSRC id. * * @param serverSsrc */ public void setServerSsrc(UnsignedInt serverSsrc) { this.serverSsrc = serverSsrc; serverSsrcMap.put(this.serverSsrc, this); LOGGER.debug("ServerTrack.setServerSsrc done: " + serverSsrc.toHexString()); } public URI getUri() { return uri; } public void setUri(URI uri) { this.uri = uri; } public void setRtcpServerSession(IoSession rtcpServerSession) { this.rtcpServerSession = rtcpServerSession; } public void setRtpServerSession(IoSession rtpServerSession) { this.rtpServerSession = rtpServerSession; } /** * Forwards a RTP packet to server. The packet will be set to the address * indicated by the server at RTP (even) port. * * @param packet * a RTP packet */ public void forwardRtpToServer(RtpPacket packet) { // modify the SSRC for the server packet.setSsrc(proxySsrc); if (rtpServerSession == null) rtpServerSession = RtpServerService.newRtpSession(new InetSocketAddress(serverAddress, serverRtpPort)); rtpServerSession.write(packet.toIoBuffer()); } /** * Forwards a RTCP packet to server. The packet will be set to the address * indicated by the server at RTCP (odd) port. * * @param packet * a RTCP packet */ public void forwardRtcpToServer(RtcpPacket packet) { // modify the SSRC for the server packet.setSsrc(proxySsrc); if (rtcpServerSession == null) rtcpServerSession = RtpServerService.newRtcpSession(new InetSocketAddress(serverAddress, serverRtcpPort)); rtcpServerSession.write(packet.toIoBuffer()); } /** * Forwards a RTP packet to client. The packet will be set to the address * indicated by the client at RTP (even) port. * <p> * TODO: This will be changed to support multiple clients connected to the * same (live) track. * * @param packet * a RTP packet */ public void forwardRtpToClient(RtpPacket packet) { // modify the SSRC for the client packet.setSsrc(proxySsrc); try { service.getRTPTransportChannel().send(packet.toIoBuffer().array()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } trackStatistic.doStatistic(packet); } /** * Forwards a RTCP packet to client. The packet will be set to the address * indicated by the client at RTCP (odd) port. * <p> * TODO: This will be changed to support multiple clients connected to the * same (live) track. * * @param packet * a RTCP packet */ public void forwardRtcpToClient(RtcpPacket packet) { // modify the SSRC for the client packet.setSsrc(proxySsrc); try { service.getRTCPTransportChannel().send(packet.toIoBuffer().array()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Set the address of the server associated with this track. * * @param serverHost * The serverHost to set. * @param rtpPort * the port number used for RTP packets * @param rtcpPort * the port number used for RTCP packets */ public void setServerSocketAddress(InetAddress serverAddress, int rtpPort, int rtcpPort) { this.serverAddress = serverAddress; this.serverRtpPort = rtpPort; this.serverRtcpPort = rtcpPort; serverAddressMap.put(new InetSocketAddress(serverAddress, rtpPort), this); serverAddressMap.put(new InetSocketAddress(serverAddress, rtcpPort), this); } public synchronized void close() { if (serverSsrc != null) { serverSsrcMap.remove(serverSsrc); } if (proxySsrc != null) { proxySsrcMap.remove(proxySsrc); } serverAddressMap.remove(new InetSocketAddress(serverAddress, serverRtpPort)); serverAddressMap.remove(new InetSocketAddress(serverAddress, serverRtcpPort)); if (proxySsrc != null) { proxySsrcSet.remove(proxySsrc); } LOGGER.debug("Closed track " + uri); } public String toString() { return "Track(url=\"" + uri + "\""; } // //////////////// /** Used in SSRC id generation */ private static Random random = new Random(); /** * Creates a new SSRC id that is unique in the proxy. * * @return the session ID */ private static String newSsrc() { long id; while (true) { id = random.nextLong() & 0xFFFFFFFFL; if (!proxySsrcSet.contains(id)) { // Ok, the id is unique String ids = Long.toString(id, 16); return ids; } // try with another id } } }