Cling源码解析

article/2025/9/28 13:46:07

项目地址:cling,分析的版本:5fd60eb,Demo 地址:BeyondUPnP

1 功能介绍

1.1 Cling

Cling类库是由java实现的DLNA/UPnP协议栈。基于DLNA/UPnP可以开发出类似多屏互动、资源共享、远程控制等功能的应用,通过Android应用管理一个或多个设备,将音频、视频、图片推送到指定设备显示。

UPnP的实现类库有很多,在http://www.upnp.org 上也有对相关内容的介绍。
比较有名的有:
- Platinum UPnP基于C++开发,可以支持windows,IOS,android等平台,XBMC就是使用的此库。
- Cling基于java开发,也是后续要介绍的,市面上很多支持DLNA功能的App都是使用的此库,如BubbleUPnP。

其他的如CyberGarage,Intel UPnP stack就不一一列举了。详情可参考http://upnp.org/certification/toolsoverview/sdks/

1.2 UPnP介绍

官方解释为:UPnP 是各种各样的智能设备、无线设备和个人电脑等实现遍布全球的对等网络连接(P2P)的结构。
UPnP实际使用场景多用于小范围对等网络内(连接至同一路由器的多个设备)之间的相互发现、控制。如使用手机控制电视盒子的音频,视频播放等。

1.3 Cling基本使用

Cling库包括两个模块:
- Cling Core
核心类库,基于UDA1.0,实现了定义服务,设备发现,通过ControlPoint发送指令,等UPnP的基本功能。
- Cling Support
顾名思义该包为Cling中一些功能的扩展,如:avtransport,lastchange等。

下面就以Android平台创建UPnP服务并调用相关的控制方法介绍Cling的基本使用。
1. 定义自己的UpnpService类,继承自AndroidUpnpServiceImpl
2. 创建该Service
3. 从UpnpService中获取ControlPoint,并搜索设备

UpnpService upnpService;
//搜索注册在多播地址的所有设备,也可根据需要使用不同条件搜索
upnpService.getControlPoint().search(new STAllHeader());
  1. 获取所有类型为MediaRenderer的设备
upnpService.getRegistry().getDevices(new UDADeviceType("MediaRenderer"));
  1. 向Device发送指令
    从查找到的结果中获取一个Device,并向其发送Play指令
        Device device;//Check selected deviceif (device == null) return;Service avtService = device.findService(new UDAServiceType("AVTransport"));if (avtService != null) {ControlPoint cp = SystemManager.getInstance().getControlPoint();cp.execute(new Play(avtService) {@Overridepublic void success(ActionInvocation invocation) {Log.i(TAG, "Play success.");}@Overridepublic void failure(ActionInvocation arg0, UpnpResponse arg1, String arg2) {Log.e(TAG, "Play failed");}});}

上述即为一个基本的发现、控制流程,通过ControlPoint发送指令并处理callback。

注:上述只涵盖了使用中的几个关键点,详细内容可参考我开源的项目BeyondUPnP

2 总体设计

2.1 概述

Cling作为UPnP协议栈,其主旨即是在设备的发现,控制等过程中对不同的协议及内容进行处理。UPnP协议栈由多个层组成,Cling只关心底层的TCP/IP协议以及包含SSDP(设备发现),SOAP(设备控制),GENA(设备事件)协议的层。

2.2 使用场景

以一个简单的设备使用场景为例:

用户将手机A中的媒体内容播放到电视B上,前提:A、B在同一个局域网中。
- A加入到多播组中,建立MulticastSocket监听多播信息
- A向多播发出M-SEARCH报文
- B获取多播的报文,判断是否符合条件,若符合向多播地址回应OK报文,报文中包含description URL
- A监听多播获取到相关报文,并通过URL获得设备描述信息
- A通过AVTransport Service将媒体内容推送到B并播放

在整个过程中A通过Cling既充当了DMC(Digital Media Controller)又作为DMS(Digital Media Server),而B作为DMR(Digital Media Renderer)播放媒体内容。

3 流程图

3.1 设备发现及控制流程

control_flow

3.2 媒体播放流程

playback_flow

4 详细设计

4.1 类关系图

overview

4.2 类功能详细介绍

由类图可知,Cling的一切都是从UpnpService开始的,其中包含了ControlPoint,ProtocolFactory,Registry,Router四个核心模块,以及一个配置信息类UpnpServiceConfiguration

