简介
Cling
Cling类库是由Java实现的DLNA/UPnP协议栈。基于DLNA/UPnP可以开发出类似多屏互动、资源共享、远程控制等功能的应用,通过Android应用管理一个或多个设备,将音频、视频、图片推送到指定设备显示、播放。
UPnP的实现类库有很多,如CyberGarage、Platinum UPnP、libupnp等一些开源库,例如:
- Platinum UPnP,基于C++开发,可以支持Windows,iOS,Android等平台,XBMC就是使用的此库。
- Cling,基于Java开发,也是后续要介绍的,市面上很多支持DLNA功能的App都是使用的此库,如BubbleUPnP。
Cling基本使用
Cling库包括两个模块:
-
Cling Core
- 核心类库,基于UDA1.0,实现了定义服务、设备发现,通过ControlPoint发送指令等UPnP的基本功能;
-
Cling Support
- 顾名思义该包为Cling中一些功能的扩展,如:ConnectionManager Service、AVTransport Service、RenderingControl Service以及LastChange;
Android平台基本使用
以Android平台创建UPnP服务并顶用相关的控制方法介绍Cling的基本使用:
UPnP协议支持Android设备作为Control Point和UPnP Device两种设备类型;
版本迭代
Cling Core和Cling Support两个模块的迭代最新的为2018年,Cling目前已停止维护,Cling提供了源码,开发人员可以自己维护和扩展功能;
总体设计
概述
UPnP协议:
Cling作为UPnP协议栈,其主旨即是在设备的发现、控制等过程中对不同的协议及内容进行处理。UPnP协议栈由多个层组成,Cling只关心底层的TCP/IP协议以及包含SSDP(简单设备发现协议)、SOAP(简单对象访问协议)、GENA(一般事件通知协议)协议的层;
使用场景
以一个简单的设备使用场景为例:
使用场景:用户将手机A中的媒体内容播放到电视B上,前提:A、B在同一个局域网中。
- A加入到多播组中,建立MulticastSocket监听多播信息;
- A向多播发出M-SEARCH报文;
- B获取多播的报文,判断是否符合条件,若符合向多播地址回应OK报文,报文中包含description URL;
- A监听多播获取到相关报文,并通过URL获得设备描述信息;
- A通过AVTransport Service将媒体内容推送到B并播放;
在整个过程中A通过Cling既充当了DMC又作为DMS,而B作为DMR播放媒体内容;
流程图
设备发现及控制流程
媒体播放流程
详细涉及
类图
类详解
- ControlPoint:异步执行网络搜索、操作、事件订阅的统一接口,后面的所有操作都要用到它;
- RegistryListener:注册表监听器,用于监听所有类型的Device的新增、删除和更新;
- Registry:UPnP协议栈的核心,追踪设备和资源;
- AndroidUpnpService:Android侧UPnP协议服务,其中包含了registry以及ControlPoint等;
- ProtocolFactory:UPnP协议工厂,工厂创建可执行的协议基于接收到的UPnP消息,或者本地设备/搜索/服务的元数据;
- Router:网络传输层接口,疯转传输层,为上层提供方法来发送UPnP流(HTTP)和发送UDP数据报,还有局域网广播;
- StreamClient:发送TCP流请求消息的服务;
- StreamServer:接收TCP流的服务,每个IP一个,该服务在一个套接字上监听TCP连接;
- DatagramIO:接收单播和发送UDP数据报的服务,每个IP绑定一个,该服务在一个套接字上监听UDP单播数据报,监听循环在run()中开始,任何接收的数据报然后被转化为IncomingDatagramMessage,然后被Router.received()处理;
- MulticastReceiver:接收UDP数据报广播的服务,每个网络接口一个,该服务在一个套接字上监听UDP数据报,监听循环在run()中开始,任何接收的数据报然后被转化为IncomingDatagramMessage,然后被Router.received()处理;
- UpnpServiceConfiguration:UPnP协议配置参数;
- GENAEventProcessor:GENA事件处理器;
- SOAPActionProcessor:完成UPnP SOAP和动作请求的互相转换;
- DatagramProcessor:数据报处理器;
- ServiceDescriptorBinder:远程Service描述文档解析类;
- DeviceDescriptorBinder:远程Device描述文档解析类;
- NetworkAddressFactory:网络地址工厂类;
流程源码分析
UpnpRequest讲解
首先我们通过上一章的UPnP协议分析可知,client和server之间的数据包请求类型,这些类型定义在UpnpRequest.java中:
public class UpnpRequest extends UpnpOperation {
public static enum Method {
GET("GET"),
POST("POST"),
NOTIFY("NOTIFY"),
MSEARCH("M-SEARCH"),
SUBSCRIBE("SUBSCRIBE"),
UNSUBSCRIBE("UNSUBSCRIBE"),
UNKNOWN("UNKNOWN");
private static Map<String, Method> byName = new HashMap<String, Method>() {{
for (Method m : Method.values()) {
put(m.getHttpName(), m);
}
}};
private String httpName;
Method(String httpName) {
this.httpName = httpName;
}
public String getHttpName() {
return httpName;
}
public static Method getByHttpName(String httpName) {
if (httpName == null) return UNKNOWN;
Method m = byName.get(httpName.toUpperCase(Locale.ENGLISH));
return m != null ? m : UNKNOWN;
}
}
private Method method;
private URI uri;
public UpnpRequest(Method method) {
this.method = method;
}
public UpnpRequest(Method method, URI uri) {
this.method = method;
this.uri = uri;
}
public UpnpRequest(Method method, URL url) {
this.method = method;
try {
if (url != null) {
this.uri = url.toURI();
}
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
public Method getMethod() {
return method;
}
public String getHttpMethodName() {
return method.getHttpName();
}
public URI getURI() {
return uri;
}
public void setUri(URI uri) {
this.uri = uri;
}
@Override
public String toString() {
return getHttpMethodName() + (getURI() != null ? " " + getURI() : "");
}
}
这个类主要是用于定义request的,里面包含了request需要定义的属性值,我们主要关注request的METHOD的类型,有NOTIFY、GET、POST等一系列的method;
Router讲解
在讲解UPnP工作流程之前,我们需要先了解cling中的一个类,这个类控制了下面所有的逻辑:Router.java;
public interface Router {
public UpnpServiceConfiguration getConfiguration();
public ProtocolFactory getProtocolFactory();
boolean enable() throws RouterException;
boolean disable() throws RouterException;
void shutdown() throws RouterException ;
boolean isEnabled() throws RouterException;
void handleStartFailure(InitializationException ex) throws InitializationException;
public List<NetworkAddress> getActiveStreamServers(InetAddress preferredAddress) throws RouterException;
public void received(IncomingDatagramMessage msg);
public void received(UpnpStream stream);
public void send(OutgoingDatagramMessage msg) throws RouterException;
public StreamResponseMessage send(StreamRequestMessage msg) throws RouterException;
public void broadcast(byte[] bytes) throws RouterException;
}
数据传输层接口,负责接收和发送UPnP和UDP消息,或者将接收到的数据流广播给局域网内的其他设备,是数据收发处理的核心类,实现类为RouterImpl。
其中包括了几个方法,我们现在看一下这几个方法的作用:
| 方法名 | 方法解释 | 备注 |
|---|---|---|
| enable() | 1.得到配置中的NetworkAddressFactory,其目前实现为NetworkAddressFactoryImpl; 2.调用startInterfaceBasedTransports()为NetworkAddressFactory中的每个网络接口绑定一个多播接收器MulticastReceiver,用来监听多播地址,并处理获取到的数据,key-value形式存储在multicastReceivers中; 3.调用startAddressBasedTransports为NetworkAddressFactory的每个地址绑定一个StreamServer和DatagramIO,监听并进行数据处理。key-value形式存储在streamServers中; 4.创建一个StreamClient用于发送TCP消息; | 通过可重入锁写锁控制启动和停止的并发 |
| desable() | 停止StreamClient; 停止StreamServers中每个StreamServer; 停止MulticastReceivers中的每个MulticastReceiver; 停止DatagramIOs中每个DatagramIO; | 通过可重入锁写锁控制启动和停止的并发 |
| getActiveStreamServers() | 根据preferredAddress得到活跃的StreamServer,如果preferredAddress对应的StreamServer存储且活跃则返回,否则返回当前所有活跃的StreamServer | |
| received() | 根据消息类型得到协议并执行 | |
| send(StreamRequestMessage msg) | 通过StreamClient发送TCP包; | |
| send(OutgoingDatagramMessage msg) | send()方法向所有DatagramIO发送UDP包; | |
| broadcast() | 向所有DatagramIO广播发送数据 |
Router接口类,实现类为RouterImpl,RouterImpl的创建在AndroidUpnpServiceImpl的onCreate()方法中创建;
后续拆解分析流程;