之前幾篇文章我們了解了多媒體數據流的采集和編碼,這一篇開始我們來了解數據傳輸的流程。
在數據流編碼完成以后,程序會通過打包器將編碼完成的數據打包再傳輸出去,代碼如下:
// 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協議和它在項目中的運用流程。