4.2.1 ControlPoint

异步执行搜索,设备控制订阅等指令,此接口定义了查找设备,向设备发送指令,订阅设备变更,其实现类只有一个为ControlPointImpl.

(1). 查找

根据UpnpHeader查询指定的设备,UpnpHeader为抽象类其中定义了枚举类型的Type以及泛型value,查询时常用的实现类有:DeviceTypeHeader,UDNHeader等,可根据设备类型、UDN、服务类型等多种方式。

public void search(UpnpHeader searchType, int mxSeconds);

(2). 执行控制指令

将ActionCallback放入DefaultUpnpServiceConfiguration中定义的线程池ClingExecutor并执行,执行完毕回调ActionCallback中定义的success或failure函数。

public Future execute(ActionCallback callback) {callback.setControlPoint(this);ExecutorService executor = getConfiguration().getSyncProtocolExecutorService();return executor.submit(callback);}

4.2.2 ProtocolFactory

协议处理工厂类使用Simple Factory Pattern封装协议内容的处理,具体实现为ProtocolFactoryImpl,分为接收报文处理和创建发送报文两部分。
在该类中UDP包通过createReceivingAsync方法对传递来的IncomingDatagramMessage进行处理,。
如NOTIFY–ReceivingNotification,MSEARCH–ReceivingSearch。TCP包通过createReceivingSync进行分发处理,并通过ReceivingSync的子类进行处理,子类中调用executeSync方法等待并返回response。

(1). 处理接收到的报文

IncomingDatagramMessage封装了UDP包的信息,在createReceivingAsync中分发到对应的处理方法中并创建处理对象,如NOTIFY–ReceivingNotification,MSEARCH–ReceivingSearch

public ReceivingAsync createReceivingAsync(IncomingDatagramMessage message){if (message.getOperation() instanceof UpnpRequest) {IncomingDatagramMessage<UpnpRequest> incomingRequest = message;switch (incomingRequest.getOperation().getMethod()) {case NOTIFY:return isByeBye(incomingRequest) || isSupportedServiceAdvertisement(incomingRequest)? createReceivingNotification(incomingRequest) : null;case MSEARCH:return createReceivingSearch(incomingRequest);}} else if (message.getOperation() instanceof UpnpResponse) {IncomingDatagramMessage<UpnpResponse> incomingResponse = message;return isSupportedServiceAdvertisement(incomingResponse)? createReceivingSearchResponse(incomingResponse) : null;}
}

StreamRequestMessage封装TCP报文,通过createReceivingSync分发处理,ReceivingSync子类中重写executeSync方法定义具体实现.

public ReceivingSync createReceivingSync(StreamRequestMessage message){if (message.getOperation().getMethod().equals(UpnpRequest.Method.GET)) {return createReceivingRetrieval(message);} else if (getUpnpService().getConfiguration().getNamespace().isControlPath(message.getUri())) {if (message.getOperation().getMethod().equals(UpnpRequest.Method.POST))return createReceivingAction(message);} else if (getUpnpService().getConfiguration().getNamespace().isEventSubscriptionPath(message.getUri())) {if (message.getOperation().getMethod().equals(UpnpRequest.Method.SUBSCRIBE)) {return createReceivingSubscribe(message);} else if (message.getOperation().getMethod().equals(UpnpRequest.Method.UNSUBSCRIBE)) {return createReceivingUnsubscribe(message);}}........
}

(2). 组装发送的报文:

有若干功能类似的方法,如:
向组播发送ssdp:alive告知设备存活

public SendingNotificationAlive createSendingNotificationAlive(LocalDevice localDevice)

生产SendingSearch实例的工厂方法,SendingSearch中定义了查询条件以及请求超时时间,并Override了execute(),在线程启动后创建OutgoingSearchRequest对象并通过Router发送。

public SendingSearch createSendingSearch(UpnpHeader searchTarget, int mxSeconds)

4.2.3 Registry

