Introduction

The RTP Library provides a high level interface for developing applications that make use of the Real Time Transport Protocol (RTP). This protocol was developed in the Audio/Video Working Group of the Internet Engineering Task Force (IETF), and has been published as RFC1889. This library is based on the most recent version of the specification, incorporating some of the newest features, including RTCP scalability algorithms.

This document describes the visible C programmer API for the library.

Acknowledgements

The RTP Library was developed at Lucent Technologies, in cooperation with Prof. Henning Schulzrinne from Columbia University, Jonathan Lennox from Columbia University, Daniel Rubenstein from U. Mass. Amherst, and Jonathan Rosenberg from Bell Laboratories. Licensing and maintenance have been provided by elemedia.

General Operation

This section overviews the basics for using the library.

Initialization

A number of calls must be made to initialize the library. The first of the first of these is RTPCreate(), which establishes a context. A context is an identifer used by the library to determine which RTP session a function call is to be associated with. An application can run many sessions at the same time, each created with a separate call to RTPCreate, resulting in a different context for each. Most library functions accept a context as the first argument.

Once RTPCreate has been called to initialize the session, the addresses for the session must be set. The library supports unicast (single point to point), multi-unicast (multiple unicast point to points),multicast, and hybrids. There are two sets of addresses used by the library. The first is the send set. This is a list of unicast and/or multicast addresses, port numbers, and ttl values (ttl values are only defined for multicast, of course). Whenever a packet is sent by the application, the library will transmit the packet to all the addresses listed in the send set. This allows for unicast (by setting a single unicast address/port value), multicast (by setting a single multicast address/port pair), multi-unicast (by setting many unicast address/port pairs), and hybrids.

The library also has a receive set defined. This set contains but one address/port pair, which can be multicast or unicast. It is the address that the library expects to receive packets on. The behavior is defined as follows:

  1. If the address is unicast, but does not correspond to a local interface, the library instead binds to INADDR_ANY, so that it will accept packets on any interface.
  2. If the address is unicast, and is a local interface, the library will only accept packets on this interface.
  3. If the address is multicast, the library will bind to INADDR_ANY, and then join the multicast group. This way, it will accept packets that are either unicast OR multicast for the given port.
  4. If the address is NULL, the library will bind to INADDR_ANY.
  5. If the port is zero, the library will use a dynamic port. Since the RTP and RTCP ports must be consecutive, the libary will try random pairs of dynamically assigned ports (above 49152) until it finds a pair. If a pair cannot be allocated, it will return an error.

All addresses are specified as strings. These must be of the form "A.B.C.D" (dotted-quad), or a hostname "machine.domain". If the string is a hostname, the library will attempt to resolve the name to an address using DNS.

There are a number of other initialization functions that are available. Most are optional. The ones which will generally need to be used are:

  1. RTPSessionSetBandwidth(). RTCP packets are sent at a rate that depends on the session bandwidth. This is a global parameter for the RTP session, obtained out of band from some other means (SAP or SIP, for example). The application should set this value before calling RTPOpenConnection() in order to ensure proper RTCP transmission rates. If it is not set, the default rate of 120 kbps.
  2. RTPMemberInfoSetSDES(). One RTCP packet type, SDES, contains information about each user. This includes the name, email, and CNAME for the user. It is the responsibility of the application to set these fields. In particular, the CNAME field MUST be set prior to calling RTPOpenConnection. All of the other SDES fields are optional.

Once the addresses for the session have been set, the function RTPOpenConnection() is called. This actually binds the receive sockets, and causes the library to join the multicast groups. After this, the library is ready to accept and send packets.

Sending and Receiving Packets

Sending packets is fairly straightforward. The RTPSend() function is used to tell the library to send an RTP packet. It requires the user to pass a pointer to a buffer, a length, a value for the marker field in the RTP header, an increment for the timestamp, and the context. The library will take the buffer, add the RTP header, perform any required operations, and send the packet. The library will automatically send RTCP packets. The initial timestamp and sequence number are chosen randomly.

