DLNA源码分析之render设备注册

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

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 这个问题可能是关于技术的,我可以回答。DLNA协议中的MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION命令是用于设置媒体播放位置的,但是它可能不起作用的原因有很多,比如设备不支持、网络连接问题等等。如果您遇到了这个问题,建议您检查设备和网络连接,或者尝试其他方法来设置媒体播放位置。 ### 回答2: DLNA协议MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION可能不起作用的原因有以下几点: 1. 设备兼容性问题:DLNA协议的实现可能会有一些差异或限制,导致MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION命令在某些设备上无法正常工作。这可能是由于设备厂商对协议的实现方式不同,或设备硬件的限制导致的。 2. 媒体服务器问题:DLNA协议中,媒体服务器负责提供媒体文件给渲染器设备播放。如果媒体服务器的实现不正确或有bug,可能会导致MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION命令无法正确传递给渲染器设备,从而无法控制媒体的播放位置。 3. 渲染器设备限制:一些渲染器设备可能对于MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION命令的支持有限。这可能是由于设备硬件的限制或软件实现的问题,导致无法准确地执行该命令。 4. 网络问题:DLNA协议是基于局域网的,如果网络不稳定或传输延迟较大,可能导致MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION命令无法及时被接收或传递给渲染器设备,从而无法起作用。 需要进一步调查和排查特定设备或场景下的具体问题,以确定导致MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION命令不起作用的原因。这可能需要查看设备的技术规格、固件版本、网络设置等信息,并进行测试和验证。 ### 回答3: DLNA协议中的MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION是用于设置媒体播放位置的命令。如果该命令不起作用,可能有以下几个原因: 1. 设备不支持:有些DLNA设备可能不支持此命令,因为DLNA协议是一个开放的标准,不同设备可能会实现不同的功能。如果设备不支持该命令,那么使用此命令就不会起作用。 2. 传输方式不支持:DLNA协议中有多种传输方式,例如HTTP、RTSP等。不同的传输方式可能对命令的支持程度不同。如果当前传输方式不支持设置媒体播放位置的命令,那么该命令就不会起作用。 3. 媒体文件类型限制:有些DLNA设备可能对支持的媒体文件类型有限制。如果播放的媒体文件类型不被设备支持,那么设置媒体播放位置的命令就不会起作用。 4. 设备或网络故障:有时DLNA设备或网络链路可能存在故障,导致命令无法正确传输。此时,可以尝试重新启动设备或重新连接网络,看是否可以解决问题。 总之,如果DLNA协议中的MEDIA_RENDER_TOCONTRPOINT_SET_MEDIA_POSITION命令不起作用,需要检查设备的兼容性、传输方式、媒体文件类型以及设备和网络的状态等因素,以确定可能的原因并进行相应的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

佳哥的技术分享

创作不易,谢谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值