Live555 Study Notes 13-rtpinterface

Source: Internet
Author: User
XIII: rtpinterface details

I haven't written a blog for several days. Looking at the source code is really tiring. I need to write my understanding on paper and organize chaotic ideas. This is a headache, so I need passion. But today, passion is coming again.

You should have understood the groupsocket class. Theoretically, the classes that need to operate UDP socket should store groupsocket instances. But this is not the case. You can check rtpsink, rtpsource, rtcpinstance, and so on. None of them store groupsocket variables. Which of the following classes do they perform socket operations? It's rtpinterface !!
The groupsocket pointers received by these classes are finally passed to rtpinterface. Why do we use rtpinterface instead of groupsocket? There is a story here.

To answer this question, let's raise the question first.
Live555 supports RTP over UDP and RTP over TCP. So how can we use groupsocket to implement RTP over TCP? Groupsocket is only UDP!
Now that rtpinterface is used for network read/write, it should support both TCP transmission and UDP transmission. It also supports one-to-many features like groupsocket. Because the server is one to multiple clients. Let's take a look at the rtpinterface members:
Groupsock * FGS;
Tcpstreamrecord * ftcpstreams; // optional, for RTP-over-TCP streaming/caching ing
Hey, these two are close to each other, indicating that their relationship is not normal (do they have a leg ?). FGS -- represents a UDP socket and its corresponding multiple destination ends, ftcpstreams -- represents multiple TCP sockets, of course, these sockets are from a socket accept () client socket (tcpstreamrecord is a linked list ).
I think everyone will come to the conclusion that rtpinterface is really cool for men and women! No matter whether your client has established a TCP or UDP connection with me, my rtpinterface can receive your data and send data to you!

Evidence 1: send data to all clients:

