Doubango ims 框架 分析之 多媒体部分

序言

RTP提供带有实时特性的端对端数据传输服务,传输的数据如:交互式的音频和视频。那些服务包括有效载荷类型定义,序列号,时间戳和传输监测控制。应用程序在UDP上运行RTP来使用它的多路技术和checksum服务。2种协议都提供传输协议的部分功能。

RTP本身没有提供任何的机制来确保实时的传输或其他的服务质量保证,而是由低层的服务来完成。它不保证传输或防止乱序传输,它不假定下层网络是否可靠,是否按顺序传送数据包。RTP包含的序列号允许接受方重构发送方的数据包顺序,但序列号也用来确定一个数据包的正确位置,例如,在视频解码的时候不用按顺序的对数据包进行解码。

  1. 介绍

doubango框架中tinyRTP文件夹实现RTP/RTCP/RTSP协议栈,目前只实现了

RTP,RTCP;RTSP还没实现。Rtp用来在网络上传输音频视频,协议栈实现时主要在音视频包的封装,拆包。

  1. rtp包由消息头及消息体组成,消息头的结构封装

typedefstruct trtp_rtp_header_s 文件trtp_rtp_header.h

{

TSK_DECLARE_OBJECT;

/*RFC 3550 section 5.1 - RTP Fixed Header Fields

0 1 2 3

01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|V=2|P|X| CC |M| PT | sequence number |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| timestamp |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| synchronization source (SSRC) identifier |

+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

| contributing source (CSRC) identifiers |

| .... |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

*/

unsignedversion:2;

//版本(V):2比特 此域定义了RTP的版本。此协议定义的版本是2。(值1被RTP草案版本使用,值0用在最初"vat"语音工具使用的协议中。)

unsignedpadding:1;

//填充(P):1比特 若填料比特被设置,则此包包含一到多个附加在末端的填充比特,填充比特不算作负载的一部分。填充的最后一个字节指明可以忽略多少个填充比特。填充可能用于某些具有固定长度的加密算法,或者用于在底层数据单元中传输多个RTP包。

unsignedextension:1;

//扩展

//扩展(X):1比特 若设置扩展比特,固定头(仅)后面跟随一个头扩展。

unsignedcsrc_count:4;

unsignedmarker:1;

//标志位

unsignedpayload_type:7;

//负载类型,即承载的语音编码类型

//负载类型(PT):7比特 此域定义了负载的格式,由具体应用决定其解释。协议可以规定负载类型码和负载格式之间一个默认的匹配。其他的负载类型码可以通过非RTP方法动态定义。RTP发送端在任意给定时间发出一个单独的RTP负载类型;此域不用来复用不同的媒体流。

uint16_tseq_num;

//序列号,重新组包

//序列号(sequencenumber):16比特 每发送一个RTP数据包,序列号加1,接收端可以据此检测丢包和重建包序列。序列号的初始值是随机的(不可预测),以使即便在源本身不加密时(有时包要通过翻译器,它会这样做),对加密算法泛知的普通文本攻击也会更加困难。

uint32_ttimestamp;

//时间戳,负责流同步

uint32_tssrc;

//同步源标识,32比特 用以识别同步源。标识符被随机生成,以使在同一个RTP会话期中没有任何两个同步源有相同的SSRC识别符。尽管多个源选择同一个SSRC识别符的概率很低,所有RTP实现工具都必须准备检测和解决冲突。若一个源改变本身的源传输地址,必须选择新的SSRC识别符,以避免被当作一个环路源。

uint32_tcsrc[15];

//贡献源标识

}

trtp_rtp_header_t;

  1. rtp包结构,文件trtp_rtp_packet.h

typedefstruct trtp_rtp_packet_s

  1. {
  2. TSK_DECLARE_OBJECT;
  3. trtp_rtp_header_t*header; //包头
  4. struct{
  5. void*data;
  6. constvoid* data_const;
  7. tsk_size_tsize;
  8. }payload; //负载,即承载内容
  9. /*extension header as per RFC 3550 section 5.3.1 */
  10. struct{
  11. void*data;
  12. tsk_size_tsize; /* contains the first two 16-bit fields */
  13. }extension;
  14. }
  15. trtp_rtp_packet_t;
  1. rtp包的控制

上面两个结构用来标示一个rtp包,同时提供了包的解析,创建等函数。

结构trtp_manager_s负责rtp.rtcp包的管理,是更高层的抽象,上层应用直接通过trtp_manager_s提供的api控制

