oRTP源码分析
前言
本文主要是想在阅读oRTP协议栈时,为了以后的回忆,做些笔录。
一、AVProfile 模块
1.1 Pt的总结:
引用:http://en.wikipedia.org/wiki/RTP_audio_video_profile
RTP/AVP audio and video payload types
Payload type (PT) | Name | Type | No. of channels | Clock rate (Hz) | Description | References |
---|---|---|---|---|---|---|
0 | PCMU | audio | 1 | 8000 | ||
1 | reserved (previously 1016) | audio | 1 | 8000 | reserved, previously CELPAudio 4.8 kbit/s | |
2 | reserved (previously G721) | audio | 1 | 8000 | ||
3 | GSM | audio | 1 | 8000 | European GSMFull Rate Audio 13 kbit/s (GSM 06.10) | |
4 | G723 | audio | 1 | 8000 | ITU-T G.723.1 | |
5 | DVI4 | audio | 1 | 8000 | ||
6 | DVI4 | audio | 1 | 16000 | ||
7 | LPC | audio | 1 | 8000 | Experimental LinearPredictive Coding Audio | |
8 | PCMA | audio | 1 | 8000 | ITU-T G.711 PCM A-LawAudio 64 kbit/s | |
9 | G722 | audio | 1 | 8000 | ITU-T G.722Audio | |
10 | L16 | audio | 2 | 44100 | Linear PCM16-bit Stereo Audio 1411.2 kbit/s,[2][3][4]uncompressed | |
11 | L16 | audio | 1 | 44100 | Linear PCM 16-bit Audio 705.6 kbit/s, uncompressed | |
12 | QCELP | audio | 1 | 8000 | ||
13 | CN | audio | 1 | 8000 | Comfortnoise. Payload type used with audio codecs that do notsupport comfort noise as part of the codec itself such as G.711,G.722.1,G.722, G.726,G.727, G.728,GSM 06.10,Siren, andRTAudio. | |
14 | MPA | audio | 1 | 90000 | ||
15 | G728 | audio | 1 | 8000 | ITU-T G.728Audio 16 kbit/s | |
16 | DVI4 | audio | 1 | 11025 | ||
17 | DVI4 | audio | 1 | 22050 | IMA ADPCM | |
18 | G729 | audio | 1 | 8000 | ITU-T G.729and G.729a | |
25 | CELB | video | 1 | 90000 | ||
26 | JPEG | video | 1 | 90000 | JPEG Video | |
28 | NV | video | 1 | 90000 | ||
31 | H261 | video | 1 | 90000 | ITU-T H.261Video | |
32 | MPV | video | 1 | 90000 | MPEG-1 and MPEG-2 Video | |
33 | MP2T | audio/video | 1 | 90000 | MPEG-2 transportstream Video | |
34 | H263 | video | 90000 | H.263 video,first version (1996) | ||
35 - 71 | unassigned | |||||
72 - 76 | Reserved for RTCP conflict avoidance | N/A | N/A | |||
77 - 95 | unassigned | |||||
dynamic | H263-1998 | video | 90000 | H.263 video,second version (1998) | ||
dynamic | H263-2000 | video | 90000 | H.263 video,third version (2000) | ||
dynamic (or profile) | H264 | video | 90000 | H.264 video(MPEG-4 Part 10) | ||
dynamic (or profile) | theora | video | 90000 | Theora video | ||
dynamic | iLBC | audio | 1 | — | Internet lowBitrate Codec 13.33 or 15.2 kbit/s | |
dynamic | PCMA-WB | audio | 16000 | ITU-T G.711.1,A-law | ||
dynamic | PCMU-WB | audio | 16000 | ITU-T G.711.1,µ-law | ||
dynamic | G718 | audio | 32000 | ITU-T G.718 | ||
dynamic | G719 | audio | (various) | 48000 | ITU-T G.719 | |
dynamic | G7221 | audio | 16 or 32 kHz | ITU-T G.722.1 | ||
dynamic | G726-16 | audio | 1 | 8000 | ITU-T G.726audio with 16 kbit/s | |
dynamic | G726-24 | audio | 1 | 8000 | ITU-T G.726 audio with 24 kbit/s | |
dynamic | G726-32 | audio | 1 | 8000 | ITU-T G.726 audio with 32 kbit/s | |
dynamic | G726-40 | audio | 1 | 8000 | ITU-T G.726 audio with 40 kbit/s | |
dynamic | G729D | audio | 1 | 8000 | ITU-T G.729Annex D | |
dynamic | G729E | audio | 1 | 8000 | ITU-T G.729Annex E | |
dynamic | G7291 | audio | (various) | ITU-T G.729.1 | ||
dynamic | GSM-EFR | audio | 1 | 8000 | ITU-T GSM-EFR(GSM 06.60) | |
dynamic | GSM-HR-08 | audio | 1 | 8000 | ITU-T GSM-HR(GSM 06.20) | |
dynamic (or profile) | AMR | audio | (various) | 8000 | AdaptiveMulti-Rate audio | |
dynamic (or profile) | AMR-WB | audio | (various) | 16000 | AdaptiveMulti-Rate Wideband audio (ITU-T G.722.2) | |
dynamic (or profile) | AMR-WB+ | audio | 1, 2 or omit | 72000 | ||
dynamic (or profile) | vorbis | audio | (various) | from 8 kHz to 192 kHz | RTP Payload Format for VorbisEncoded Audio | |
dynamic (or profile) | opus | audio | 1, 2 | 48000 (the actual clock rate is signaled inside the payload) | RTP Payload Format for OpusSpeech and Audio Codec | |
dynamic (or profile) | speex | audio | 1 | 8000, 16000 or 32000 | RTP Payload Format for the SpeexCodec | |
dynamic (96-127) | mpa-robust | audio | 90000 | A More Loss-Tolerant RTP Payload Format for MP3Audio | ||
dynamic (or profile) | MP4A-LATM | audio | 90000 or others | RTP Payload Format for MPEG-4Audio | ||
dynamic (or profile) | MP4V-ES | video | 90000 or others | RTP Payload Format for MPEG-4Visual | ||
dynamic (or profile) | mpeg4-generic | audio/video | 90000 or other | RTP Payload Format for Transport of MPEG-4Elementary Streams | ||
dynamic | VP8 | video | 90000 | RTP Payload Format for Transport of VP8 Streams | ||
dynamic | L8 | audio | (various) | (various) | Linear PCM8-bit audio with 128 offset | RFC 3551Section 4.5.10 and Table 5 |
dynamic | DAT12 | audio | (various) | 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000 orothers | IEC 61119 12-bit nonlinear audio | RFC 3190Section 3 |
dynamic | L16 | audio | (various) | 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000 orothers | Linear PCM16-bit audio | |
dynamic | L20 | audio | (various) | 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000 orothers | Linear PCM20-bit audio | RFC 3190Section 4 |
dynamic | L24 | audio | (various) | 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000 orothers | Linear PCM24-bit audio | RFC 3190Section 4 |
RFC 3551 listsdetails of the codec,or a reference for the details is provided. Payload identifiers96–127 are reserved for payloads defined dynamically during asession. The minimum payload support is defined as 0 (PCMU) and 5(DVI4). The document recommends dynamically assigned port numbers,although 5004 and 5005 have been registered for use of the profileand can be used instead. The standard also describes the process ofregistering new payload types with IANA.
H.264 可以填(96).
1.2 源码分析
这部分代码比较好理解,实现主要在avprofle.c和 payloadtype.h,rtpprofile.h,rtpprofile.c中。
struct _PayloadType { int type; /**< one of PAYLOAD_* macros*/ int clock_rate; /**< rtp clock rate*/ char bits_per_sample; /* in case of continuous audio data */ char *zero_pattern; int pattern_length; /* other useful information for the application*/ int normal_bitrate; /*in bit/s */ char *mime_type; /**<actually the submime, ex: pcm, pcma, gsm*/ int channels; /**< number of channels of audio */ char *recv_fmtp; /* various format parameters for the incoming stream */ char *send_fmtp; /* various format parameters for the outgoing stream */ int flags; void *user_data; };
这里定义出PayloadType的基本的元素,具体变量的意义看代码注释。
变量 recv_fmtp/send_fmtp ,是用来保存SDP 的参数。
/** * The RTP profile is a table RTP_PROFILE_MAX_PAYLOADS entries to make the matching * between RTP payload type number and the PayloadType that defines the type of * media. **/ struct _RtpProfile { char *name; PayloadType *payload[RTP_PROFILE_MAX_PAYLOADS]; }; typedef struct _RtpProfile RtpProfile;
RtpProfile av_profile; 是一个全局的变量。
这里定义一个Payload的集合,然后调用rtp_profile_set_payload(...)函数,把格式话好的playload赋值到这个集合中,这是在用户调用ortp_init()时完成的。未初始化的可以调用rtp_profile_set_payload()来在应用层设置。
二、oRTP中的event和signal模块
2.1 Signal的实现
应用层可以调用 rtp_session_signal_connect(...)函数来绑定一个signal 和一个callback函数。当协议栈遇到某一事件时 就会调用rtp_signal_table_emit_x(...) 来向应用层报告。这里的实现思想也不难,主要就是回调函数的原理。
应用层 调用rtp_session_new(),那么就会调用rtp_session_init().在这个初始化函数中初始化了singnal_table。
在struct_RtpSession 结构体中定义了
RtpSignalTable on_ssrc_changed; RtpSignalTable on_payload_type_changed; RtpSignalTable on_telephone_event_packet; RtpSignalTable on_telephone_event; RtpSignalTable on_timestamp_jump; RtpSignalTable on_network_error; RtpSignalTable on_rtcp_bye; struct _OList *signal_tables; 分别表示RTP ssrc 变化,pt变化等事件的发生。 typedef void (*RtpCallback)(struct _RtpSession *, ...); struct _RtpSignalTable { RtpCallback callback[RTP_CALLBACK_TABLE_MAX_ENTRIES]; unsigned long user_data[RTP_CALLBACK_TABLE_MAX_ENTRIES]; struct _RtpSession *session; const char *signal_name; int count; };
这里需要注意 一个struct_OList*signal_tables;的变量,在调用rtp_signal_table_init()时会把这些signale table 加入到一个list当中,这是为了后面应用层在添加事件时方便查寻。
见代码:
int rtp_session_signal_connect (RtpSession * session, const char *signal_name, RtpCallback cb, unsigned long user_data) { OList *elem; for (elem=session->signal_tables;elem!=NULL;elem=o_list_next(elem)){ RtpSignalTable *s=(RtpSignalTable*) elem->data; if (strcmp(signal_name,s->signal_name)==0){ return rtp_signal_table_add(s,cb,user_data); } } ortp_warning ("rtp_session_signal_connect: inexistant signal %s",signal_name); return -1; }
函数rtp_session_signal_connect在 session->signal_tables中先根据signal_name来找到具体的一个RtpSignalTable,然后再在这个表中调节事件的回调函数。
2.2 Event 实现
Event的实现似乎和signal功能差不多,但实现却不一样,至于为什么这样实现,我们先不考虑,就看看作者到是是如何实现这一过程的吧。初看Event的实现有点搞不明白事件到底是如何发送和接受,尤其是时间如何被接受。后来结合linphone的应用程序才有点搞明白。
在 struct_RtpSession 的结构中有个成员变量struct_OList*eventqs;这个List用来存放OrtpEvQueue的头指针,结构如下图。
/**
* Register an event queue. * An application can use an event queue to get informed about various RTP events. **/ void rtp_session_register_event_queue(RtpSession *session, OrtpEvQueue *q){ session->eventqs=o_list_append(session->eventqs,q); } void rtp_session_unregister_event_queue(RtpSession *session, OrtpEvQueue *q){ session->eventqs=o_list_remove(session->eventqs,q); }
event的 OrtpEvQueue是在应用层定义,通过rtp_session_register_event_queue()注册到oRTP协议栈中。协议栈通过rtp_session_dispatch_event()向session->eventqs中的每一个OrtpEvQueue分发消息,应用层中,需要自己调用ortp_ev_queue_get()来获得消息,并处理。
消息类型有
/* type is one of the following*/ #define ORTP_EVENT_STUN_PACKET_RECEIVED 1 #define ORTP_EVENT_PAYLOAD_TYPE_CHANGED 2 #define ORTP_EVENT_TELEPHONE_EVENT 3 #define ORTP_EVENT_RTCP_PACKET_RECEIVED 4 /**<when a RTCP packet is received from far end */ #define ORTP_EVENT_RTCP_PACKET_EMITTED 5 /**<fired when oRTP decides to send an automatic RTCP SR or RR */ #define ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED 6 #define ORTP_EVENT_ZRTP_SAS_READY 7 #define ORTP_EVENT_ICE_CHECK_LIST_PROCESSING_FINISHED 8 #define ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED 9 #define ORTP_EVENT_ICE_GATHERING_FINISHED 10 #define ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED 11 #define ORTP_EVENT_ICE_RESTART_NEEDED 12
ORTP_EVENT_RTCP_PACKET_RECEIVED消息接收到后,应用层可以根据RTCP包做流量控制。
typedefmblk_tOrtpEvent;
消息体的结构就是个mblk_t的类型,这个结构块中存放了
typedefunsignedlongOrtpEventType;
和
struct _OrtpEventData{ mblk_t *packet; /* most events are associated to a received packet */ RtpEndpoint *ep; ortpTimeSpec ts; union { int telephone_event; int payload_type; bool_t zrtp_stream_encrypted; struct _ZrtpSas{ char sas[5]; // 4 characters bool_t verified; } zrtp_sas; OrtpSocketType socket_type; bool_t ice_processing_successful; } info; };
两个数据类型。
三、定时器RtpTimer
RtpTimer所涉及的文件有rtptimer.c posixtimer.crtptimer.h .
RtpTimer就是一个状态和函数指针的结构体。具体的函数在posixtimer.c中赋值。
//这里是个函数的指针类型; typedef void (*RtpTimerFunc)(void); struct _RtpTimer { int state; #define RTP_TIMER_RUNNING 1 #define RTP_TIMER_STOPPED 0 RtpTimerFunc timer_init; RtpTimerFunc timer_do; RtpTimerFunc timer_uninit; struct timeval interval; }; typedef struct _RtpTimer RtpTimer;
RtpTimer posix_timer; 这是个全局的变量作为协议栈的定时器。
/* 定义RtpTimer , 全局变量 */ RtpTimer posix_timer={ 0, posix_timer_init, posix_timer_do, posix_timer_uninit, {0,POSIXTIMER_INTERVAL}};
最需要具体理解下的就是posix_timer_do()函数,这个函数实现了精确的定时处理。
Timer的实现可以由下图表示:
posix_timer_time 是安自己的步调向前走,每次posix_timer_do()被调用,函数就会比较实际时间和posix_timer_time的差距,如果diff为 0,就表示时间到了,如何不为0,就利用 select(0,NULL,NULL,NULL,&tv);做延时。 对照下面的代码可以很好理解了。
void posix_timer_do() { int diff,time; struct timeval tv; gettimeofday(&cur,NULL); time=((cur.tv_usec-orig.tv_usec)/1000 ) + ((cur.tv_sec-orig.tv_sec)*1000 ); if ( (diff=time-posix_timer_time)>50){ ortp_warning("Must catchup %i miliseconds.",diff); } while((diff = posix_timer_time-time) > 0) { tv.tv_sec = diff/1000; tv.tv_usec = (diff%1000)*1000; #if defined(_WIN32) || defined(_WIN32_WCE) /* this kind of select is not supported on windows */ Sleep(tv.tv_usec/1000 + tv.tv_sec * 1000); #else select(0,NULL,NULL,NULL,&tv); #endif gettimeofday(&cur,NULL); time=((cur.tv_usec-orig.tv_usec)/1000 ) + ((cur.tv_sec-orig.tv_sec)*1000 ); } posix_timer_time+=POSIXTIMER_INTERVAL/1000; } 时间单位 秒 (s) second 毫秒(ms) millisecond 微秒 (us) microsecond 纳秒 (ns) 皮秒 (ps) 飞秒 (fs) 阿托秒(简称阿秒)(as)
四、RtpScheduler模块
RtpScheduler主要是在上一节中提到的RtpTimer的基础上实现的一个系统的调度处理,实现了类似linux的select()syscall 的功能。要明白RtpShcedule的工作原理,先看看几个重要的数据结构的关系。
调度器的工作看起来比较复杂,但慢慢分析还是可以理解里面的原理的。调度器是在一个void*rtp_scheduler_schedule(void* psched)的线程中实现的。他以一个恒定的速度调用rtp_session_process
来轮训所有的rtp_session。在这个rtp_session_process中又通过
struct { RtpProfile *profile; int pt; unsigned int ssrc; WaitPoint wp; int telephone_events_pt;/* the payload type used for telephony events */ } snd,rcv;
这个变量wp来控制 rtp_session_recvm_with_t和rtp_session_sendm_with_t 的速度。
对几个时间变量的理解:
(1)sched->time_:在调度器时间轴的时间值,从0 开始,安sched->timer_inc向前运行。
(2)session->rtp.snd_ts_offset:第一次发送数据的时间戳值;
(3)session->rtp.snd_time_offset:第一发送数据时的在调度器时间轴上的时间值;
session->rtp.snd_ts_offset和 session->rtp.snd_time_offset是为了计算packet_time,也就是把发送数据时的时间都统一到调度器的时间轴上来做比较(比较使用宏TIME_IS_STRICTLY_NEWER_THAN, 大于)。
图中T1-T0 = T2-T1 =Tn-T(n-1) 表示sched 在匀速的向前。
图中t 0,t1,t2表示packet_time的时间
图中黄色的部分为记录集的状态
sched 还通过 sched->unblock_select_cond变量来控制了 session_set_select的速度。 从以上分析可以看出调度器 分别控制着 数据发送和接受 和select 的速度。所有sched的颗粒度决定了 协议栈运行速度的快慢。
其他:
1. oRTP中UDP也使用了connectsyscall, 这样使得UDP的效率更高。
UDP调用CONNECT
在末连接UDP套接口上给两个数据报调用函数sendto导致内核执行下列六步:
1.连接套接口;
2.输出第一个数据报
3.断开套接口连接;
4.连接套接口,
5.输出第二个数据报;
6.断开套接口连接
已连接套接口发送两个数据报的导致内核执行如下步骤;
1.连接套接口;
2.输出第一个数据报;
3.输出第二个数据报。
对同一套接口发送时,耗时减少1/3