If an application needs to send an RTP packet that is stored in a scatter gather structure, it can make use of the RTPSendVector, which is the writev equivalent of RTPSend.

Receiving packets is a little more complex. In order to know if a packet is available for reading, a process can block, it can poll, or use any other kind of mechanism. Since the library does not dictate this policy, it is up to the user to determine when data is available for reading. To do this, the library allows the user access to the receive sockets. There are two: one for RTP, one for RTCP. The functions RTPSessionGetRTPSocket and RTPSessionGetRTCPSocket are used to do this. They take as input the context and a pointer to a socket. When they return, the socket has been filled in. The use may then check for the presence on an RTP packet on these sockets in any fashion; using select, for example.

When a packet is present on either socket, the application should call the function RTPReceive(). This function takes the context, the socket on which data is present, a pointer to a buffer, and a pointer to a length value. The length value should be initialized to the amount of room in the buffer. The library will read and process the RTP or RTCP packet. For RTCP, it will perform all statistics collection and parsing. Generally, there will be no further action required from the user. For an RTP packet, the library will also update some statistics and variables. The buffer will be filled in with the entire RTP/RTCP packet, including the header.

Once RTPReceive is complete, the user may want to access data in the packet if it is an RTP packet. However, the packet in the original buffer used in the RTPReceive still contains the header. To gain access to the header fields, and the payload, the function RTPGetRTPPacket() is provided. This function accepts the buffer as an input, and returns a pointer to an RTP packet structure. This pointer is of type rtp_packet, and contains fields for the various RTP headers, and a pointer to the payload. The user can then access these fields for whatever purposes it likes.

Closing the Connection and Session

When the application has decided to terminated and/or leave the RTP session, it should do two things. First, the function RTPCloseConnection should be called. This will close all active receive sockets, send a BYE packet, and then close all send sockets. It does not delete internal storage or statistics that have been collected, however. RTPCloseConnection accepts a reason string. This string is sent in the BYE packet. If set to NULL, the library will not include any reason in the BYE.

To actually destroy all internal information about the session, the function RTPDestroy is provided. It takes the context as its sole argument, and frees all memory associated with the context.

Accessing Member Information

Many of the RTP Library functions provide support for accessing information about the other members of the session. Each user is assigned a unique identifier by the library, of type person. Unlike the SSRC, which can change due to collisions, the unique identifier stays constant. Most of the member information elements are accessed by passing the unique id to the library.

There are a number of ways to obtain the unique id for a member. These include:

  1. CallBack functions: The library provides for callback functions that the library calls when certain events occur. These events include things like new member, timeouts, new sender, etc. The functions are provided with the unique id of the person associated with the event. This allows applications to access membership information easily at the time when it is likely to be most needed.
  2. List Iterators. The library allows the application to obtain the list of current group members. This list is provided by returning the unique identifiers for the users.
  3. Most Recent. The library provides two function calls for obtaining the member who sent the most recently received RTP and RTCP packets (RTPMostRecentRTPPerson and RTPMostRecentRTCPPerson).
  4. Local. The local member (i.e., the member that the application is sending packets on behalf of) is always known by the unique identifier 0.
  5. Finding. The library provides a function, RTPFindMember, that allows an application to obtain a unique id for a member based on the value of an SDES field. The function accepts a context, an SDES field, and a value. It also accepts a pointer to a person, which is set to the unique ID of the first member whose SDES field has the given value. This function is most useful for finding members based on CNAME. When multiple sessions are used, the CNAME of the user stays the same across sessions. An application will most likely need to access sender report information (timestamps in particular) based solely on CNAME.

