live555学习笔记13-RTPInterface详解
来源:互联网 发布:数据库建设方案 编辑:程序博客网 时间:2024/06/06 04:54
十三: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一律能接收你们的数据,并向你们发出数据!
很明显啊,先发送udp数据,一对多的问题在GroupSocket中解决。再发送tcp数据,一对多的问题本地解决。
证据二:从所有客户端读取数据:
我现在找不到直接的证据,所以我就憶想一下吧:当udp端口或tcp端口收到数据时,分析后,是哪个客户端的数据就发给对应这个客户端的RTPSink或RTCPInstance。
好像已经把最开始的问题解答完了。下面让我们来分析一下RTPInterface吧。
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()添加的,看一下下:用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)。证据如下:可见在注册第一个多路复用对象时启动reand handler。看一下函数主体:最开始的注释中解释了多路复用头的格式。这一段引起了我的兴趣:啊!原来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请求中指定的。在哪个请求中呢?自己找去吧。
好几天没写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端口或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);
- }
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);
- }
- }
上述主要讲的是一个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);
- }
- }
- 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;
- }
还有,socketDescriptor们并不属于任何RTPInterface,而是单独保存在一个Hash table中,这样多个RTPInterface都可以注册到一个socketDescriptor中,以实现多路复用。
总结一下通过RTPInterface,live555不仅实现了rtp over udp,还实现了rtp over tcp,而且还实现了同时即有rtp over tcp,又有rtp over udp!
最后,channel id是从哪里来的呢?是在RTSP请求中指定的。在哪个请求中呢?自己找去吧。
- live555学习笔记13-RTPInterface详解
- live555学习笔记13-RTPInterface详解
- live555学习笔记13-RTPInterface详解
- live555学习笔记13-RTPInterface详解
- live555学习笔记17-H264VideoStreamParser详解
- live555学习笔记17-H264VideoStreamParser详解 .
- live555学习笔记17-H264VideoStreamParser详解
- live555学习笔记17-H264VideoStreamParser详解 .
- live555学习笔记9-h264 RTP传输详解(1)
- live555学习笔记10-h264 RTP传输详解(2)
- live555学习笔记11-h264 RTP传输详解(3)
- live555学习笔记9-h264 RTP传输详解(1)
- live555学习笔记10-h264 RTP传输详解(2)
- live555学习笔记11-h264 RTP传输详解(3)
- live555学习笔记9-h264 RTP传输详解(1)
- live555学习笔记10-h264 RTP传输详解(2)
- live555学习笔记11-h264 RTP传输详解(3)
- live555学习笔记11-h264 RTP传输详解(3)
- live555学习笔记11-h264 RTP传输详解(3)
- linux下挂载u盘、iso镜像、光驱
- live555学习笔记12-h264 rtp包的时间戳
- Windows7(64位)系统中PL/SQL Developer连接Oracle数据库
- SVM支持向量机
- live555学习笔记13-RTPInterface详解
- JNI开发------测试编译好的ffmpeg库
- 用U盘通过局域网来安装CentOS 6.0
- Live555学习笔记14-live555多线程论
- 黑马程序员--面向对象(二)
- struct sockaddr与struct sockaddr_in的区别和联系
- OpenCV学习笔记(四十九)——号外!OpenCV-2.4.0 release 千呼万唤始出来
- 4种强制类型转换
- live555学习笔记15-RTCPInstance类小结