rtp包,比如在网络上发送音频数据,在音频session结构中包含trtp_manager_s用来管理经过封装的rtp包。

/**RTP/RTCP manager */

typedefstruct trtp_manager_s

{

TSK_DECLARE_OBJECT;

struct{

uint16_tseq_num;

uint32_ttimestamp;

uint32_tssrc;

uint8_tpayload_type;

char*remote_ip;

tnet_port_tremote_port;

structsockaddr_storage remote_addr;

char*public_ip;

tnet_port_tpublic_port;

constvoid* callback_data;

trtp_manager_rtp_cb_fcallback;

}rtp;

struct{

char*remote_ip;

tnet_port_tremote_port;

structsockaddr_storage remote_addr;

tnet_socket_t*local_socket;

char*public_ip;

tnet_port_tpublic_port;

constvoid* callback_data;

trtp_manager_rtcp_cb_fcallback;

}rtcp;

char*local_ip;

tsk_bool_tipv6;

tsk_bool_tstarted;

tsk_bool_tenable_rtcp;

tsk_bool_tsocket_disabled;

tnet_transport_t*transport;

}

trtp_manager_t;

  1. tdav是音视频会话的抽象层,负责传输层的启动,音频会话,视频会话,各种编码的注册。

对于音频/视频会话(session)被tmedia_session_mgr_t管理,而tmedia_session_mgr_t则具体由sip信令控制会话的状态。比如sip客户端请求时通过tmedia_session_mgr_t构造自己的sdp信息要借助此结构,当客户端对invite作ACK应答时同样要指定自己的媒体信息。整个rtp流的启动入口都由tmedia_session_mgr_t控制。

各种媒体会话以插件的形式注册,如音频会话在启动时注册到tmedia_session_mgr_t的插件链表,并绑定start,stop,prepare回调。tmedia_session_mgr_t为sip信令控制媒体流的接口。

tmedia_session_plugin_def_t为音频视频抽象接口,指定回调。如音频会话,内部会实现相应的回调函数。

/**Virtual table used to define a session plugin */

typedefstruct tmedia_session_plugin_def_s

{

//!object definition used to create an instance of the session

consttsk_object_def_t* objdef;

//!the type of the session

tmedia_type_ttype;

//!the media name. e.g. "audio", "video", "message","image" etc.

constchar* media;

int(*set) (tmedia_session_t* , const tmedia_param_t*);

int(* prepare) (tmedia_session_t* );

int(* start) (tmedia_session_t* );

int(* pause) (tmedia_session_t* );

int(* stop) (tmedia_session_t* );

struct{/* Special case */

int(* send_dtmf) (tmedia_session_t*, uint8_t );

}audio;

consttsdp_header_M_t* (* get_local_offer) (tmedia_session_t* );

/*return zero if can handle the ro and non-zero otherwise */

int(* set_remote_offer) (tmedia_session_t* , const tsdp_header_M_t* );

}

tmedia_session_plugin_def_t;

tmedia_session_t为会话的抽象层,包含tmedia_session_plugin_def_t,

/**Base objct used for all media sessions */

typedefstruct tmedia_session_s

{

TSK_DECLARE_OBJECT;

//!unique id. If you want to modifiy this field then you must use @reftmedia_session_get_unique_id()

uint64_tid;

//!session type

tmedia_type_ttype;

//!list of codecs managed by this session

tmedia_codecs_L_t*codecs;

//!negociated codec

tmedia_codecs_L_t*neg_codecs;

//!whether the ro have been prepared (up to the manager to update thevalue)

tsk_bool_tro_changed;

//!whether the session have been initialized (up to the manager toupdate the value)

tsk_bool_tinitialized;

//!whether the session have been prepared (up to the manager to updatethe value)

tsk_bool_tprepared;

//!QoS

tmedia_qos_tline_t*qos;

//!bandwidth level

tmedia_bandwidth_level_tbl;

struct{

tsdp_header_M_t*lo;

tsdp_header_M_t*ro;

}M;

//!plugin used to create the session

conststruct tmedia_session_plugin_def_s* plugin;

}

tmedia_session_t;

sipsession

trtp_manager_t

tmedia_session_t tmedia_session_plugin_def_s

使用过程:

tdav_init注册音频,视频,多媒体session;注册支持的编码类型,注册支持的媒体信息承载类型(文本,流等)。

tdav_init-> register sessions, codecs.

tmedia_session_mgr_create-> tmedia_session_mgr_ctor ,sessions,qos,sdp.

_tmedia_session_mgr_load_sessions,创建音视频会话。

