DLNA源码分析之render设备注册

时间:2024-05-06 07:08:28

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(互联网数字分配组织)保留的多播地址。