协议栈的核心,实现类为RegistryImpl,可把其看做一个注册表,当发现新设备时将其加入Registry,当该设备失效后从Registry中移除,设备的订阅信息也在此维护。
该类中通过下列类对注册内容以及订阅内容等进行处理:
- RegistryListener
注册表监听类,定义为一组监听器,Set registryListeners。
实现类为DefaultRegistryListener,监听Device的add,remove动作。

  • Resource
    资源的父类。该类中定义资源的URI,model等属性。

  • ExpirationDetails
    每个RegistryItem对象都有自己的ExpirationDetails,ExpirationDetails通过构造函数传递的maxAgeSeconds记录最大超时时间,在每次maintain时判断该RegistryItem是否过期。

  • RegistryMaintainer
    用来每隔1000ms调用一次registry.maintain()方法,该方法执行的操作有:
    1.判断过期的item,并从resourceItems中移除。
    2.遍历resourceItems,并对其中的每个Resource调用其maintain()方法。
    3.remoteItems.maintain()对remote进行维护
    4.localItems.maintain()对local进行维护
    5.runPendingExecutions执行异步任务

  • RemoteItems
    包含deviceItems集合,定义了对RemoteDevice的增删查改等操作。
    保存search后的RemoteDevice集合。

  • LocalItems
    继承自RegistryItems,包含了对LocalDevice的操作。

4.2.4 Router

Router为数据收发处理的核心类,实现类为RouterImpl。在其中通过重入读写锁控制设备的启用和禁用,并

(1). 并发控制

使用可重入读写锁ReentrantReadWriteLock实现设备并发读写的控制

protected volatile boolean enabled;
protected ReentrantReadWriteLock routerLock = new ReentrantReadWriteLock(true);
protected Lock readLock = routerLock.readLock();
protected Lock writeLock = routerLock.writeLock();//writeLock只在enable()和disable()函数尝试获取并禁止其他线程访问,完成操作后释放unlock(writeLock)
public boolean enable() throws RouterException {lock(writeLock);try {if (!enabled) {.....}return false;} finally {unlock(writeLock);}}//多个线程可同时获取读锁lock(readLock)并发处理内容
public void send(OutgoingDatagramMessage msg) throws RouterException {lock(readLock);try {if (enabled) {for (DatagramIO datagramIO : datagramIOs.values()) {datagramIO.send(msg);}} else {log.fine("Router disabled, not sending datagram: " + msg);}} finally {unlock(readLock);}}

(2). 获取网络信息

NetworkAddressFactory的实现类NetworkAddressFactoryImpl提供网络相关内容,如NetworkAddress,interface等

(3). 初始化

startAddressBasedTransports函数,将绑定到router上的ip及端口都以StreamServer的方式进行监听。每一个StreamServer对应一个DatagramIO,进行数据处理。

startInterfaceBasedTransports函数,对应每个NetworkInterface创建对应的MulticastReceiver,用来监听多播地址,并处理获取到的数据。

(4). 发送数据

send(StreamRequestMessage msg) 通过StreamClient发送TCP包
send(OutgoingDatagramMessage msg) 通过datagramIO发送多播的UDP包。

(5). StreamClient

StreamClient具体实现类为AbstractStreamClient以及其子类StreamClientImpl。
在Android系统下使用的Jetty实现。在该类中具体的http协议处理由HttpClient实现,核心方法sendRequest用于创建请求并获取返回response,请求及返回值通过HttpContentExchange封装,每一个StreamRequestMessage及其对应的HttpContentExchange通过createCallable方法封装为Callable对象,并将其压入DefaultUpnpServiceConfiguration中的defaultExecutorService。在call()中调用client.send(exchange)发送request并获取response。

(6). StreamServer

StreamServer用来接收HTTP请求并进行处理。在AndroidUpnpServiceConfiguration中进行初始化:

public StreamServer createStreamServer(NetworkAddressFactory networkAddressFactory) {// Use Jetty, start/stop a new shared instance of JettyServletContainerreturn new AsyncServletStreamServerImpl(new AsyncServletStreamServerConfigurationImpl(JettyServletContainer.INSTANCE,networkAddressFactory.getStreamListenPort()));}

本质上是由Jetty实现的servlet容器。从HttpServletRequest中获取数据流并传递给Router的received(UpnpStream stream)进行处理。JettyServletContainer使用了单例模式,其中定义Server的具体实现,并使用synchronized同步Server属性变更操作。

(7). ReceivingNotification

处理接收到的notification消息。如ALIVE,BYEBYE。当接收到ALIVE消息后,会在后台启动一个线程执行RetrieveRemoteDescriptors获取该设备的信息。

(8). RetrieveRemoteDescriptors

用来主动获取远端内容。并返回RemoteService加入到Registry中。

5 结语

Cling作为一款优秀的开源UPnP协议栈从之前的1.x版本发展到现在的2.x在稳定性易扩展等方面有着显著的提升,由于对Android平台有着较好的支持如BubbleUPnP等越来越多的产品使用Cling作为解决方案。当然它本身也还存在着如Router切换WIFI时注册设备清除失败等问题,但瑕不掩瑜本着学习的态度还是可以从中受益良多。


http://chatgpt.dhexx.cn/article/jCX6OaQR.shtml

相关文章

树莓派python蓝牙_在树莓派3B上做蓝牙音频

无损播放器无线音频方案设计 1. 音频流程简介 暂缺。 2. 关键库和应用简介 2.1 pulseaudio 2.2 bluez 2.3 gstreamer 3. 无线音频方案实施方案 3.1 主要参考材料 3.2 树莓派和R16方案设计 ( 工具&#xff0c;具体位置在&#xff1a;tina/staging_dir/toolchain-arm_c…

cantata测试工具_我如何构建和维护开源音乐播放器Cantata

cantata测试工具 这是与开发和维护开源音乐播放器的开发人员进行的一系列对话的第三部分。 Craig Drummond是Cantata的开发者和维护者&#xff0c; Cantata是一种开源音乐播放器&#xff0c;充当Music Player Daemon&#xff08;MPD&#xff09;音乐服务器的前端&#xff08;客…

sd卡烧写linux内核,linux下怎样烧写sd卡

广告 提供包含云服务器,云在内的50+款云计算产品。打造一站式的云产品试用服务,助力开发者和企业零门槛上云。 就像用硬盘装windows平台一样,将sd启动卡插入板子的sd卡插槽,然后将启动顺序拨码开关拨到sd卡启动处,开机启动即可手动把sd卡中加装好的平台烧写到板子上...如下…

linux下实现dlna识别当前音频格式,如何将实时音频流设置为DLNA兼容设备?

问题描述 有没有办法将声卡的实时输出从我们的12.04.1 LTS amd64桌面流式传输到我们网络中的DLNA-compliant外部设备&#xff1f;使用Rygel&#xff0c;miniDLNA和uShare选择共享目录中的媒体内容总是很好 – 但到目前为止&#xff0c;我们完全无法通过DLNA向客户端获取实时音频…

IOS版aplayer使用教程_享声数播APP使用指南【ios版】

享声数播的内容播放是支持APP操作的,现在我们简单介绍一下相关的操作指南。 关于享声APP,官方推荐安卓手机用户使用bubbleupnp ,苹果ios用户使用8player 举例设备:享声A1 软件:8player 操作顺序 【数播端操作】 ①享声数播用网线连接到路由器 ②在享声主屏幕上切换到【通用…

打开方式中选择默认方式无反映_「全民标签」论享声A1的APP打开方式以及与树莓派的使用对比...

​ 大家好&#xff0c;我是流氓才子。 说在前面&#xff1a;今天的推送&#xff0c;末尾没有福利&#xff0c;因为个人觉得文章的主角就是最大的福利。。。先别急着说这句广告很硬&#xff0c;往下翻翻试试&#xff1f; 近一个月来论坛最火的机器是啥&#xff1f;没猜错的话应该…

Android上好用的DLNA播放器BubbleUPnP

BubbleUPnP的厉害之处在于&#xff1a;实现在手机上听电脑上的歌&#xff0c;或者控制电脑上的播放器当遥控器用。用foobar2000只需要添加一个叫foo_upnp的插件即可&#xff0c;XMBC或者别的支持UPNP的播放器也可以。 BubbleUPnP播放器能够轻松播放手机中的音视频及网络上的UPn…

BubbleUPnP多屏互动

一&#xff0c;相关简介 1、 DLAN简介&#xff08;百度百科&#xff09; DLNA的全称是DIGITAL LIVING NETWORK ALLIANCE&#xff0c;其宗旨是Enjoy your music, photos and videos, anywhere anytime&#xff0c; DLNA(Digital Living Network Alliance)由索尼、英特尔、微软等…

WebDAV之葫芦儿·派盘+BubbleUPnP

BubbleUPnP 支持WebDAV方式连接葫芦儿派盘。 推荐一款投屏神器,它将手机内容分享到电视大屏上与家人好友一起共享,软件还提供了丰富的音乐及影视资源,喜欢的内容在线搜索就能播放。支持连接葫芦儿派盘WebDAV服务站,可以直接播放派盘内的影视资源。 BubbleUPnP是一款支持U…

数据结构和算法——Huffman树和Huffman编码

Huffman树是一种特殊结构的二叉树&#xff0c;由Huffman树设计的二进制前缀编码&#xff0c;也称为Huffman编码在通信领域有着广泛的应用。在word2vec模型中&#xff0c;在构建层次Softmax的过程中&#xff0c;也使用到了Huffman树的知识。 在通信中&#xff0c;需要将传输的文…

Huffman编码压缩文件

文章目录 前言一、Huffman编码是什么&#xff1f;二、Huffman编码的实现方法三、Huffman压缩文件1.统计文件个字符出现的次数2.生成Huffman树3.生成码表4.对文件进行压缩 四、Huffman解压文件五、实验结果总结 前言 这个实验是我在学习信息论与编码时所做的课程实验&#xff0…

自适应Huffman编码

自适应Huffman编码&#xff0c;可用初始编码表&#xff08;数字音视频技术&#xff0c;实验二&#xff09; 如果你已经理解了 自适应Huffman编码 &#xff0c;那么你不应该浪费时间在无聊的实验上 实验目的 1、深入掌握自适应Huffman编码的原理 2、掌握自适应Huffman编码算法…

huffman python,哈夫曼(Huffman)编码python代码实现

首先看定义 哈夫曼编码(Huffman Coding)&#xff0c;又称霍夫曼编码&#xff0c;是一种编码方式&#xff0c;哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法&#xff0c;该方法完全依据字符出现概率来构造异字头的平均长度最短的码字&#xff0c;有时称…

Huffman Tree

Huffman Tree 哈夫曼树&#xff1b;哈夫曼编码&#xff1b;最优二叉树 自底向上 变长编码&#xff1b;前缀编码&#xff1b;熵编码 数据无损压缩&#xff1b;最短编码&#xff1b;最佳判定树 一、基本概念 Huffman Tree&#xff0c;又称最优二叉树&#xff0c;是带权路径长度最…

Huffman Codes

题目 In 1953, David A. Huffman published his paper “A Method for the Construction of Minimum-Redundancy Codes”,and hence printed his name in the history of computer science. As a professor who gives the final exam problem on Huffman codes, I am encounte…

HuffmanTree

#include "stdafx.h" #include "stdio.h" #include "stdlib.h" #include "string.h"typedef int ELEMTYPE;//哈弗曼树节点结构体 typedef struct HuffmanTree {ELEMTYPE weight;ELEMTYPE id;//区分权值相同的节点struct HuffmanTree* …

JPEG中Huffman解码实例讲解

DHT Huffuman表格式 -------------------------------------------------------------------------- 名称 字节数 值 说明 -------------------------------------------------------------------------- 段标识 1 FF 段类型 1 C4 段…

哈夫曼树(huffman)

学完了huffman树&#xff0c;讲一下自己对它的理解 huffman树遵循二叉树的原则&#xff0c;每个节点最多有两个子节点&#xff0c;但是每个节点都带有一个权重&#xff0c;如果我们要将一组字符串 “ B D C A F E ” 插入huffman树&#xff0c;每个字符都会带有一个权重&#…

Huffman树和Huffman编码

文章目录 Huffman树的定义带权路径长度WPL Huffman树的构造Huffman树的特点 Huffman编码构造Huffman编码 Huffman树的定义 哈夫曼&#xff08;Huffman&#xff09;树&#xff0c;又称最优二叉树&#xff0c;是一类带权路径长度WPL最短的树。 带权路径长度WPL 要理解带权路径…

Huffman树(哈夫曼树)

哈夫曼树又称最优二叉树&#xff0c;是一种带权路径长度最短的二叉树。所谓树的带权路径长度&#xff0c;就是树中所有的叶结点的权值乘上其到根结点的路径长度&#xff08;若根结点为0层&#xff0c;叶结点到根结点的路径长度为叶结点的层数&#xff09;。树的带权路径长度记为…