Once the unique identifier has been obtained, it can be used to access a host of different pieces of information. These include:

  1. SDES. The function RTPMemberInfoGetSDES() takes a unique id, an SDES field, and a pointer to a buffer. The library will copy the SDES item into the buffer.
  2. SR Info. There are a host of functions for getting the information present in sender reports from a member. These functions are of the form RTPMemberInfoGetX, where X is one of {NTPStamp,RTPStamp,PktCount,OctCount}, which correspond to the elements in a sender report from that member. If the member is not a sender, these will return zero.
  3. SSRC. The function RTPMemberInfoGetSSRC() returns the SSRC for a member.
  4. RR. Each member in a group sends out receiver reports for each sender. The library records all the information in receiver reports from every group member. To obtain information from a receiver report, the application must provide both the unique identifier for the member who sent the receiver report, AND the SSRC for the sender that the RR refers to.
  5. Member Status. The function RTPMemberInfoGetStatus() returns the state of the member (pending, expired, confirmed), and whether they are an active sender.

Callback Functions

Certain events happen during an RTP session which are of interest to the application. To alert the application of these events, callback functions are used. There are four callbacks which will generally be needed by the application. Two others are more advanced, and should not usually be needed. The three basic ones are:

  1. MemberUpdateCallBack: When the status of a member changes, this callback is called, along with a flag indicating the new status. Member status is discussed below.
  2. ChangedMemberInfoCallBack. When a session member sends an SDES packet, and the information in the packet is different from the last SDES packet, this function is called. This is useful for finding when the NOTE field changes, for example.
  3. ChangedMemberAddressCallBack. When the library receives a packet from a member, and that packet is from a different network address than the last packet received from the member, this function is called reporting the new address.
  4. RevertingIDCallBack: This functions usage is a little complex, but is needed. Each member of the group is assigned a unique ID, as described above. If a member is involved in an SSRC collision, they may leave the group, change their SSRC and rejoin with the new SSRC. When they rejoin, they may send an RTP packet before an RTCP packet. The library will see a new SSRC, and not realize that this is a rejoined member. So, it assigns this member a unique ID. When the RTCP packet shows up, the library realizes that this is the same member, who now has two unique ID's. The library will then call this callback, and indicate both ID's. The library will also destroy all membership records associated with the new ID, and combine the statistics with the old one. However, the library allows the application to set a piece of the membership record (user info, described below). If this information is a pointer to some memory in application space, the user must free this memory, or make a copy of the pointer, since the library will destroy its own copy of the pointer. This is the main motivation for this callback. Note that if the library never makes use of the user info field, it need not set this callback function.

Each member in the group has two states. The first is its member state, and is one of six: pending, confirmed, expired, pending contributor, confirmed contributor, and expired contributor. A member is pending if it has sent a single packet (RTP or RTCP), or appeared in a sender report or receiver report. The member is confirmed if it has sent more than one RTP or RTCP packets, or a CNAME is received. The member is a pending contributor if they have appeared in the CSRC list once in a RTP data packet. The member is a confirmed contributor if they have appeared twice in the CSRC list of a data packet, or have had a CNAME sent for them in an SDES packet. RTP has a provision for member timeouts, which occur if a member has sent neither an RTP or RTCP packet over the last 5 RTCP transmission periods. When a confirmed member times out, they change state to expired. The statistics and user information are not destroyed, but they are not counted as part of the group. When a pending member times out, they are destroyed. Similarly, if a confirmed contributor has not appeared in an CSRC list, or an SDES packet been received for them in the last 5 RTCP transmission periods, they become a pending contributor. A pending contributor in the same scenario is destroyed.

Pending members are not used in group size estimation. This helps prevent encrypted packets being mistakenly considered as from a new random SSRC. A member in any of the pending states is not used towards group size estimation, and neither are expired members. The group size estimate is used by the library in computing the interval between RTCP packet transmissions.

If an expired member "comes back" by sending a packet, they revert back to the confirmed state.

