DLNA源码分析之render设备注册

ops/2024/10/19 3:25:23/

1,概述

        DLNA设备、服务的注册及发现(依赖开源库cling),DLNA中设备的注册、发现主要基于UPNP协议实现,这是微软推行的一个标准。Upnp最大的愿景是希望任何设备只要一接入网络,所有网上的设备马上就能知道有新设备加入,这些设备之间就可以彼此通信。

2,render设备注册

2.1 设备实例的创建LocalDevice

类LocalDevice有多个构造函数,主要用于不同参数集的对象生成,它的父类为Device。

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,Icon[] icons, LocalService[] services) throws ValidationException {super(identity, type, details, icons, services);this.deviceDetailsProvider = null;}

第一个参数是设备ID,其中的UDN是全球唯一的标识符,无论是根设备还是其中的嵌入式设备,而且要保持不变,即使设备重启。这个UND将在SSDP中被使用,有统一格式,前缀是uuid:,后面是Upnp厂商指定的UUID后缀。如:uuid:b7c7c900-6983-f00b-0000-0000264ce182,其中后面的数字实际是一段hashcode,根据自定义的名字加设备标识生成的hashcode。

第二个参数是设备类型,设备类型有固定的命名空间schemas-upnp-org,然后才是具体的类型,如果是渲染端为MediaRenderer,最后是版本号。完整的设备类型是命名空间+设备类型+版本号。如:urn:schemas-upnp-org:device:MediaRenderer:1

NOTIFY * HTTP/1.1                                                                                            

CACHE-CONTROL: max-age=1800                                                                                       LOCATION: http://192.168.192.65:37757/upnp/dev/4dc519be-77b4-31f1-908a-95c7b88eb8ab/desc                                                                                              

NT: urn:schemas-upnp-org:device:MediaRenderer:1                                                               HOST: 239.255.255.250:1900                                                                                               NTS: ssdp:alive                                                                                                

USN: uuid:4dc519be-77b4-31f1-908a-95c7b88eb8ab::urn:schemas-upnp-org:device:MediaRenderer:1

第三个参数是设备详情,其中friendlyname是给设备起的一个比较友好的显示名字,另外一个列表DLNACaps,标识DLNA的能力,通常是"av-upload", "image-upload", "audio-upload"。

    public DeviceDetails(String friendlyName, ManufacturerDetails manufacturerDetails,ModelDetails modelDetails, URI presentationURI, DLNADoc[] dlnaDocs, DLNACaps dlnaCaps) {this(null, friendlyName, manufacturerDetails, modelDetails, null, null, presentationURI, dlnaDocs, dlnaCaps);}

最后一个参数是服务列表,就是这个设备需要支持哪些服务,比如渲染设备要支持:

ConnectionManagerService,AVTransportService,AudioRenderingControl。

protected open fun generateLocalServices(): Array<LocalService<*>>

2.2 通过RegistryImpl的addDevice来完成设备注册

   localDevice = createRendererDevice(Utils.getHttpBaseUrl(applicationContext))upnpService.registry.addDevice(localDevice)

        接着调用LocalItems的add方法继续干活,先是判断是否已经注册过了,如果已经注册过,不重复执行,直接返回。然后进一步完成添加设备的过程。

    synchronized public void addDevice(LocalDevice localDevice) {localItems.add(localDevice);}

第一步,添加设备下的资源,根据命名空间/upnp下提供的资源,主要有DeviceDescriptor,ServiceDescriptor,ServiceControl,ServiceEvent等资源。

第二步,生成RegistryItem对象,添加到DeviceItems集合中。

第三步,如果需要通告设备存在,发出存在的通知advertiseAlive()。

第四步,回调监听,告诉RegistryListener有本地localDevice设备添加。监听类通常继承DefaultRegistryListener类,并重写其中的设备添加,设备删除方法,具体是在应用的activity中,upnpService实例构建完成,通过upnpService获取到其中的RegistryImpl来添加监听。

