live555學習筆記13-RTPInterface詳解

十三:RTPInterface詳解


好幾天沒寫blog了。看源碼真累啊,還要把理解的寫到紙上,還要組織混亂的思想,令人頭痛,所以這需要激情。不過,今天激情又來了。


大家應該已理解了GroupSocket這個類。理論上講那些需要操作udp socket 的類應保存GroupSocket的實例。但事實並不是這樣,可以看一下RTPSink,RTPSource,RTCPInstance等,它們都沒有保存GroupSocket型的變量。那它們通過哪個類進行socket操作呢?是RTPInterface!!
這些類接收的GroupSocket指針最後都傳給了 RTPInterface 。爲什麼用RTPInterface而不直接用GroupSocket呢?這裏面有個故事...扯遠了。


要解答這個問題,讓我們先提出問題吧。
首先請問,Live555即支持rtp over udp,又支持rtp over tcp。那麼在rtp over tcp情況下,用 GroupSocket 怎麼實現呢?GroupSocket可是僅僅代表UDP啊!
那麼RTPInterface既然用於網絡讀寫,它就應該既支持tcp收發,也支持udp收發。而且它還要像GroupSocket那樣支持一對多。因爲服務端是一對多個客戶端哦。我們看一下RTPInterface的成員:
Groupsock* fGS;
tcpStreamRecord* fTCPStreams; // optional, for RTP-over-TCP streaming/receiving
嘿嘿,這兩個緊靠着,說明它們關係不一般啊(難道他們有一腿?)。fGS--代表了一個udp socket和它對應的多個目的端,fTCPStreams--代表了多個TCP socket,當然這些socket都是從一個socket accept()出來的客戶端socket(tcpStreamRecord是一個鏈表哦)。
看到這個架式,我想大家都要得出結論了:RTPInterface還真是男女通吃啊!不論你客戶端與我建立的是tcp連接,還是udp連接,我RTPInterface一律能接收你們的數據,並向你們發出數據!

證據一:向所有客戶端發出數據:

Boolean RTPInterface::sendPacket(unsigned char* packet, unsigned packetSize)
{
	Boolean success = True; // we'll return False instead if any of the sends fail

	// Normal case: Send as a UDP packet:
	if (!fGS->output(envir(), fGS->ttl(), packet, packetSize))
		success = False;

	// Also, send over each of our TCP sockets:
	for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;
			streams = streams->fNext) {
		if (!sendRTPOverTCP(packet, packetSize, streams->fStreamSocketNum,
				streams->fStreamChannelId)) {
			success = False;
		}
	}

	return success;
}


很明顯啊,先發送udp數據,一對多的問題在GroupSocket中解決。再發送tcp數據,一對多的問題本地解決。
證據二:從所有客戶端讀取數據:
我現在找不到直接的證據,所以我就憶想一下吧:當udp端口或tcp端口收到數據時,分析後,是哪個客戶端的數據就發給對應這個客戶端的RTPSink或RTCPInstance。
好像已經把最開始的問題解答完了。下面讓我們來分析一下RTPInterface吧。

void RTPInterface::setStreamSocket(int sockNum, unsigned char streamChannelId)
{
	fGS->removeAllDestinations();
	addStreamSocket(sockNum, streamChannelId);
}

void RTPInterface::addStreamSocket(int sockNum, unsigned char streamChannelId)
{
	if (sockNum < 0)
		return;

	for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;
			streams = streams->fNext) {
		if (streams->fStreamSocketNum == sockNum
				&& streams->fStreamChannelId == streamChannelId) {
			return; // we already have it
		}
	}

	fTCPStreams = new tcpStreamRecord(sockNum, streamChannelId, fTCPStreams);
}