tmedia_session_create,创建具体会话插件类型,tdav_session_video/audio_ctor

tmedia_session_init,初始化

tmedia_session_load_codecs,此会话支持的编码类型

tmedia_codec_create ,穿件具体编码类型。

创建过程

准备阶段

trtp_manager_prepare,指定传输层接收数据回调trtp_transport_layer_cb

tdav_session_audio_prepare,trtp_manager_create,trtp_manager_set_rtp_callback

tnet_transport_create

tnet_transport_set_callback

启动

tmedia_session_mgr_start(),启动所有上面创建的会话类型,启动之前一定要设置sdp信息

(Startsthe session manager by

startingall underlying sessions.You should set

bothremote and local offers before calling this

session->plugin->start(),如视频会话启动 ,tdav_session_video/audio.c

trtp_manager_set_rtp_remote, 设置对端ip,port,后续发送rtp包时构造包头用

trtp_manager_set_payload_type,设置此次会话用什么编码类型,编码类型通过协商后选择最佳

trtp_manager_start,启动rtp,rtcp包管理,

tnet_transport_start,启动传输层线程,绑定socket地址,开始接收udp数据, tnet_transport_mainthread

请求或响应中sdp与codec匹配过程

tmedia_session_match_codec->tmedia_codec_match_fmtp->tdav_codec_h264_fmtp_match->

tdav_codec_h264_get_profile(根据fmt获取对方的profile版本),

当发起外乎请求时codec与sdp处理关系,

发起invite或对方更改媒体信息时要把codec信息加载到sdp消息体中,

对于video,audio过程是一样的。

(videosession from codecs to sdp)

tdav_session_video_get_lo

|

tsdp_header_M_create(创建sdp媒体头)

|

tmedia_session_match_codec(此函数最终会返回一个协商成功的编码类型)

对于h264编码格式,此函数内部调用过程,遍历协议栈初始化时指定的编码链表,用此次请求的sdp消息体中的编码与自己的编码链表比较。->tmedia_codec_match_fmtp->tdav_codec_h264_fmtp_match->

tdav_codec_h264_get_profile

tmedia_session_match_codec返回协商成功的编码列表(即双方都支持的编码类型列表)后复制给协议栈,

self->neg_codecs= tmedia_session_match_codec

然后调用tmedia_codec_video_set_callback设置此编码类型对应的回调函数,当想发送rtp包时直接触发此回调函数即可完成发送rtp包的任务。

tmedia_codec_video_set_callback((tmedia_codec_video_t*)TSK_LIST_FIRST_DATA(self->neg_codecs),tdav_session_video_raw_cb, self);

tdav_session_video_raw_cb为具体的毁掉函数,内部为调用trtp_manager_send_rtp,发送rtp包。

值得注意的是传给函数的tdav_session_video_raw_cb数据只是未经过加工成rtp包的裸数据,tdav_session_video_raw_cb内部调用trtp_manager_send_rt,由trtp_manager_send_rt来把数据加工成rtp包,

然后调用传输层发送到网络上。

/*Encapsulate raw data into RTP packet and send it over the network

*Very IMPORTANT: For voice packets, the marker bits indicates thebeginning of a talkspurt */

inttrtp_manager_send_rtp(trtp_manager_t* self, const void* data,tsk_size_t size, uint32_t duration, tsk_bool_t marker, tsk_bool_tlast_packet)

trtp_manager_send_rtp内部又具体调用trtp_rtp_packet_create,创建rtp格式的数据包,包括rtp消息头的创建,初始化默认参数(version,marker,payload_type,seq_num等)。然后调用trtp_rtp_packet_serialize把rtp包序列化到一个buffer中。

trtp_manager_send_rtp最后调用tnet_sockfd_sendto传输层函数完成实际发送到网络上。

回到设置tmedia_codec_video_set_callback完毕后,tdav_session_video_get_lo调用tmedia_codec_to_sdp

把协商后的编码类型的信息转换成sdp格式的信息。

tmedia_codec_to_sdp(self->neg_codecs,self->M.lo); 保存到M.lo属性,即本地的媒体信息。

tmedia_codec_to_sdp分析:

此函数的功能即把协商后的编码链表放到协议栈的sdp属性中,这样以后发送invite请求时就可以直接用。

/**@ingrouptmedia_codec_group

*Serialize a list of codecs to sdp (m= line) message.<br>

*Will add: fmt, rtpmap and fmtp.

*@param codecs The list of codecs to convert

*@param m The destination

*@retval Zero if succeed and non-zero error code otherwise

*/

inttmedia_codec_to_sdp(const tmedia_codecs_L_t* codecs, tsdp_header_M_t*m)

TSK_DEBUG_INFO("Serializea list of codecs to sdp (m= line) message/n");

tsk_list_foreach(item,codecs){

遍历每个编码类型,添加fmt,rtpmap属性,fmtp属性(tmedia_codec_get_fmtp,对于h264格式即调用tmedia_codec_h264_get_fmtp)

最后,tdav_session_video_get_lo内部在属性M.ro(即已经有请求的sdp信息)非空时考虑此请求是否为

保持还是接回,通过设置spd属性,sendrecv,sendonly来提示类型。最后,设置Qos信息。

流程tdav_session_video_get_lo

|

tsdp_header_M_create(创建sdp媒体头)

|

tmedia_session_match_codec

|

tmedia_codec_video_set_callback

|

tmedia_codec_to_sdp

但是

tdav_session_video_get_lo又是由谁触发的呢?tdav_session_video_get_lo为某一具体session的回调,

比如视频的session回调,音频的回调,视频,音频的session以plugin的方式挂在到session中。

/**Virtual table used to define a session plugin */

typedefstruct tmedia_session_plugin_def_s

{

//!object definition used to create an instance of the session

consttsk_object_def_t* objdef;

//!the type of the session

tmedia_type_ttype;

//!the media name. e.g. "audio", "video", "message","image" etc.

constchar* media;

int(*set) (tmedia_session_t* , const tmedia_param_t*);

int(* prepare) (tmedia_session_t* );

int(* start) (tmedia_session_t* );

int(* pause) (tmedia_session_t* );

int(* stop) (tmedia_session_t* );

struct{/* Special case */

int(* send_dtmf) (tmedia_session_t*, uint8_t );

}audio;

consttsdp_header_M_t* (* get_local_offer) (tmedia_session_t* );

/*return zero if can handle the ro and non-zero otherwise */

int(* set_remote_offer) (tmedia_session_t* , const tsdp_header_M_t* );

}

tmedia_session_plugin_def_t;

tdav_session_video_get_lo即为get_local_offer的具体回调。

get_local_offer被tmedia_session_get_lo调用。tmedia_session_get_lo又被tmedia_session_mgr_get_lo】

调用,正是上面提到的tmedia_session_mgr为管理session的抽象接口,用来与sip信令交互。

整个流程为:

tmedia_session_mgr

|

tmedia_session_get_lo

|

tdav_session_video_get_lo

|

tsdp_header_M_create(创建sdp媒体头)

|

tmedia_session_match_codec

|

tmedia_codec_video_set_callback

|

tmedia_codec_to_sdp

tmedia_session_mgr_get_lo又被谁触发呢?

刚才说了,是由sip协议栈调用的,具体有这样几个与sdp协商有关的sip点,我们知道,invite请求以及200ok应答,183响应,100响应的确认(prack)中有sdp信息:

(1)发送或者更新请求(invite)

send_INVITEorUPDATE

//send INVITE/UPDATE request

intsend_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE,tsk_bool_t force_sdp)

  1. prack响应

//Send PRACK

intsend_PRACK(tsip_dialog_invite_t *self, const tsip_response_t* r1xx)

  1. //Send ACK

intsend_ACK(tsip_dialog_invite_t *self, const tsip_response_t*r2xxINVITE)

初始请求中没有sdp信息,在ack中需要携带sdp信息

(4)发送响应时

/Send any response

intsend_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t*request, short code, const char* phrase, tsk_bool_t force_sdp)