Orthogonal to a member's status as pending, confirmed, or expired, they can be in one of three sending states: not, purported, or confirmed. A member is in the not state if it has never sent a data packet, never send an SR packet, and never appeared in a receiver report block. A purported sender is a member who has sent an RTP packet but is not a confirmed member, or a member who has sent an SR or appeared in a receiver report block, but never sent a packet. These senders are purported because they, or someone else, has claimed them to be senders, but this has not yet been verified by a data packet. A sender is confirmed if they are a confirmed member and have sent an RTP data packet in the last RTCP transmission interval. A confirmed sender will time out, and become purported, after not sending a data packet in the last RTCP transmission interval. After four more transmission intervals, it will move from purported to not. At this point, any statistics about this member are destroyed. The library uses the count of confirmed senders in determining the frequency of RTCP packet transmissions.

When a members status changes in any way, the MemberUpdateCallBack is called. The function is passed a flag which indicates what the status change is. The flags are described in rtp_api.h

To set the callback functions, there are six RTPSetXXXCallBack functions, each of which sets the appropriate callback. These are passed the context and the a pointer to the function, written by the user, to be used for callback.

The library also defines two advanced callback functions. The first is CollidedMemberCallBack. When two users have an SSRC collision, this function is called by the library. The library will automatically handle the resolution of the collision. The callback is available if the presence of a collision needs to be known by the application.

The second advanced callback is SendErrorCallBack. The library supports sending data to multiple receive destinations. Internally to the library, each supports a separate connected socket. Should one of the peers on a socket go down, or no longer be responding on the given port, certain systems provide notification when trying to send, even on connectionless sockets. When the library tries to send data to a socket, and it gets some kind of error indication, this callback is called. The function is passed the IP address and port that the error was reported from. The library will not close sockets that continually report errors. It is up to the application to turn them off with RTPRemoveSendAddr.

Using Iterator Functions

There are cases where the application would like to obtain a list of things, such as a list of the current session members, or a list of the current session senders. To support this in a scalable manner, the library supports the concept of iterators. An iterator is an object that represents a list of members and a pointer to one of the members on the list. Once an iterator is obtained, it can be used to cycle through the list. Two functions are used for this. RTPCurrentMember returns the current member of the list, and RTPNextMember advances the pointer to the next member, and returns it as well. Note that successive calls to RTPCurrentMember will not advance the pointer, whereas RTPNextMember will.

To get an iterator, the function RTPSessionGetMemberList is provided. It takes as input the context, and a pointer to an iterator. The function fills in the iterator and initializes it. Afterwards, the list can be walked through with RTPCurrentMember and RTPNextMember. Once the end of the list is reached, both functions return an error condition. Note that the state of the iterator WILL NOT BE MAINTAINED ACROSS CALLS TO OTHER LIBRARY FUNCTIONS. The iterator is meant to be used in one shot, during a single application operation. Library functions may modify these lists and make the pointers contained in the iterator invalid.

Note that RTPCurrentMember and RTPNextMember return a unique-id to represent the member. The member information functions, described above, can then be used to obtain the desired information, including member status.

Timed Events

Two RTP related events must occur which are timed. They are:
  1. RTCP reports must be sent periodically.
  2. BYE packets may be reconsidered for transmission.

To support scheduling, timed events are handled by two functions, RTPSchedule and RTPExecute. The first of these functions is written by the user. It is called by the RTP library to request scheduling of events at some predetermined time. The user is responsible for writing the function to schedule the event with whatever mechanism is appropriate for the application. The second function is part of the RTP library, and is to be called upon execution of the timed event. The specific formats of the functions are:

void RTPSchedule(context cid, rtp_opaque_t opaque, struct timeval *tp);

rtperror RTPExecute(context cid, rtp_opaque_t opaque);

The RTP library will call RTPSchedule, and pass it the context cid and an opaque piece of data, opaque. It will also pass the time for which the scheduled event is to occur, tp. At the appropriate time, tp, the application should call RTPExecute, and pass it the opaque token provided in the call to RTPSchedule, in addition to the context.

This general mechanism allows for the RTP library to schedule arbitrary timed events. All information about the nature of the events is kept internal. The opaque data is used internally to identify particular events. RTPExecute() cannot be called from within a callback function in the same context.

RTPStrError

