寻找Linux开发upnp的库,发现就是gupnp对我来说比较合适。同样的没有tutorial,同样的看example。基于gupnp,针对不同的功能有几个不同的包。gupnp-av,gupnp-dlna,gupnp-igd等等。我们需要用到的是gupnp-av,下载之后没有发现合适的例子。寻找后发现例子在gupnp-tools里面。和intel出的那套”Developer Tools for UPnP Technologies”很像,有一个灯的例子,还有一个gupnp-av-cp(cp: control point)。就是它了!
下载下来看源码,gupnp-av-cp里面大概分为三个大功能,一是搜索网络上的media-server并且显示多媒体文件,二是搜索render,三是播放。在我们的项目里面,可以只用到第一个功能,然后获取到uri地址,之后用我们自己的播放器进行播放。也就是说,我们的目标简化成为制作一个upnp的client,它的功能是寻找网络上的upnp media server,并得到其中的多媒体文件的uri地址。
网上关于upnp的原理的文章很多,但是反而开发教程极少。在gupnp的官网有一份简单粗略的教程,但是也不涉及media。通过阅读gupnp-av-cp的源码,终于明白了其基本流程,并且仿照着做出一份基于qt界面的程序,但若要从头讲起有些繁琐,就简略记录一下分析gupnp-av-cp源码的过程吧。
第一步,初始化
从main.c: init_upnp函数开始,在函数中连接了"context-available"和on_context_available回调函数。在on_context_available中再次连接了"device-proxy-available"和dms_proxy_available_cb。可以看到有点类似dbus,到处都是连接信号和回调。
第二步,添加media-server
“device-proxy-available”触发时,就会调用dms_proxy_available_cb再调用add_media_server。从现在开始,进入playlist-treeview.c文件,并且会设计gtk的建立tree的api,我们只关注upnp部分,忽略ui部分。add_media_server判断如果尚未添加这个server,那么就调用append_media_server再调用gupnp_device_info_get_friendly_name,就可以得到media server的名称。记录并添加到ui中。
第三步,添加目录
append_media_server中会调用browse (content_dir, "0", 0, MAX_BROWSE);再调用gupnp_service_proxy_begin_action,action的名称是”Browse”,绑定回调函数browse_cb。browse_cb中再调用gupnp_service_proxy_end_action,就可以拿到browse的结果。该结果是一个xml字符串,用GUPnPDIDLLiteParser对象进行解析就可以得到目录中的详细信息了。然后递归调用browse 函数,就可以遍历子目录,子目录的子目录,直到遍历到叶子节点,即普通文件为止。当然,在找到目录或者文件的同时,需要进行记录以便之后的递归,并且将名称存入ui中。
第四步,获取uri
获取到叶子节点后调用browse_metadata函数,再次进行一次名为”Browse”的action。在gupnp_service_proxy_end_action的"Result"中,即可得到多媒体文件的metadata。调用find_compat_res_from_metadata函数,从metadata中得到GUPnPDIDLLiteResource对象。再调用gupnp_didl_lite_resource_get_uri,获得多媒体文件最终的uri。
第五步,播放
调用player中的setUri函数,传入刚刚获取到的地址,即可播放。
总结
整个过程看起来繁琐复杂,实际上在实现过一遍之后,就会显得清晰很多。除了media相关的功能,upnp还可以用在很多硬件设备上,同样都是调用不同的action来工作的。之所以有如此多的signal和callback,是由于upnp这种基于event触发事件的场景所决定的。熟悉gupnp的使用,对了解这种event driven的c/c++实现,也是很有帮助的。