2.处理请求中的sdp信息过程

tsip_dialog_invite_process_ro

|

tmedia_session_mgr_set_ro

tsip_dialog_invite_process_ro为sip信令中处理sdp信息的入口,在状态机的回调中适时调用

。比如在保持状态转到接回状态。

tsip_dialog_invite_process_ro会初始化mgr,启动,

tmedia_session_mgr_create,tmedia_session_mgr_set_ro,tmedia_session_mgr_set_natt_ctx,

tmedia_session_mgr_start

时间: 2024-10-25 02:32:12

Doubango ims 框架 分析之 多媒体部分的相关文章

Google Test测试框架分析

Google Test测试框架分析 一.简介 Google Test是由Google主导的一个开源的C++自动化测试框架,简称GTest.GTest基于xUnit单元测试体系,和CppUint类似,可以看作是JUnit.PyUnit等对C++的移植. 下图是GTest测试框架的测试过程,表示的是GTest的两种测试方式. 下面将使用一个极其简单的例子表示xUnit测试的主要过程.如对Hummer的CTXString类的成员方法GetLength进行测试.详见下面GTest代码和注释说明. //

Android Bitmap 开源图片框架分析(精华三)

主要介绍这三个框架,都挺有名的,其他的框架估计也差不多了 Android-Universal-Image-Loaderhttps://github.com/nostra13/Android-Universal-Image-Loader ImageLoaderhttps://github.com/novoda/ImageLoader Volley(综合框架,包含图片部分)https://github.com/mcxiaoke/android-volley 扯淡时间,可以跳过这段这些开源框架的源码还

