美文网首页
spydroid-ipcamera源码分析(七):Rtsp和Rt

spydroid-ipcamera源码分析(七):Rtsp和Rt

作者: 管弦_ | 来源:发表于2017-05-31 17:12 被阅读0次

    Rtsp协议

    实时流协议(RTSP)是应用层协议,控制实时数据的传送 。RTSP提供了一个可扩展框架,使受控、按需传输实时数据(如音频与视频)成为可能。数据源包括现场数据与存储在剪辑中的数据。本协议旨在于控制多个数据发送会话,提供了一种选择传送途径(如UDP、组播UDP与TCP)的方法,并提供了一种选择基于RTP (RFC1889)的传送机制的方法。

    RTSP协议的服务端与客户端建立连接流程:

    (1)客户端发起RTSP OPTION请求,目的是得到服务器提供什么方法。RTSP提供的方法一般包括OPTIONS、DESCRIBE、SETUP、TEARDOWN、PLAY、PAUSE、SCALE、GET_PARAMETER。

    (2)服务器对RTSP OPTION回应,服务器实现什么方法就回应哪些方法。在此系统中,我们只对DESCRIBE、SETUP、TEARDOWN、PLAY、PAUSE方法做了实现。

    (3)客户端发起RTSP DESCRIBE请求,服务器收到的信息主要有媒体的名字,解码类型,视频分辨率等描述,目的是为了从服务器那里得到会话描述信息(SDP)。

    (4)服务器对RTSP DESCRIBE响应,发送必要的媒体参数,在传输H.264文件时,主要包括SPS/PPS、媒体名、传输协议等信息。

    (5)客户端发起RTSP SETUP请求,目的是请求会话建立并准备传输。请求信息主要包括传输协议和客户端端口号。

    (6)服务器对RTSP SETUP响应,发出相应服务器端的端口号和会话标识符。

    (7)客户端发出了RTSP PLAY的请求,目的是请求播放视频流。

    (8)服务器对RTSP PLAY响应,响应的消息包括会话标识符,RTP包的序列号,时间戳。此时服务器对H264视频流封装打包进行传输。

    (9)客户端发出RTSP TEARDOWN请求,目的是关闭连接,终止传输。

    (10)服务器关闭连接,停止传输。

    RtspServer

    RtspServer继承Android的Server,主要作为后台服务提供为传输Rtsp协议数据的服务端Socket,监听并处理Rtsp传输的连接逻辑。

    /** 
         * Starts (or restart if needed, if for example the configuration 
         * of the server has been modified) the RTSP server. 
         */
        public void start() {
            if (!mEnabled || mRestart) stop();
            if (mEnabled && mListenerThread == null) {
                try {
                    mListenerThread = new RequestListener();
                } catch (Exception e) {
                    mListenerThread = null;
                }
            }
            mRestart = false;
        }
    
        /** 
         * Stops the RTSP server but not the Android Service. 
         * To stop the Android Service you need to call {@link android.content.Context#stopService(Intent)}; 
         */
        public void stop() {
            if (mListenerThread != null) {
                try {
                    mListenerThread.kill();
                    for ( Session session : mSessions.keySet() ) {
                        if ( session != null ) {
                            if (session.isStreaming()) session.stop();
                        } 
                    }
                } catch (Exception e) {
                } finally {
                    mListenerThread = null;
                }
            }
        }
    

    start()方法启动一个线程阻塞监听Socket的连接,stop()方法则循环遍历停止和释放所有客户端连接(Rtsp协议支持多个客户端同时连接)。

        while (!Thread.interrupted()) {
            try {
                new WorkerThread(mServer.accept()).start();
            } catch (SocketException e) {
                break;
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                continue;   
            }
        }
    

    上面是RequestListener内部类中截取run()方法中的一段代码,可以看到这里使用了无限循环和线程阻塞的方式监听Socket的连接,只要有客户端连接就会启动一个WorkerThread线程来对应该客户端,同时又可以监听到多个客户端的连接。

        while (!Thread.interrupted()) {
    
            request = null;
            response = null;
    
            // Parse the request
            try {
                //读取和过滤客户端发来的数据包。
                request = Request.parseRequest(mInput);
            } catch (SocketException e) {
                // Client has left
                break;
            } catch (Exception e) {
                // We don't understand the request :/
                response = new Response();
                response.status = Response.STATUS_BAD_REQUEST;
            }
    
            // Do something accordingly like starting the streams, sending a session description
            if (request != null) {
                try {
                    //解析请求内容
                    response = processRequest(request);
                }
                catch (Exception e) {
                    // This alerts the main thread that something has gone wrong in this thread
                    postError(e, ERROR_START_FAILED);
                    Log.e(TAG,e.getMessage()!=null?e.getMessage():"Aerror occurred");
                    e.printStackTrace();
                    response = new Response(request);
                }
            }
    
            // We always send a response
            // The client will receive an "INTERNAL SERVER ERROR" if an exception has been thrown at some point
            try {
                //发送返回数据给客户端
                response.send(mOutput);
            } catch (IOException e) {
                Log.e(TAG,"Response was not sent properly");
                break;
            }
        }
    

    上面是WorkerThread内部类中截取run()方法中的一段代码,主要流程就是读取客户端发来的请求数据包,解析并处理数据包内容,发送返回数据给客户端。

        public Response processRequest(Request request) throws IllegalStateException, IOException {
                Response response = new Response(request);
    
                /* ********************************************************************************** */
                /* ********************************* Method DESCRIBE ******************************** */
                /* ********************************************************************************** */
                if (request.method.equalsIgnoreCase("DESCRIBE")) {
    
                    // Parse the requested URI and configure the session
                    mSession = handleRequest(request.uri, mClient);
                    mSessions.put(mSession, null);
                    mSession.syncConfigure();
                    
                    String requestContent = mSession.getSessionDescription();
                    String requestAttributes = 
                            "Content-Base: "+mClient.getLocalAddress().getHostAddress()+":"+mClient.getLocalPort()+"/\r\n" +
                                    "Content-Type: application/sdp\r\n";
    
                    response.attributes = requestAttributes;
                    response.content = requestContent;
    
                    // If no exception has been thrown, we reply with OK
                    response.status = Response.STATUS_OK;
    
                }
    
                /* ********************************************************************************** */
                /* ********************************* Method OPTIONS ********************************* */
                /* ********************************************************************************** */
                else if (request.method.equalsIgnoreCase("OPTIONS")) {
                    response.status = Response.STATUS_OK;
                    response.attributes = "Public: DESCRIBE,SETUP,TEARDOWN,PLAY,PAUSE\r\n";
                    response.status = Response.STATUS_OK;
                }
    
                /* ********************************************************************************** */
                /* ********************************** Method SETUP ********************************** */
                /* ********************************************************************************** */
                else if (request.method.equalsIgnoreCase("SETUP")) {
                    Pattern p; Matcher m;
                    int p2, p1, ssrc, trackId, src[];
                    String destination;
    
                    p = Pattern.compile("trackID=(\\w+)",Pattern.CASE_INSENSITIVE);
                    m = p.matcher(request.uri);
    
                    if (!m.find()) {
                        response.status = Response.STATUS_BAD_REQUEST;
                        return response;
                    } 
    
                    trackId = Integer.parseInt(m.group(1));
    
                    if (!mSession.trackExists(trackId)) {
                        response.status = Response.STATUS_NOT_FOUND;
                        return response;
                    }
    
                    p = Pattern.compile("client_port=(\\d+)-(\\d+)",Pattern.CASE_INSENSITIVE);
                    m = p.matcher(request.headers.get("transport"));
    
                    if (!m.find()) {
                        int[] ports = mSession.getTrack(trackId).getDestinationPorts();
                        p1 = ports[0];
                        p2 = ports[1];
                    }
                    else {
                        p1 = Integer.parseInt(m.group(1)); 
                        p2 = Integer.parseInt(m.group(2));
                    }
    
                    ssrc = mSession.getTrack(trackId).getSSRC();
                    src = mSession.getTrack(trackId).getLocalPorts();
                    destination = mSession.getDestination();
    
                    mSession.getTrack(trackId).setDestinationPorts(p1, p2);
                    
                    boolean streaming = isStreaming();
                    mSession.syncStart(trackId);
                    if (!streaming && isStreaming()) {
                        postMessage(MESSAGE_STREAMING_STARTED);
                    }
    
                    response.attributes = "Transport: RTP/AVP/UDP;"+(InetAddress.getByName(destination).isMulticastAddress()?"multicast":"unicast")+
                            ";destination="+mSession.getDestination()+
                            ";client_port="+p1+"-"+p2+
                            ";server_port="+src[0]+"-"+src[1]+
                            ";ssrc="+Integer.toHexString(ssrc)+
                            ";mode=play\r\n" +
                            "Session: "+ "1185d20035702ca" + "\r\n" +
                            "Cache-Control: no-cache\r\n";
                    response.status = Response.STATUS_OK;
    
                    // If no exception has been thrown, we reply with OK
                    response.status = Response.STATUS_OK;
    
                }
    
                /* ********************************************************************************** */
                /* ********************************** Method PLAY *********************************** */
                /* ********************************************************************************** */
                else if (request.method.equalsIgnoreCase("PLAY")) {
                    String requestAttributes = "RTP-Info: ";
                    if (mSession.trackExists(0)) requestAttributes += "url=rtsp://"+mClient.getLocalAddress().getHostAddress()+":"+mClient.getLocalPort()+"/trackID="+0+";seq=0,";
                    if (mSession.trackExists(1)) requestAttributes += "url=rtsp://"+mClient.getLocalAddress().getHostAddress()+":"+mClient.getLocalPort()+"/trackID="+1+";seq=0,";
                    requestAttributes = requestAttributes.substring(0, requestAttributes.length()-1) + "\r\nSession: 1185d20035702ca\r\n";
    
                    response.attributes = requestAttributes;
    
                    // If no exception has been thrown, we reply with OK
                    response.status = Response.STATUS_OK;
    
                }
    
                /* ********************************************************************************** */
                /* ********************************** Method PAUSE ********************************** */
                /* ********************************************************************************** */
                else if (request.method.equalsIgnoreCase("PAUSE")) {
                    response.status = Response.STATUS_OK;
                }
    
                /* ********************************************************************************** */
                /* ********************************* Method TEARDOWN ******************************** */
                /* ********************************************************************************** */
                else if (request.method.equalsIgnoreCase("TEARDOWN")) {
                    response.status = Response.STATUS_OK;
                }
    
                /* ********************************************************************************** */
                /* ********************************* Unknown method ? ******************************* */
                /* ********************************************************************************** */
                else {
                    Log.e(TAG,"Command unknown: "+request);
                    response.status = Response.STATUS_BAD_REQUEST;
                }
    
                return response;
    
            }
    

    processRequest(Request request)方法就是对Rtsp协议请求做处理,spydroid-ipcamera项目只对DESCRIBE、OPTIONS、SETUP、PLAY、PAUSE、TEARDOWN这几个请求做回应。在回应客户端的同时,在处理DESCRIBE请求中,会建立和配置一个Session对象来对应这个客户端。而在处理SETUP请求时,会对Session对象进行配置并启动Session的内部运行流程(流媒体的采集、编码、传输)。

    这篇文章我们大致了解了RTSP协议和它在spydroid-ipcamera项目中的运用,包括Socket连接的创建流程和Rtsp请求的具体处理。

    至此,spydroid-ipcamera源码分析系列文章也完结了,水平有限,难免会有错误的地方,欢迎指出和评论。这一系列文章参考了网上许多资料和博客,在此不一一列举,特此感谢!

    相关文章

      网友评论

          本文标题:spydroid-ipcamera源码分析(七):Rtsp和Rt

          本文链接:https://www.haomeiwen.com/subject/pxejfxtx.html