Boolean rtpinterface: sendpacket (unsigned char * packet, unsigned packetsize) <br/>{< br/> Boolean success = true; // We'll return false instead if any of the sends fail </P> <p> // normal case: Send as a UDP packet: <br/> If (! FGS-> output (envir (), FGS-> TTL (), packet, packetsize) <br/> success = false; </P> <p> // also, send over each of our TCP sockets: <br/> for (tcpstreamrecord * streams = ftcpstreams; streams! = NULL; <br/> streams = streams-> fnext) {<br/> If (! Sendrtpovertcp (packet, packetsize, streams-> fstreamsocketnum, <br/> streams-> fstreamchannelid) {<br/> success = false; <br/>}</P> <p> return success; <br/>}Obviously, UDP data is sent first, and the one-to-many problem is solved in groupsocket. Send TCP data again, and solve one-to-multiple problems locally.
Evidence 2: read data from all clients:
I can't find any direct evidence, so I just want to think about it: When the UDP port or TCP port receives data, after analysis, the data of the client is sent to the rtpsink or rtcpinstance of the client.
It seems that you have answered the first question. Let's analyze the rtpinterface.

Void rtpinterface: setstreamsocket (INT socknum, unsigned char streamchannelid) <br/>{< br/> FGS-> removealldestinations (); <br/> addstreamsocket (socknum, streamchannelid ); <br/>}</P> <p> void rtpinterface: addstreamsocket (INT socknum, unsigned char streamchannelid) <br/>{< br/> If (socknum <0) <br/> return; </P> <p> for (tcpstreamrecord * streams = ftcpstreams; streams! = NULL; <br/> streams = streams-> fnext) {<br/> If (streams-> fstreamsocketnum = socknum <br/> & streams-> fstreamchannelid = streamchannelid) {<br/> return; // We already have it <br/>}</P> <p> ftcpstreams = new tcpstreamrecord (socknum, streamchannelid, ftcpstreams); <br/>}There is no need to talk about setstreamsocket (). Let's take a look at addstreamsocke (). You can understand it literally by adding a streaming socket, that is, adding TCP
Socket. Find whether the record already exists in the loop and create it if it does not exist. In the constructor of tcpstreamrecord, you have added yourself to the linked list. For parameters, socknum is easy to understand, that is, the socket type returned by socket ().
What is streamchannelid? Let's take a second guess. (It's strange that I can guess it every time, hey ...): When RTP over TCP, this TCP connection directly uses the TCP connection used by RTSP. If there are many
Session, coupled with the RTSP session, we all use this socket for communication. How do you distinguish between yours and mine? I think this channel
ID is used to solve this problem. Assign a unique ID to each session, and add a header to the package when sending its own package. The session ID must be included in the header, that is, the channel ID, package length and other fields. In this way, you can wear a pair of trousers. The term is multiplexing, but you must note that only TCP can be used for multiplexing. UDP does not need to be used, because UDP is a session corresponding to a socket (and RTCP is two ).
Imagine that the server must add a handler to taskscheduler to read and write data from the TCP socket. When reading data, analyze the read data, obtain the length of the data packet, and its channel ID. Find the corresponding handler and object according to the channel ID, hand it over to them to process their own data.
Imagine two RTP Sessions established on TCP. The two TCP sockets are both responsible for RTSP communication and RTP communication. If the two RTP sessions share one stream, only one rtpinterface is responsible for the communication between the two sessions. There are two items in the ftcpstreams linked list in the rtpinterface, corresponding to the two sessions respectively. Tcpstreamrecord is mainly used for the correspondence between socket number and channel ID. These tcpstreamrecord are added through addstreamsocket. The handler that processes data is added through startnetworkreading (). See the following example:

Void rtpinterface: startnetworkreading (taskscheduler: backgroundhandlerproc * handlerproc) <br/>{< br/> // normal case: arrange to read UDP Packets: <br/> envir (). taskscheduler (). turnonbackgroundreadhandling (FGS-> socketnum (), handlerproc, <br/> fowner); </P> <p> // also, receive RTP over TCP, on each of our TCP connections: <br/> freadhandlerproc = handlerproc; <br/> for (tcpstreamrecord * streams = ftcpstrea MS; streams! = NULL; <br/> streams = streams-> fnext) {<br/> // get a socket descriptor for "streams-> fstreamsocketnum ": <br/> socketdescriptor * socketdescriptor = lookupsocketdescriptor (envir (), <br/> streams-> fstreamsocketnum); </P> <p> // tell it about our subchannel: <br/> socketdescriptor-> registerrtpinterface (streams-> fstreamchannelid, this); <br/>}< br/>}It is easy to use UDP. Simply add the handler as a handler to taskscheduler. In TCP, You need to register yourself with all session sockets. As you can imagine, socketdescriptor represents a TCP socket, and it has a linked list and other things. It stores all rtpinterfaces interested in this socket, the channal ID corresponding to the rtpinterface is also recorded. Only when you register yourself with socketdescriptor, socketdescriptor can read data from the analyzed Channel
ID to find the corresponding rtpinterface to call the data processing handler in rtpinterface. Of course, this function is not the caller of rtpinteface, but received from startnetworkreading.
The above mainly describes the situation where an rtpinterface corresponds to multiple client TCP sockets. Now I found another problem: Why does socketdescriptor need to correspond to multiple rtpinterfaces? As mentioned above, it is for multiplexing, because this socket is responsible for RTSP communication, RTP communication, and RTCP communication. Socketdescriptor records multiplexing data (rtpinterface and channel ID) using a hash table: hashtable * fsubchannelhashtable. Socketdescriptor: static
Void tcpreadhandler (socketdescriptor *, int mask ). The evidence is as follows:

Void socketdescriptor: registerrtpinterface (<br/> unsigned char streamchannelid, <br/> rtpinterface * rtpinterface) <br/>{< br/> Boolean isfirstregistration = fsubchannelhashtable-> isempty (); <br/> fsubchannelhashtable-> Add (char const *) (long) streamchannelid, <br/> rtpinterface); </P> <p> If (isfirstregistration) {<br/> // arrange to handle reads on this TCP socket: <br/> taskscheduler:: backgroundhandlerproc * Handler = <br/> (taskscheduler: backgroundhandlerproc *) & tcpreadhandler; <br/> fenv. taskscheduler (). turnonbackgroundreadhandling (foursocketnum, <br/> handler, this); <br/>}< br/>}It can be seen that the reand handler is started when the first multiplexing object is registered. Take a look at the function body:

Void socketdescriptor: tcpreadhandler1 (INT mask) <br/>{< br/> // We recommend CT the following data over the TCP channel: <br/> // optional RTSP command or response bytes (before the first '$ 'character) <br/> // a' $ 'character <br/> // A 1-byte channel id <br/> // a 2-byte packet size (in network byte order) <br/> // The packet data. <br/> // However, because the socket is being read asynchronously, this data Might arrive in pieces. </P> <p> u_int8_t C; <br/> struct sockaddr_in fromaddress; <br/> If (ftcpreadingstate! = Awaiting_packet_data) {<br/> int result = readsocket (fenv, foursocketnum, & C, 1, fromaddress); <br/> If (result! = 1) {// Error reading TCP socket, or no more data available <br/> If (result <0) {// error <br/> fenv. taskscheduler (). turnoffbackgroundreadhandling (<br/> foursocketnum); // stops further callto us <br/>}< br/> return; <br/>}</P> <p> switch (ftcpreadingstate) {<br/> case awaiting_dollar: {<br/> If (C = '$') {<br/> ftcpreadingstate = awaiting_stream_channel_id; <br/>} else {<br/> // This Cha Racter is part of a RTSP request or command, which is handled separately: <br/> If (fserverrequestalternativebytehandler! = NULL) {<br/> (* fserverrequestalternativebytehandler) (<br/> fserverrequestalternativebytehandlerclientdata, c); <br/>}< br/> break; <br/>}< br/> case awaiting_stream_channel_id: {<br/> // The byte that we read is the stream channel ID. <br/> If (lookuprtpinterface (c )! = NULL) {// sanity check <br/> fstreamchannelid = C; <br/> ftcpreadingstate = awaiting_size1; <br/>} else {<br/> // This wasn' t a stream channel ID that we expected. we're (somehow) in a strange state. try to recover: <br/> ftcpreadingstate = awaiting_dollar; <br/>}< br/> break; <br/>}< br/> case awaiting_size1: {<br/> // The byte that we read is the first (high) byte of the 16-bit RTP or RTCP packet' Size '. <br/> fsizebyte1 = C; <br/> ftcpreadingstate = awaiting_size2; <br/> break; <br/>}< br/> case awaiting_size2: {<br/> // The byte that we read is the second (low) byte of the 16-bit RTP or RTCP packet 'SIZE '. <br/> unsigned short size = (fsizebyte1 <8) | C; </P> <p> // record the information about the packet data that will be read next: <br/> rtpinterface * rtpinterface = lookuprtpinterface (fstreamc Hannelid); <br/> If (rtpinterface! = NULL) {<br/> rtpinterface-> fnexttcpreadsize = size; <br/> rtpinterface-> fnexttcpreadstreamsocketnum = foursocketnum; <br/> rtpinterface-> token = fstreamchannelid; <br/>}< br/> ftcpreadingstate = awaiting_packet_data; <br/> break; <br/>}< br/> case awaiting_packet_data: {<br/> // call the appropriate read handler to get the packet data from the TCP stream: <br/> rtpinterface * rtpin Terface = lookuprtpinterface (fstreamchannelid); <br/> If (rtpinterface! = NULL) {<br/> If (rtpinterface-> fnexttcpreadsize = 0) {<br/> // we 've already read all the data for this packet. <br/> ftcpreadingstate = awaiting_dollar; <br/> break; <br/>}< br/> If (rtpinterface-> freadhandlerproc! = NULL) {<br/> rtpinterface-> freadhandlerproc (rtpinterface-> fowner, mask); <br/>}< br/> return; <br/>}< br/>}The first comment explains the format of multiplexing headers. This section aroused my interest:

Case awaiting_dollar: {<br/> If (C ==) {<br/> ftcpreadingstate = awaiting_stream_channel_id; <br/>} else {<br/> // this character is part of a RTSP request or command, which is handled separately: <br/> If (fserverrequestalternativebytehandler! = NULL) {<br/> (* fserverrequestalternativebytehandler) (<br/> fserverrequestalternativebytehandlerclientdata, c); <br/>}< br/> break; <br/>}Ah! Originally serverrequestalternativebytehandler was used to process RTSP data. That is, when the RTSP data is received from this socket, serverrequestalternativebytehandler is called. If you receive RTP/RTCP data, first check its channel ID, find rtpinterface with the data ID (RTCP also uses rtpiterface for communication), and set the variables related to the read buffer in rtpinterface, then, when reading the starting position of the package data, call the data processing handler stored in rtpinterface. Remember, the data processing handler in rtpinterface is also used in UDP. In this function, you need to read the data of a package and then process the package. Socketdescriptor places the read location at the beginning of the package data and then hand it to the data processing handler, which is exactly the same as UDP's data processing handler!
In addition, socketdescriptor does not belong to any rtpinterface, but is stored separately in a hash table, so that multiple rtpinterfaces can be registered to a socketdescriptor for multiplexing.
Conclusion: Through rtpinterface, live555 not only implements RTP over UDP, but also implements RTP over TCP. It also implements RTP over TCP and RTP over UDP!
Finally, where does the channel Id come from? Is specified in the RTSP request. In which request? Find it by yourself.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.