setStreamSocket()沒必要說了吧,看一下addStreamSocke()。從字面意思應能瞭解,添加一個流式Socket,也就是添加tcp
socket了。循環中查找是否已經存在了,最後如果不存在,就創建之,在tcpStreamRecord的構造函數中己經把自己加入了鏈表。對於參數,sockNum很易理解,就是socket()返回的那個SOCKET型
數據唄,streamChannelId是什麼呢?我們不防再猜測一下(很奇怪,我每次都能猜對,嘿嘿...):rtp over tcp時,這個tcp連接是直接利用了RTSP所用的那個tcp連接,如果同時有很多rtp
session,再加上rtsp session,大家都用這一個socket通信,怎麼區分你的還是我的?我想這個channel
id就是用於解決這個問題。給每個session分配一個唯一的id,在發送自己的包時爲包再加上個頭部,頭部中需要有session的標記--也就是這個channel id,包的長度等等字段。這樣大家就可以穿一條褲子了,術語叫多路複用,但要注意只有tcp才進行多路複用,udp是不用的,因爲udp是一個session對應一個socket(加上RTCP是兩個)。
想像一下,服務端要從這個tcp socket讀寫數據,必須把一個handler加入TaskScheduler中,這個handler在可讀數據時進行讀,在可寫數據時進行寫。在讀數據時,對讀出的數據進行分析,取得數據包的長度,以及其channel id,跟據channel id找到相應的處handler和對象,交給它們去處理自己的數據。
試想兩個建立在tcp上的rtp session,這個兩個tcp socket既擔負着rtsp通訊,又擔負着rtp通訊。如果這兩個rtp session共用一個stream,那麼最終負責這兩個session通信的就只有一個RTPInterface,那麼這個RTPInterface中的fTCPStreams這個鏈表中就會有兩項,分別對應這兩個session。tcpStreamRecord主要用於socket number與channel id的對應。這些tcpStreamRecord是通過addStreamSocket()添加的。處理數據的handler是通過startNetworkReading()添加的,看一下下:

void RTPInterface::startNetworkReading(TaskScheduler::BackgroundHandlerProc* handlerProc)
{
	// Normal case: Arrange to read UDP packets:
	envir().taskScheduler().turnOnBackgroundReadHandling(fGS->socketNum(),handlerProc,
		fOwner);

	// Also, receive RTP over TCP, on each of our TCP connections:
	fReadHandlerProc = handlerProc;
	for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;
			streams = streams->fNext) {
		// Get a socket descriptor for "streams->fStreamSocketNum":
		SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(),
				streams->fStreamSocketNum);

		// Tell it about our subChannel:
		socketDescriptor->registerRTPInterface(streams->fStreamChannelId, this);
	}
}


用UDP時很簡單,直接把處理函數做爲handler加入taskScheduler即可。而TCP時,需向所有的session的socket都註冊自己。可以想像,socketDescriptor代表一個tcp socket,並且它有一個鏈表之類的東西,其中保存了所有的對這個socket感興趣的RTPInterface,同時也記錄了RTPInterface對應的channal id。只有向socketDescriptor註冊了自己,socketDescriptor在讀取數據時,才能跟據分析出的channel id找到對應的RTPInterface,才能調用RTPInterface中的數據處理handler,當然,這個函數也不是RTPInteface自己的,而是從startNetworkReading()這個函數接收到的調用者的。
上述主要講的是一個RTPInterface對應多個客戶端tcp socket的情形。現在又發現一個問題:SocketDescriptor爲什麼需要對應多個RTPInterface呢?上面已經講了,是爲了多路複用,因爲這個socket即負擔rtsp通信又負擔rtp通信還負擔RTCP通信。SocketDescriptor記錄多路複用數據(也就是RTPInterface與channel id)用了一個Hash table:HashTable* fSubChannelHashTable。SocketDescriptor讀數據使用函數:static void tcpReadHandler(SocketDescriptor*, int mask)。證據如下:

void SocketDescriptor::registerRTPInterface(
unsigned char streamChannelId,
		RTPInterface* rtpInterface)
{
	Boolean isFirstRegistration = fSubChannelHashTable->IsEmpty();
	fSubChannelHashTable->Add((char const*) (long) streamChannelId,
			rtpInterface);

	if (isFirstRegistration) {
		// Arrange to handle reads on this TCP socket:
		TaskScheduler::BackgroundHandlerProc* handler = 
			(TaskScheduler::BackgroundHandlerProc*) &tcpReadHandler;
		fEnv.taskScheduler().turnOnBackgroundReadHandling(fOurSocketNum,
				handler, this);
	}
}


可見在註冊第一個多路複用對象時啓動reand handler。看一下函數主體:

