spydroid-ipcamera源碼分析(六):Rtp和Rtcp

之前幾篇文章我們了解了多媒體數據流的采集和編碼,這一篇開始我們來了解數據傳輸的流程。

在數據流編碼完成以后,程序會通過打包器將編碼完成的數據打包再傳輸出去,代碼如下:

    // The packetizer encapsulates the bit stream in an RTP stream and send it over the network
        mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
        mPacketizer.setInputStream(new MediaCodecInputStream(mMediaCodec));
        mPacketizer.start();

簡單來說,就是設置傳輸目的地地址、Rtp端口和Rtcp端口,設置數據源的InputStream(MediaCodecInputStream繼承InputStream,從MediaCodec對象中獲取完成編碼的數據流),啟動start()方法執行數據打包操作。下面我們來看一下打包器的源碼:

AbstractPacketizer類

AbstractPacketizer就是打包器的基類,封裝了對數據流打包操作的基本操作和一些公共參數變量。

    public AbstractPacketizer() {
        int ssrc = new Random().nextInt();
        ts = new Random().nextInt();
        socket = new RtpSocket();
        socket.setSSRC(ssrc);
    }
    
    ...
    
    /** Starts the packetizer. */
    public abstract void start();

    /** Stops the packetizer. */
    public abstract void stop();
    
    /** Updates data for RTCP SR and sends the packet. */
    protected void send(int length) throws IOException {
        socket.commitBuffer(length);
    }

上面截取AbstractPacketizer類的部分代碼。AbstractPacketizer的構造函數中直接創建了一個RtpSocket對象,就是將打包好的數據用Rtp協議傳輸出去的執行者。ssrc:用于標識同步信源。ts:時間戳。AbstractPacketizer還提供了兩個抽象方法start()和stop(),用于控制流的打包操作。在子類(根據數據格式生成不同的子類)實現的start()方法中,會創建一個線程來執行數據打包操作,數據打包的內部原理涉及到音視頻的相關格式和相關傳輸協議,這里不再深入。send(int length)方法就是使用RtpSocket對象更新和發送數據包。

RTP協議和RTCP協議

  • RTP全名是Real-time Transport Protocol(實時傳輸協議),RTCP(Real-time Transport Control Protocol,即實時傳輸控制協議)。
  • RTP用來為IP網上的語音、圖像、傳真等多種需要實時傳輸的多媒體數據提供端到端的實時傳輸服務。
  • RTP為Internet上端到端的實時傳輸提供時間信息和流同步,但并不保證服務質量,服務質量由RTCP來提供。
  • RTCP的主要功能是:服務質量的監視與反饋、媒體間的同步,以及多播組中成員的標識。在RTP會話期 間,各參與者周期性地傳送RTCP包。RTCP包中含有已發送的數據包的數量、丟失的數據包的數量等統計資料,因此,各參與者可以利用這些信息動態地改變傳輸速率,甚至改變有效載荷類型。RTP和RTCP配合使用,它們能以有效的反饋和最小的開銷使傳輸效率最佳化,因而特別適合傳送網上的實時數據。
  • Rtp和Rtcp分別使用兩個端口執行通信。RTP數據發向偶數的UDP端口,而對應的控制信號RTCP數據發向相鄰的奇數UDP端口(偶數的UDP端口+1),這樣就構成一個UDP端口對。
  • 參考資料:RTP協議分析 應該稍微了解一下RTP的封裝和RTCP的封裝。

RtpSocket類