The format of the call is :
char *RTPStrError();

RTPStrError returns an error message associated with the most recent error that was raised by the RTP library (not including RTP_OK, which represents a lack of error. This error message is more detailed than the error number returned by a function that returns type rtperror in that it also states the calling function and often offers details about the context and other values that might have induced the error.

Obtaining RR Information

RTCP receiver reports (RR's) contain information from each receiver, about each sender. The library stores all of this information, and makes it available to the application if needed. Reception quality for the local member is obtained in the same way its obtained for all other members.

To get reception quality information, the application must provide the identifier of the user about whom the reception reports are desired. The library routines operator via an iterator. The initialization routine gets the first receiver report about the particular user. Another routine cycles through the reception reports on each call, returning receiver reports from different receivers about the given sender. Once the iterator is complete, an error value is returned.

The iterators return a pointer to a receiver report structure. This structure contains all of the relevant information from the receiver reports.

RTPSenderInfoGetFirstReceiverReport(context cid, person p, receiver_report_iterator *the_iterator, receiver_report *report) is used to initialize the iterator. It also fills in the receiver report structure with the first receiver report for the given person p. An error is returned if the person is not a sender. To get the next report blcok, the user must call RTPSenderInfoGetNextReceiverReport(context cid, person p, receiver_report_iterator *the_iterator, receiver_report *report) to get the next one.

When used with a person value of 0, the library returns receiver reports received about the local member. In addition, receiver reports sent by the local member will appear on the list of receiver reports returned by the above functions. These represent the state sent in actual reports, not the most recent information the local member has about the given sender

To obtain the most recent statistics about other senders, as observed by the local member, the function RTPSenderInfoGetLocalReception(context cid, person p, receiver_report *report) is used. It takes the given person and a pointer to a receiver report structure, and fills it in. The resulting receiver report is the receiver report which would be sent at that point in time for the given sender. Note that since the library doesn't send a receiver report at this point, the actual receiver report generated will differ when it is sent.

User Info Fields

In many cases, it would be very desirable to not have the application maintain an actual list of members of its own, since the library already does this. However, most applications will need to establish additional information associated with each member (such as a pointer to a window in the GUI). To support this, the library allows the application to set a pointer in the internal list of members. This pointer can point to memory allocated and used by the application. The pointer can be obtained from the library using some API functions.

In particular, the function RTPMemberInfoSetUserInfo takes the context, a unique-id, and a pointer as an argument. The pointer is stored internally in the record associated with that member. The function RTPMemberInfoGetUserInfo can then be used to retrieve this pointer. It takes the context, the unique-id of the member, and a pointer to the user info pointer as an argument. The library will then write the value of the pointer.

As an example of the usage of this field, consider an application which lists the current group members in a GUI, and highlights those members who are currently senders. When a member times out, they are removed from the window. To support this, an application can set the MemberUpdateCallBack to be called. When a new user appears, the library will call the callback. The application can then create a new window in the GUI, and place a pointer to it in the user info field with the above RTPMemberInfoSetUserInfo function. Some time later, if the member becomes a sender, the callback is called once more. The library can the retrieve the pointer to the window, and use it to change the members name to bold. This allows the application to not even store the pointers to the windows in application space. It can rely totally on storage in the library.

The library also allows the application to store a pointer for each session. This pointer is set using RTPSessionSetUserInfo and read using RTPSessionGetUserInfo. Its usage is similar to the MemberInfo versions.

A word of caution on the usage of these pointers. When a member is to be deleted by the library, its member record is destroyed. This includes the pointer which might have been set by RTPMemberInfoSetUserInfo. If the application does not keep a copy of this pointer, or does not free the memory when the callback is called to indicate the member is to be deleted, memory leaks WILL occur. Similarly, a reverting ID event (due to a collision of SSRC) will cause memerber records to be destroyed. When the callback for this is invoked, the application should free the memory associated with the member. The same is true for the session user info. Before invoking RTPDestroy, the application should free any memory associated with the session user info pointer.