void SocketDescriptor::tcpReadHandler1(int mask)
{
	// We expect the following data over the TCP channel:
	//   optional RTSP command or response bytes (before the first '$' character)
	//   a '$' character
	//   a 1-byte channel id
	//   a 2-byte packet size (in network byte order)
	//   the packet data.
	// However, because the socket is being read asynchronously, this data might arrive in pieces.

	u_int8_t c;
	struct sockaddr_in fromAddress;
	if (fTCPReadingState != AWAITING_PACKET_DATA) {
		int result = readSocket(fEnv, fOurSocketNum, &c, 1, fromAddress);
		if (result != 1) { // error reading TCP socket, or no more data available
			if (result < 0) { // error
				fEnv.taskScheduler().turnOffBackgroundReadHandling(
						fOurSocketNum); // stops further calls to us
			}
			return;
		}
	}

	switch (fTCPReadingState) {
	case AWAITING_DOLLAR: {
		if (c == '$') {
			fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;
		} else {
			// This character is part of a RTSP request or command, which is handled separately:
			if (fServerRequestAlternativeByteHandler != NULL) {
				(*fServerRequestAlternativeByteHandler)(
						fServerRequestAlternativeByteHandlerClientData, c);
			}
		}
		break;
	}
	case AWAITING_STREAM_CHANNEL_ID: {
		// The byte that we read is the stream channel id.
		if (lookupRTPInterface(c) != NULL) { // sanity check
			fStreamChannelId = c;
			fTCPReadingState = AWAITING_SIZE1;
		} else {
			// This wasn't a stream channel id that we expected.  We're (somehow) in a strange state.  Try to recover:
			fTCPReadingState = AWAITING_DOLLAR;
		}
		break;
	}
	case AWAITING_SIZE1: {
		// The byte that we read is the first (high) byte of the 16-bit RTP or RTCP packet 'size'.
		fSizeByte1 = c;
		fTCPReadingState = AWAITING_SIZE2;
		break;
	}
	case AWAITING_SIZE2: {
		// The byte that we read is the second (low) byte of the 16-bit RTP or RTCP packet 'size'.
		unsigned short size = (fSizeByte1 << 8) | c;

		// Record the information about the packet data that will be read next:
		RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);
		if (rtpInterface != NULL) {
			rtpInterface->fNextTCPReadSize = size;
			rtpInterface->fNextTCPReadStreamSocketNum = fOurSocketNum;
			rtpInterface->fNextTCPReadStreamChannelId = fStreamChannelId;
		}
		fTCPReadingState = AWAITING_PACKET_DATA;
		break;
	}
	case AWAITING_PACKET_DATA: {
		// Call the appropriate read handler to get the packet data from the TCP stream:
		RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);
		if (rtpInterface != NULL) {
			if (rtpInterface->fNextTCPReadSize == 0) {
				// We've already read all the data for this packet.
				fTCPReadingState = AWAITING_DOLLAR;
				break;
			}
			if (rtpInterface->fReadHandlerProc != NULL) {
				rtpInterface->fReadHandlerProc(rtpInterface->fOwner, mask);
			}
		}
		return;
	}
	}
}


最開始的註釋中解釋了多路複用頭的格式。這一段引起了我的興趣:

case AWAITING_DOLLAR: {
		if (c == $) {
			fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;
		} else {
			// This character is part of a RTSP request or command, which is handled separately:
			if (fServerRequestAlternativeByteHandler != NULL) {
				(*fServerRequestAlternativeByteHandler)(
						fServerRequestAlternativeByteHandlerClientData, c);
			}
		}
		break;
	}


啊!原來ServerRequestAlternativeByteHandler是用於處理RTSP數據的。也就是從這個socket收到RTSP數據時,調用ServerRequestAlternativeByteHandler。如果收到RTP/RTCP數據時,先查看其channel id,跟據id找到RTPInterface(RTCP也是用了RTPIterface進行通信),設置RTPInterface中與讀緩衝有關的變量,然後當讀到包數據的開始位置時,調用rtpInterface中保存的數據處理handler。還記得吧,rtpInterface中的這個數據處理handler在UDP時也被使用,在這個函數中要做的是讀取一個包的數據,然後處理這個包。而SocketDescriptor把讀取位置置於包數據開始的位置再交給數據處理handler,正好可以使用與UDP相同的數據處理handler!
還有,socketDescriptor們並不屬於任何RTPInterface,而是單獨保存在一個Hash table中,這樣多個RTPInterface都可以註冊到一個socketDescriptor中,以實現多路複用。
總結一下通過RTPInterface,live555不僅實現了rtp over udp,還實現了rtp over tcp,而且還實現了同時即有rtp over tcp,又有rtp over udp!
最後,channel id是從哪裏來的呢?是在RTSP請求中指定的。在哪個請求中呢?自己找去吧。

 

轉自: http://blog.csdn.net/niu_gao/article/details/6988044

發佈了7 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章