RtpSocket類是使用RTP協議的Socket的封裝實現。

    /**
     * This RTP socket implements a buffering mechanism relying on a FIFO of buffers and a Thread.
     * @throws IOException
     */
    public RtpSocket() {
        
        mCacheSize = 00;
        mBufferCount = 300; // TODO: reajust that when the FIFO is full 
        mBuffers = new byte[mBufferCount][];
        mPackets = new DatagramPacket[mBufferCount];
        mReport = new SenderReport();
        mAverageBitrate = new AverageBitrate();
        
        ...

        try {
        mSocket = new MulticastSocket();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        
    }

在構造函數中初始化各個參數變量和發送RTCP報文的實例(SenderReport),mSocket對象(這里使用MulticastSocket,實現將數據報以廣播的方式發送到多個client)。

    /** Sends the RTP packet over the network. */
    public void commitBuffer(int length) throws IOException {
        updateSequence();
        mPackets[mBufferIn].setLength(length);

        mAverageBitrate.push(length);

        if (++mBufferIn>=mBufferCount) mBufferIn = 0;
        mBufferCommitted.release();

        if (mThread == null) {
            mThread = new Thread(this);
            mThread.start();
        }       
        
    }

發送RTP協議包數據。這里啟動了一個線程來執行發送動作,以一定的速率依次發送數據包。

/** The Thread sends the packets in the FIFO one by one at a constant rate. */
    @Override
    public void run() {
        Statistics stats = new Statistics(50,3000);
        try {
            // Caches mCacheSize milliseconds of the stream in the FIFO.
            Thread.sleep(mCacheSize);
            long delta = 0;
            while (mBufferCommitted.tryAcquire(4,TimeUnit.SECONDS)) {
                if (mOldTimestamp != 0) {
                    // We use our knowledge of the clock rate of the stream and the difference between two timestamps to
                    // compute the time lapse that the packet represents.
                    if ((mTimestamps[mBufferOut]-mOldTimestamp)>0) {
                        stats.push(mTimestamps[mBufferOut]-mOldTimestamp);
                        long d = stats.average()/1000000;
                        //Log.d(TAG,"delay: "+d+" d: "+(mTimestamps[mBufferOut]-mOldTimestamp)/1000000);
                        // We ensure that packets are sent at a constant and suitable rate no matter how the RtpSocket is used.
                        if (mCacheSize>0) Thread.sleep(d);
                    } else if ((mTimestamps[mBufferOut]-mOldTimestamp)<0) {
                        Log.e(TAG, "TS: "+mTimestamps[mBufferOut]+" OLD: "+mOldTimestamp);
                    }
                    delta += mTimestamps[mBufferOut]-mOldTimestamp;
                    if (delta>500000000 || delta<0) {
                        //Log.d(TAG,"permits: "+mBufferCommitted.availablePermits());
                        delta = 0;
                    }
                }
                mReport.update(mPackets[mBufferOut].getLength(), System.nanoTime(),(mTimestamps[mBufferOut]/100L)*(mClock/1000L)/10000L);
                mOldTimestamp = mTimestamps[mBufferOut];
                if (mCount++>30) mSocket.send(mPackets[mBufferOut]);
                if (++mBufferOut>=mBufferCount) mBufferOut = 0;
                mBufferRequested.release();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        mThread = null;
        resetFifo();
    }

簡單流程就是:配置傳輸速率,使用while來循環執行,如果一個執行過程不超過4秒,則一直循環下去。在循環執行的動作包括:計算速率的平均值(按照這個平均值的速率來執行傳輸,確保速率恒定),更新Rtcp報文,Socket執行發送動作(Send)來給客戶端(接收端)傳輸數據包。

SenderReport類

SenderReport類是執行Rtcp協議的實現類。

    public SenderReport() {

        ...
    
        try {
            usock = new MulticastSocket();
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        upack = new DatagramPacket(buffer, 1);

        // By default we sent one report every 5 secconde
        interval = 3000;
        
    }

正如上面介紹時所說,Rtp和Rtcp分別使用兩個端口執行通信,所以SenderReport類的構造函數也需要初始化一個Socket對象用于發送Rtcp報文。

    /** 
     * Updates the number of packets sent, and the total amount of data sent.
     * @param length The length of the packet 
     * @throws IOException 
     **/
    public void update(int length, long ntpts, long rtpts) throws IOException {
        packetCount += 1;
        octetCount += length;
        setLong(packetCount, 20, 24);
        setLong(octetCount, 24, 28);

        now = SystemClock.elapsedRealtime();
        delta += oldnow != 0 ? now-oldnow : 0;
        oldnow = now;
        if (interval>0) {
            if (delta>=interval) {
                // We send a Sender Report
                send(ntpts,rtpts);
                delta = 0;
            }
        }
        
    }

在Rtp的Socket不斷發送數據包的同時,SenderReport也不斷在更新和發送Rtcp的報文,
update()方法就是在不斷更新發送的數據包數,以及發送的數據總量。

    /** Sends the RTCP packet over the network. */
    private void send(long ntpts, long rtpts) throws IOException {
        long hb = ntpts/1000000000;
        long lb = ( ( ntpts - hb*1000000000 ) * 4294967296L )/1000000000;
        setLong(hb, 8, 12);
        setLong(lb, 12, 16);
        setLong(rtpts, 16, 20);
        upack.setLength(28);
        usock.send(upack);      
    }

send()方法就是用于在update()更新Rtcp報文后,將新的Rtcp報文通過Socket傳給接收端。

到這里我們了解了打包器Packetizer的打包流程和Rtp&Rtcp協議的傳輸流程,下一篇將了解Rtsp協議和它在項目中的運用流程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容