DefaultRegistryListener的实现类,通常需要实现:

localDeviceAdded(),

localDeviceRemoved(),

remoteDeviceAdded(),

remoteDeviceRemoved()等方法。

2.3  advertiseAlive

        LocalItems.java中的advertiseAlive(),处理设备存在的通知。

这里是通过异步的方式提交一个通知任务,通常任务先睡眠100毫米后在执行,避免对网络造成拥塞。接着通过RegistryImpl中创建的ProtocolFactoryImpl实例生成一个通知消息。具体是SendingNotificationAlive实例。这个通知的类型是NotificationSubtype.ALIVE,也就是ALIVE("ssdp:alive"),SendingNotification这个类,实际是一个Runnable实例,是为注册的本地设备发送一个通知消息。

        最后,看下这个通知存在的消息发给了谁?

SendingNotification#Execute()

第一步,获取本机IP地址上初始化的streamServer,   

  List<NetworkAddress> activeStreamServers =getUpnpService().getRouter().getActiveStreamServers(null);

NetworkAddress.java类型,包含了三个属性,本机IP地址,端口,本机物理地址(MAC)。

 

第二步,封装一个带有本地设备(渲染器,非当前手机),本机上streamServer的描述对象Location。

        List<Location> descriptorLocations = new ArrayList();for (NetworkAddress activeStreamServer : activeStreamServers) {descriptorLocations.add(new Location(activeStreamServer,getUpnpService().getConfiguration().getNamespace().getDescriptorPath(getDevice())));}

其中的参数,getDevice(),是最开始创建的LocalDevice 实例。

Location实例包含了两个属性,一个是本机当前可用的streamServer,一个是本地设备对应的URI值。Web上可用的每一种资源都有一个通用资源标识符URI进行定位。

本地设备(DMR)对应的

URI: /upnp/dev/a2eeed03-2d36-3176-9b16-31410da11b4c/desc

第三步,发送消息,因为是基于udp协议发送,默认间隔150毫秒,重复发送三次。

 Execute()@SendingNotification.java

for (Location descriptorLocation : descriptorLocations) {
        sendMessages(descriptorLocation);

}

把descriptorLocation添加上localdevice包装成OutgoingNotificationRequest消息类型。

这个过程会添加消息头:

UpnpHeader.Type.NT: (RootDeviceHeader) 'upnp:rootdevice'

UpnpHeader.Type.USN: (USNRootDeviceHeader) 'uuid:b7c7c900-6983-f00b-0000-0000264ce182'

如有物理地址:

UpnpHeader.Type.EXT_IFACE_MAC: (InterfaceMacHeader) '00:0A:F5:06:0F:24'

1, 数据包IO口的初始化(负责数据包的发送)

从上一步转入RouterImpl.java中的send()方法,这里用DatagramIO完成数据包的发送。

先看DatagramIO的初始化,因为绑定到一个IP地址上,这个过程是在startAddressBasedTransports()中,获取到本机IP地址后完成的。

DatagramIOImpl是具体的类实现,构造实例时,可以配置发送参数,如跳数,发送数据包的最大值。

数据包内容的读写通过DatagramProcessorImpl.java来完成。

接着看DatagramIOImpl的初始化。

 Init() @ DatagramIOImpl.java

在本机IP地址的基础上封装端口:

localAddress = new InetSocketAddress(bindAddress, 0);

封装一个多播端口,以便把数据包发送到多个client端:

socket = new MulticastSocket(localAddress);

最后数据包的发送是通过MulticastSocket完成。

send() @ DatagramIOImpl.java

发送的数据包会通过DatagramProcessorImpl.java的write方法来构建DatagramPacket对象。

数据包发给谁了呢?这要看message中目标地址destinationAddress是谁?

消息报的目标地址,是在OutgoingNotificationRequest.java的构造函数中设置的。

前面说过,要发送的消息会被包装成OutgoingNotificationRequest类型,或者其子类型OutgoingNotificationRequestRootDevice, OutgoingNotificationRequestUDN,或OutgoingNotificationRequestDeviceType,具体是在:

