转载自:http://blog.csdn.net/lickylin/article/details/19713317
1、upnp 介绍及工作过程
1.1 upnp介绍
UPnP的全称是“Universal Plug and Play”,是PnP(Device Plug and Play)的扩展,可以发现和控制各种网络设备,并能提供相应的服务,如网络打印机,Internet网关等。但UPnP不仅仅是即插即用的简单扩展,它支持“0配置”和无线网络,可以自动发现其它供应商提供的设备。在UPnP协议下,一个设备可以动态的加入网络、获得IP地址、广播它的功能、并了解其他设备的功能。很多种类的设备都可以使用UPnP协议,包括智能设备,无线设备。UPnP使用标准的TCP/IP和Internet协议,使其能够很好的在现存网络中使用,使用这些标准协议使得UPnP可以从已有的各种经验和知识中获利,打破了各种信息孤岛,越过各种物理层,可以是有线的,也可以是无线的,使得设备间的相互协作成为一个基本的特点。
Upnp实用internet上的多种标准:IP、TCP、UDP、HTTP及XML作为设备之间通信的协议。其中XML是upnp的核心部分,被用在设备与服务描述、控制信息与事件处理中。
Upnp设备的工作过程包括设备寻址、设备发现、设备描述、设备控制、事件通知、设备表征6个部分。控制点至少应该包括发现、描述、命令转换等。设备寻址是当设备加入网络后,通过DHCP服务器或者Auto IP获得唯一的IP地址。
1.2 upnp工作过程
Upnp的工作过程包括:设备寻址、设备发现、设备描述、设备控制、事件通知、设备表征。
0. 控制点跟设备都先取得IP地址才能做之后的沟通。
1. 控制点发送一个多播m-http寻找整个网络上的UPnP设备,而设备也可宣告他本身的存在。
2. 控制点取得设备与服务的描述,这包括设备提供什么样的服务。
3. 根据设备提供的服务,控制点发出动作信息 (对设备操作的命令信息)给设备。
4. 控制点监听设备的状态,当状态改变时做出相应的处理动作。
5. 控制点利用HTML界面来控制设备和监看设备状态。
2、upnp av的工作过程
一个完整的upnp av体系由三部分组成:控制点(control point)、设备(media server)、媒体播放器(media renderer)。
控制点提供用户控制的界面,并协调服务器与播放器之间的相互操作;媒体服务器提供内容目录、连接管理与内容传输等服务;播放器主要用来播放数据。下面是三者之间的联系:
(图1 UPnP AV应用框架)
注意:从媒体服务器向媒体播放器传输媒体流所用的协议是与控制点无关的。而且,媒体流也不经过 AV 控制点,AV 控制点只负责连接的建立。
2.1 upnp av控制点
AV控制点用来控制媒体服务器与媒体播放器的操作,使用户可以在一个指定的upnp播放设备中播放指定upnp服务器中的媒体文件。在大多应用场合,upnp av控制点执行的操作有:
①发现网络中存在的媒体服务器与媒体播放器,即设备发现
②列出用户可以操作的媒体文件,即内容浏览
③查询媒体服务器与媒体播放器,找出所选媒体文件的传输协议与媒体格式,协商媒体服务器与媒体播放器都支持的传输协议与媒体格式
④调整播放器的属性,如音量、屏幕亮度等,即播放器属性控制
控制点通过调用媒体服务器与媒体播放器的upnp av服务来完成上述操作,从而根据用户的需求完成相应的操作。
2.2 媒体服务器
媒体服务器用来管理要共享的媒体文件,并将内容传输给媒体播放器进行播放。
媒体服务器提供的服务有:
内容目录服务:控制点通过内容目录服务可以浏览、查找共享的媒体数据文件;
连接管理服务:连接管理服务提供媒体服务器所支持的数据传输协议与媒体数据格式;
传输服务(可选):当支持的传输协议是http-get时,由媒体播放器执行av传输服务,提供数据流的控制,当支持的传输协议是http-post时,由媒体服务器执行av传输服务,提供数据流的控制。Ushare使用的传输协议是http-get,所以在其代码中没有实现av传输服务。
2.3 媒体播放器
媒体播放器用来接收媒体服务器所共享的媒体数据,并在本地播放,媒体播放器包括电视、音响、电子图片显示器等。
媒体播放器提供的upnp av服务有:
播放控制:控制点通过播放控制服务控制如何播放媒体文件(音量调节、屏幕亮度调节等);
连接控制:连接管理服务与媒体服务器相似,提供媒体播放器所支持的媒体传输协议与媒体数据格式;
传输服务:当其支持的媒体数据传输协议是http-get时,则必须要有传输服务,当支持的媒体数据传输协议是http-post时,则由媒体服务器来实现传输服务,媒体播放器可以不提供传输服务。
3、媒体服务器ushare的工作架构
3.1 相关的结构体
对于媒体服务器ushare,其工作过程如下:
Ushare有关的结构体:
struct ushare_t {
char *name;
char *interface;//eth0、eth1
char *model_name;
content_list *contentlist;//共享内容目录
struct rbtree *rb;//平衡二叉树
struct upnp_entry_t *root_entry;
int nr_entries;//
int starting_id;//
int init;
UpnpDevice_Handle dev;//传递给upnp的handler
char *udn;
char *ip;//ip地址
unsigned short port;
unsigned short telnet_port;
struct buffer_t *presentation;
bool use_presentation;
bool use_telnet;
#ifdef HAVE_DLNA
bool dlna_enabled;
dlna_t *dlna;
dlna_org_flags_t dlna_flags;
#endif
bool xbox360;
bool verbose;
bool daemon;
bool override_iconv_err;
char *cfg_file;
pthread_mutex_t termination_mutex;
pthread_cond_t termination_cond;
};
struct upnp_entry_t {
int id;
char *fullpath; //内容目录
#ifdef HAVE_DLNA
dlna_profile_t *dlna_profile;
#endif
struct upnp_entry_t *parent;
int child_count;//判断是文件还是文件夹(0:文件夹 -1:文件)
struct upnp_entry_t **childs;
struct mime_type_t *mime_type;//媒体数据格式与协议数据
char *title;//内容目录的最后一个'/'后面的内容
char *url;//id.ext example:32768.avi
off_t size;
int fd;//
};
该结构体主要用于内容目录有关的函数中.
struct mime_type_t {
char *extension;//数据格式 ex:wmv、rmvb、mp3、mp4等
char *mime_class;
char *mime_protocol;
};
该结构体主要用于媒体格式与数据传输协议相关的函数中.
3.2 ushare代码架构
Ushare程序的开发流程与upnp官方文档中所规定的基本步骤是一致的,
①首先是调用upnp库接口函数init_upnp(),通过upnp库的接口函数UpnpInit()初始化一个upnp,并调用函数UpnpSetVirtualDirCallbacks注册http协议有关的回调函数get_info、open、read、write、close,这些函数用于媒体播放器与媒体服务器建立连接与进行数据的传输
②然后调用upnp库的接口函数UpnpRegisterRootDevice2()注册ushare设备的文档信息与事件响应回调函数device_callback_event_handler 。ushare注册的设备描述文档用于控制点的设备发现阶段,当控制点发现ushare媒体服务器后,就会得到ushare的描述文档,该描述文档描述了设备的名称和支持的服务信息(ushare支持的服务:ConnectionManager 、ContentDirectory 、
X_MS_MediaReceiverRegistrar),控制点根据设备所提供的服务,就可以向upnp设备发送请求相关服务的事件(主要通过回调函数函数
device_callback_event_handler实现)
③ushare提供的ConnectionManager服务,主要是用来获取ushare所支持的媒体数据格式与传输协议,控制点通过该服务获取媒体服务器和媒体播放器的连接管理服务来确定进行数据传输的协议与都支持的数据格式。
④ushare提供的ContentDirectory 服务,主要用来确定在媒体服务器上共享的资源,该服务提供目录浏览与目录搜素cds_browsecds_search(),媒体播放器获得了ushare共享的资源的url后,就可以通过http协议来获取相应数据了,这主要通过 UpnpSetVirtualDirCallbacks注册的回调函数get_info、open、read、write、close来实现。
(图2 ushare main() 总体框架)
3.3 ushare的设备描述文档与服务描述文档
Ushare是一个媒体服务器,它需要设定自己的媒体描述文档与服务描述文档,以便在网络上发布或者被控制点在设备发现阶段获得。控制点获得了媒体服务器的设备描述文档与服务描述文档后,就可以通过事件请求向媒体服务器发送命令请求,获得媒体服务器支持的服务。Ushare是在init_upnp()函数里来将设备描述文档注册到upnp中去的。
(图3 ushare注册设备描述文档流程)
设备描述文档是以XML形式编写的,下面是ushare的设备描述文档,
在ushare的描述里,有ushare的名称、编写者、ushare所支持的服务,控制点获得了ushare的设备描述文档后,就能了解ushare主要支持哪些服务了,如果控制点要获得关于服务更详细的内容,就需要去获得关于服务的文档信息。主要元素有:
:包含所有描述根设备的其它元素如:、、
包括、指示upnp设备的主版本、副版本
包括设备类型、名称、制造者等
包括upnp设备提供的所有服务列表
Ushare所支持的服务的描述文档中会列出动作列表action,动作列表用来表示ushare的服务所支持的动作。服务描述文档的每个元素介绍如下:
动作列表,用于定义动作;
每一个动作的具体定义,包括动作名称、
仅当为动作定义参数时要求,用于说明与一个动作有关的所有变量
每一个变量的具体说明,包括变量名、、
要求。无论变量是输入还是输出参数。必须采用in xorout 格式。
任何 in变量必须列在任何 out 变量的前面。
要求。必须是一个状态变量的名称。
包含以下定义:
sendEvents 属性将定义当这一状态变量的值
发生变化时是否生成事件消息
要求:状态变量的名称
string、i4、i2、int等
列举合法的字符串值
字符串变量的合法值
为合法数值定义范围,有、两个子
元素
当控制器获得ushare服务的详细描述文档后,就获得了ushare所支持的动作命令,然后可以根据ushare提供的事件响应回调函数device_callback_event_handler向ushare发送动作命令。
3.4 ushare提供的服务
Ushare支持的服务有ConnectionManager、ContentDirectory。控制点可以通过向ushare发出动作命令请求,来获得ushare的相关服务,下面是ushare的动作响应过程:
图4 ushare的动作响应过程
当有一个来自控制点的动作请求后,回调函数device_callback_event_handler()就会调用handle_action_request()来进行处理,然后handle_action_request()就会调用find_service_action()来查找ushare程序是否支持该动作请求,若支持就会调用数组services->actions[i].function()执行请求动作的回应。
3.4.1 ConnectionManager服务及其响应函数
连接管理服务主要用来向控制点提供ushare所支持的传输协议与数据格式,以便控制点对服务器与播放器之间的传输协议与数据格式进行匹配,来确定服务器与播放器之间传输时所使用的传输协议,下面是ConnectionManager服务所支持的动作响应函数。比较重要的服务是cms_get_protocol_info()
Ushare支持的传输协议为http-get,支持的数据格式为avi、rmvb、mp3、mp4、wma、wmv等,下面是部分ushare支持的数据格式与传输协议定义:
3.4.1.1 动作响应函数cms_get_protocol_info()
图5 cms_get_protocol_info
该函数的作用是获取ushare所支持的传输协议与数据格式,该函数首先会遍历数组const struct mime_type_t MIME_Type_List[],并将ushare所支持的传输协议与数据格式传递给字符指针resptext,然后通过函数upnp_add_response()调用libupnp库的接口函数UpnpAddToActionResponse()添加到动作回应中。Ushare支持的传输协议为http-get。
3.4.2 ContentDirectory服务及其响应函数
ContentDirectory服务中比较重要的两个动作响应函数为cds_search、cds_browse
它们提供内容目录的查找与浏览功能。下面是该服务提供的全部动作响应函数:
对于内容目录服务,要用到以下结构体:
该结构体中的fullpath存储的是ushare共享目录的地址;child_count用来判断一个upnp_entry_t类型的变量指向的是一个共享目录中的一个文件还是一个文件夹,0表示是一个文件夹,-1表示是一个数据文件;title指的是一个目录地址的最后一个'/'后面的内容,如对于目录/home/film,则title = film;url指的是id+媒体格式,如id.avi、id.rmvb等。
3.4.2.1 动作响应函数cds_browse()
控制点通过该函数来获取ushare中共享目录下的目录列表或者其子目录下的目录列表,以便控制点来根据目录列表请求响应的数据文件。
图 6 cds_browse
该事件响应的是一个xml文档,下面是它的一个示例:其中蓝色是指浏览的是一个文件时所需要填充的内容,黄色是指浏览的是一个文件夹时需要填充的内容
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/">
upnp:class dc:title
...
upnp:class dc:title
3.4.2.2 动作响应函数cds_search
控制节点通过该函数来搜索一个共享数据文件夹下的所有文件。该动作响应调用upnp_add_response()将一个xml形式的应答文档传送给sdk,该xml文档的内容与3.4.2.1中的内容大致是相似的。
①cds_search()首先会从控制点传来的事件请求变量Upnp_Action_Request中读取与搜索有关的值,包括index、count、id、search_criteria和filter。②然后根据读取的id值,调用函数upnp_get_entry (ut, id)得到要搜索目录的结构体;③接着调用函数cds_search_directchildren (event, out, index, count, entry, filter, search_criteria)开始搜索,1)若搜索的子节点是一个数据文件,则会调用函数matches_search (search_criteria, *childs)来判断该文件是否符合搜索条件,若符合搜索条件则调用didl_add_item()将其放入out->buf中2)若搜索的子节点是一个目录,则调用递归函数cds_search_directchildren_recursive,搜索该目录下所有的数据文件,并与搜索条件进行匹配,若符合搜索条件则调用didl_add_item()将其放入out->buf中;out->buf中存放的是xml形式的搜索结果;④最后调用upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf)将事件应答传递给upnp。
下面是该函数的流程图:
(图7 cds_search)
3.5 ushare生成的目录列表
生成目录列表时会用到以下结构体
该结构体中的fullpath存储的是ushare共享目录的地址;child_count用来判断一个upnp_entry_t类型的变量指向的是一个共享目录中的一个文件还是一个文件夹,0表示是一个文件夹,-1表示是一个数据文件;title指的是一个目录地址的最后一个'/'后面的内容,如对于目录/home/film,则title = film;url指的是id+媒体格式,如id.avi、id.rmvb等。
在ushare的main()里,当进行了upnp的初始化、注册了事件响应回调函数与数据传输的回调函数后,就是调用build_metadata_list (ut)生成内容目录列表。
build_metadata_list()的执行过程如下:
①调用程序upnp_entry_new (ut, "root", NULL, NULL, -1, true)构建ut->root_entry 根节点;
②然后通过for循环,对共享的每个目录进行遍历,从共享目录中添加文件;
③对其中的一个共享目录,构建以root_entry为父节点的entry;
④接着调用upnp_entry_add_child()将entry作为root_entry的子节点加入到ut->rb数据结构中(平衡二叉树);
⑤调用metadata_add_container()将entry目录下的所有子目录和子数据进行添加文件操作。
在metadata_add_container()里,
1)首先调用scandir 获得entry目录下的所有子目录或者子文件的名称;
2)对于entry的所有子文件,将会调用metadata_add_file()完成文件添加操作,对于entry的所有子目录,将会递归调用metadata_add_container();
⑥通过以上各步和递归函数,就实现了将共享目录下的所有数据文件都添加到ut->rb数据结构中。
下面是build_metadata_list的流程图
(图8 build_metadata_list)
3.6 ushare支持的数据传输
对于ushare,其支持的传输协议为http-get,在upnp av中媒体流的传输是不经过控制点的,而是在媒体服务器与媒体播放器之间进行的。
下图9中是upnp av体系中控制点、媒体服务器、媒体播放器的建立过程。在upnp av中,控制点的作用是发现网络上存在的媒体服务器,获得媒体服务器支持的服务与事件;然后获取媒体服务器与媒体播放器支持的数据传输协议与格式列表,通过对比确定双方都支持的传输协议与数据格式,然后通过媒体播放器提供的媒体传输服务就可以对数据流进行控制。因为ushare使用的http-get传输协议,其定义了关于http-get协议的数据流操作函数open、read、write、seek、get_info、close。
(图9 upnp av体系的交互过程)
与http协议操作函数有关的结构体:
在该结构体中的detail成员中的memory成员用于FILE_MEMORY类型的文件;而local成员用于FILE_LOCAL类型的文件,即需要传输的媒体数据文件,local.fd即打开的文件句柄,entry为该文件的upnp_entry_t结构体,其中包含文件的完整路径信息。
3.6.1 http_open()
该函数的作用有两个
1):从内存中打开一个媒体服务描述文档;
2)从共享目录中打开一个数据文件。
该函数返回的是一个web_file_t类型的结构体变量,该结构体会记录已打开的文件的路径、文件的类型等。将该结构体变量传递http_read()后,就可以读取文件信息了。对于文件类型是FILE_LOCAL的媒体数据文件,将会对entry.detail.local.fd进行赋值,在http_read()里调用read()时就会传递该变量,读取媒体数据流;对于文件类型是FILE_MEMORY的媒体服务描述文档,将会对entry.detail.memory.contents进行赋值,将媒体服务描述文档的内容赋值给它。
下图是http_open的流程图
3.6.2 http_read()
该函数的作用是读取媒体数据流或者媒体服务描述文档的信息。当要读取的文件类型是FILE_MEMORY时,就直接调用memcpy()将file.detail.memory.contents的值复制给buf即可;当要读取的文件类型是FILE_LOCAL时,就调用文件操作函数read将读取到的媒体数据文件流放入buf中即可。
下面是该函数的流程图
3.6.3 http_close()
用来关闭一个以打开的文件,当文件类型为FILE_LOCAL时,调用文件操作函数close()完成关闭文件;当文件类型为FILE_MEMORY时,调用free()释放file.detail.memory.contents的内存即可。