Android Bitmap 开源图片框架分析(精华四)

disk缓存主要难点在于内存缓存,disk缓存其实比较简单,就是图片加载完成后把图片文件存到本地方便下次使用 同样,先贴一下官方主页的介绍(主页地址见文章最开始处)和内存缓存差不多,根据算法不同提供了几种类别,可以自行通过ImageLoaderConfiguration.discCache(..)设置<ignore_js_op> 硬盘缓存,保存是以文件的形式框架提供了4种类型,具体算法规则不同,看名字我们大概也能知道对应意思 UnlimitedDiscCache                

Linux输入子系统框架分析(1)

在Linux下的输入设备键盘.触摸屏.鼠标等都可以用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层,事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备驱动层将输入事件上报给核心层input.c,核心层找到匹配的事件层,将事件交给事件层处理,事件层处理完后传递到用户空间. 我们最终要搞清楚的是在用户空间调用open和read最终在内核中是怎样处理的,向内核上报的事件又是谁处理的,处理完后是怎样传递到用户空间的? 上面两个图是输入子系统的框架. 下面

TI BLE协议栈软件框架分析

看源代码的时候,一般都是从整个代码的入口处开始,TI  BLE 协议栈源码也不例外.它的入口main()函数就是整个程序的入口,由系统上电时自动调用. 它主要做了以下几件事情: (一)底层硬件初始化配置 (二)创建任务并初始化任务配置 (三)检测并执行有效的任务事件 Main() 函数源码如下: 一:底层硬件初始化设置 75行,设置系统时钟,使能内存缓冲功能. 78行,关中断,刚启动时,系统运行不稳定,一般会首先关中断. 81行,硬件相关的I/O 口配置. 84行,初始化mcu 内部的flash

开源学习--SlideExpandableListView中的列表项动画实现框架分析

前面的话 开源项目Android-SlideExpandableListView是一个简单的介绍列表项动画展示的小型项目,分析这个项目可以对自定义框架及列表类动画实现有个比较清晰的认识,工作中中时常根据需求扩展定义自己的适配器,虽然具体需求不同,但架构类似,本文把最近关于该开源项目的研究心得整理分享,共同学习~ 项目简介 github地址https://github.com/tjerkw/Android-SlideExpandableListView 这是个入门级的列表项动画展示框架,实现效果如

YII框架分析笔记2:组件和事件行为管理

Yii是一个基于组件.用于开发大型 Web 应用的高性能 PHP 框架.CComponent几乎是所有类的基类,它控制着组件与事件的管理,其方法与属性如下,私有变量$_e数据存放事件(evnet,有些地方叫 hook),$_m数组存放行为(behavior). 组件管理 YII是一个纯oop框架,很多类中的成员变量的受保护或者私有的,CComponent中利用php中的魔术方法__get(),__set()来访问和设置属性,但这些方法的作用远不指这些.下面用__get()来说明 [php] vi

jQuery源码 框架分析

每一个框架都有一个核心,所有的结构都是基于这个核心之上,结构建立好了之后,剩下的就是功能的堆砌. jQuery的核心就是从HTML文档中匹配元素并对其操作. 就跟一座大楼一样,让我们一步一步了解这座大厦的基石和结构. 1.构造函数 2.链式语法 3.选择器 4.扩展性  一.构造函数 我们知道类是面向对象编程的一个重要概念,它是对事物的最高抽象,它是一个模型.通过实例化一个类,我们可以创建一个实例. javascript本身没有类的概念,只有原型prototype,prototype是什么呢?它

Linux USB驱动框架分析 【转】

转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了.好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动开发.但这次只先针对Linux的USB子系统作分析,因为周五研讨老板催货.当然,还会顺带提一下其他的驱动程序写法. 事实上,Linux的设备驱动都遵循一个惯例——表征驱动程序(用driver更