createDeviceMessages()@SendingNotification.java

最后看OutgoingNotificationRequest.java的构造函数:

   super(

                new UpnpRequest(UpnpRequest.Method.NOTIFY),

                ModelUtil.getInetAddressByName(Constants.IPV4_UPNP_MULTICAST_GROUP),

                Constants.UPNP_MULTICAST_PORT

        );

其中,消息类型是NOTIFY,

相应的目标地址:IPV4_UPNP_MULTICAST_GROUP = "239.255.255.250";

目标端口:UPNP_MULTICAST_PORT = 1900;

这是IANA(互联网数字分配组织)保留的多播地址。


http://www.ppmy.cn/ops/24399.html

相关文章

设计模式- 访问者模式(Visitor Pattern)结构|原理|优缺点|场景|示例

设计模式&#xff08;分类&#xff09; 设计模式&#xff08;六大原则&#xff09; 创建型&#xff08;5种&#xff09; 工厂方法 抽象工厂模式 单例模式 建造者模式 原型模式 结构型&#xff08;7种&#xff09; 适配器…

3分钟了解拍摄VR全景需要哪些硬件

VR全景图片是一张水平方向360度&#xff0c;垂直方向180度&#xff0c; 图片尺寸宽高比为2:1的图片。 通过720yun APP或720yun官网上传生成全景H5页面&#xff0c;即可360度全方位观看画面中的景象。 拍摄VR全景有很多方法&#xff0c;下面介绍用单反相机、全景相机、智能手机…

Python爬虫--Scrapy框架安装

Scrapy框架安装 &#xff0c; Scrapy 是 Python 领域专业的爬虫开发框架&#xff0c;已经完成爬虫程序的大部分通用工具 它使用了 Twisted 异步网络库来处理网络通讯。整体架构大致如下 第一步&#xff1a;挂小灰机或者将要安装的文件下载到本地 Scrapy 框架安装踩坑中 为什…

IDEA 创建Servlet-HelloWorldServlet

servlet 1.创建空项目2.配置web项目3.配置Tomcat4.加载Tomcat包5.创建HelloWorldServlet类6.配置web.xml7.运行get与post请求 1.创建空项目 2.配置web项目 3.配置Tomcat 4.加载Tomcat包 5.创建HelloWorldServlet类 public class controller extends HttpServlet {Override//get…

Double 4 VR智能互动系统在法律法庭上的模拟演练教学

随着科技的不断发展&#xff0c;虚拟现实技术&#xff08;VR&#xff09;在各个领域的应用也越来越广泛。而在法律领域&#xff0c;VR技术也被用于模拟法庭审判&#xff0c;让学习者能够在虚拟环境中亲身体验法庭审判的全过程&#xff0c;从而提高其法律意识和法律素养。 为了…

深入理解Python多进程:从基础到实战

title: 深入理解Python多进程&#xff1a;从基础到实战 date: 2024/4/29 20:49:41 updated: 2024/4/29 20:49:41 categories: 后端开发 tags: 并发编程多进程管理错误处理资源调度性能优化异步编程Python并发库 引言 在Python编程中&#xff0c;多进程是一种重要的并发编程…

网盘——进入文件夹

本文主要讲解网盘的文件操作中进入文件夹的部分&#xff0c;具体实现步骤如下&#xff1a; 1、具体步骤如下&#xff1a; A、客户端发送进入文件夹的请求&#xff08;该请求包含目录信息以及要进入的文件夹名字&#xff09; B、服务器收到该请求之后&#xff0c;服务器首先判…

JAVA内部类

内部类 Java内部类&#xff08;Inner Class&#xff09;是定义在另一个类&#xff08;外部类&#xff09;内部的一个类。这种嵌套类的定义允许更紧密的代码组织和更高的封装性。内部类可以访问其外部类的所有成员&#xff0c;包括私有成员&#xff0c;反之则不然。内部类可以有…