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阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容