8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png
DLNA 投屏相关简析
aiyun 2020-5-11

视频投屏概念

投屏不止是投射视频,还包括常见的多媒体文件。但常见的还是视频投屏功能需求。

视频投屏大致分2种两种:

  1. 投射视频流到设备上。

    • Google -> Chromecast
    • Wi-Fi Alliance -> Miracast
    • Intel -> WiDi
    • Apple -> AirPlay
  2. 传递多媒体地址到设备上,由设备自己来展示或者播放。

    • Digital Home Working Group -> DLNA
    • Apple -> AirPlay

DLNA

简介

数字生活网络联盟 Digital Living Network Alliance,简称 DLNA 是一个由消费性电子、移动电话以及计算机厂商组成的联盟组织。

最初索尼于2003年6月创立 Digital Home Working Group,并于一年后更名为 DLNA 联盟。联盟成员包括飞利浦、三星、索尼、微软、英特尔和诺基亚等厂家。

2017年2月20日,数字生活网络联盟在其官网宣布:本组织的使命已经完成,已于2017年1月5日正式解散,相关的认证将移交给 SpireSpark 公司。

设备类型

大致家用网络设备种类,详见 https://en.wikipedia.org/wiki/Digital_Living_Network_Alliance。

DLNA 设备类型 功能
Digital Media Server(DMS)数字媒体服务器 提供了媒体文件的获取、录制、存储以及作为源头的设备。
Digital Media Player(DMP)数字媒体播放器 可查找并播放或输出任何由DMS所提供的媒体文件的设备。
Digital Media Controller(DMC)数字媒体控制器 作为遥控设备使用,可查找DMS上的多媒体文件,并指定可播放该多媒体文件的DMR进行播放或是控制多媒体文件上下传到DMS的设备。
Digital Media Renderer(DMR) 可接收并播放从DMC 投送过来的媒体文件。
Digital Media Printer(DMPr)数字媒体打印机 DMPr的打印机可以在DLNA网络架构下提供打印功能。

支持媒体类型

媒体类型 支持格式
图片 JPEG、PNG
音频 LPCM、MP3、AAC、WMA
视频 MPEG2、MPEG-4、WMV

具体协议

投屏功能的实现本质上就是局域网内的设备发现,交互与控制功能的实现。

我们先看下 DLNA 协议的一些技术分层。

这里最主要的是 UPnP 协议,它是 DLNA 设备发现与控制最主要的协议,也可以说 UPnP 就是 DLNA 的核心,类似于 AirPlay 里的 Bonjour 协议。

UPnP

UPnP Universal Plug and Play 协议主要功能是提供了不同设备之间发现,控制,通信的功能。

上面三层主要是 UPnP 设备定义相关,最上层是由 UPnP 设备制造厂商定义的,包含厂家信息或独有的一些功能。

下面三层负责设备的发现,描述,控制等功能。

常见的DLNA库有Cling,CyberGarage,Intel UPnP stack,Platinum 等,具体见 http://www.upnp.org 。

抽象组件

在将 SSDP 等协议之前,我们需要理解 UPnP 协议中基本的几个抽象组件。

  • 设备 Devices

    一个物理设备可能有多个根设备。根设备可以有多个嵌入式设备。因此 UPnP提供了一种非常灵活的逻辑安排。设备将有一个状态表来维护状态变量。设备上的所有操作都基于这些状态变量的状态。

  • 服务 Services

    代表设备能提供的功能,是依赖于设备存在的。

  • 控制点 Control points

    提供设备控制能力比如提供获取设备描述,服务列表,设备控制指令,服务订阅等功能。

协议简述

  • 发现 Discovery

    UPnP 设备通过 简单服务发现协议 Simple Service Discovery Protocol (SSDP) 来完成发现功能。

  • 描述 Description

    • 一旦找到了网络中的其他可用设备,我们就需要获得设备的具体服务细节。
    • 设备还应该向请求它的其他设备提供其功能细节
  • 控制 Control
    控制点可以通过SOAP消息调用服务提供的功能。
    简单对象访问协议(Simple Object Access Protocol, SOAP)是微软用于跨平台rpc的技术。
    设备服务一旦接收到消息,就必须对其进行操作

  • 事件 Eventing
    UPnP 遵循发布-订阅模型,将状态变量的变化通知给订阅状态的控制点。采用通用事件通知体系结构 General Event Notification Architecture (GENA) 来实现这个功能。

  • 浏览 Presentation
    UPnP 设备还能提供基于浏览器的界面。基于HTML的,表示URL是作为描述过程一部分提供的设备描述符文档(DDD)的一部分。

寻址 Addressing

UPnP 基于 IP 协议,所以跟普通网络设备一样都需要一个唯一的 IP 地址。通常局域网内的 IP 都是通过 DHCP 自动分配,如果没有 UPnP 则会使用 LLA 来为自己找适合的 IP 地址。另外在运行过程中如果发现有 DHCP 服务了,那么就改用 DHCP 分配的地址。

发现 Discovery - SSDP

简单服务发现协议(SSDP,Simple Service Discovery Protocol)是一种应用层协议。它提供了在局部网络里面发现设备的机制。控制点可以通过使用简单服务发现协议,根据自己的需要查询在自己所在的局部网络里面提供特定服务的设备。设备也可以通过使用简单服务发现协议,向自己所在的局部网络里面的控制点宣告它的存在。

简单服务发现协议是在 HTTPU 和 HTTPMU 的基础上实现的协议。

HTTPU & HTTPMU
  • HTTPU 协议是指在 UDP 上实现普通的 HTTP 传送协议。它一项自1999年至今,实验中的技,至今尚未被列入RFC之中。

  • HTTPMU 协议是指在 UDP 上实现 HTTP 协议的多址传送。

发现方式

SSDP 通过多播来实现设备发现功能,地址限定如下,端口也是固定的 1900。

协议 地址
IPv4 site-local address 239.255.255.250
IPv6 link-local FF02::C
IPv6 site-local FF05::C
IPv6 organization-local FF08::C
IPv6 global FF0E::C

SSDP 有两种发现设备的方式:

  1. 接收 NOTIFY 信息

    • ssdp:alive 存活消息

      NOTIFY * HTTP/1.1
      HOST: 239.255.255.250:1900
      CACHE-CONTROL: max-age=62
      LOCATION: http://10.23.167.129:49153/description.xml
      NT: urn:schemas-upnp-org:service:AVTransport:1
      NTS: ssdp:alive
      SERVER: Linux/3.10.61+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
      USN: uuid:F7CA5454-3F48-4390-8009-4c3d4ee64f1c::urn:schemas-upnp-org:service:AVTransport:1
      
    • ssdp:byebye 离开消息

      NOTIFY * HTTP/1.1
      HOST: 239.255.255.250:1900
      NTS: ssdp:byebye
      USN: uuid:F7CA5454-3F48-4390-8009-4c3d4ee64f1c::urn:schemas-upnp-org:service:AVTransport:1
      
  2. 主动发送搜索消息

    • 多播发送搜索消息

      M-SEARCH * HTTP/1.1
      ST: ssdp:all
      HOST: 239.255.255.250:1900
      MAN: "ssdp:discover"
      MX: 5
      
    • 多播搜索响应,返回内容跟 alive 消息区别不大。

      HTTP/1.1 200 OK
      LOCATION: http://10.23.169.140:61000/
      DATE: Thu, 06 Sep 2018 07:47:49 GMT
      EXT:
      SERVER: REALTEK, UPnP/1.0, Intel MicroStack/1.0.2718
      USN: uuid:31d5c4f9-8bd3-44bd-bb62-e352417eff66
      CACHE-CONTROL: max-age=1800ST: uuid:31d5c4f9-8bd3-44bd-bb62-e352417eff66
      
字段 含义
CACHE-CONTROL max-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
DATE 指定响应生成的时间
MAN ssdp:discover 表示是搜索消息
EXT 向控制点确认MAN头域已经被设备理解
LOCATION 包含根设备描述地址
SERVER 饱含操作系统名,版本,产品名和产品版本信息
ST 内容和意义与查询请求的相应字段相同,ssdp:all 表示搜索所有设备
USN 表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力。
HOST 固定的多播地址
NTS ssdp:alive 表示存活消息 ssdp:bye 表示离开消息
NT 设备的服务类型
MX 表示设备应该再5秒内响应,值是0到5之间随机。
设备类型及服务类型
设备类型 表示
UPnP_RootDevice upnp:rootdevice
UPnP_InternetGatewayDevice1 urn:schemas-upnp-org:device:InternetGatewayDevice:1
UPnP_WANConnectionDevice1 urn:schemas-upnp-org:device:WANConnectionDevice:1
UPnP_WANDevice1 urn:schemas-upnp-org:device:WANConnectionDevice:1
UPnP_WANCommonInterfaceConfig1 urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
UPnP_WANIPConnection1 urn:schemas-upnp-org:device:WANConnectionDevice:1
UPnP_Layer3Forwarding1 urn:schemas-upnp-org:service:WANIPConnection:1
UPnP_WANConnectionDevice1 urn:schemas-upnp-org:service:Layer3Forwarding:1
服务类型 表示
UPnP_MediaServer1 urn:schemas-upnp-org:device:MediaServer:1
UPnP_MediaRenderer1 urn:schemas-upnp-org:device:MediaRenderer:1
UPnP_ContentDirectory1 urn:schemas-upnp-org:service:ContentDirectory:1
UPnP_RenderingControl1 urn:schemas-upnp-org:service:RenderingControl:1
UPnP_ConnectionManager1 urn:schemas-upnp-org:service:ConnectionManager:1
UPnP_AVTransport1 urn:schemas-upnp-org:service:AVTransport:1

描述 Description - DDD & SDD

有可能考虑到 UDP 内传输的内容大小的考虑,上面发现时消息里的数据内容都不是很多,难以获得完整的 DLNA 设备信息。所以 UPnP 通过提供 XML 描述文件来详细记录设备信息,并把 XML 文件的地址放在消息体内,就是 Location 字段内的地址。

描述 XML 主要有两种类型:

  1. 设备描述文档 DDD
  2. 服务描述文档 SDD
设备描述文档
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
<presentationURL>/</presentationURL>
<friendlyName>小小的小米盒子</friendlyName>
<manufacturer>Xiaomi</manufacturer>
<manufacturerURL>http://www.xiaomi.com/</manufacturerURL>
<modelDescription>Xiaomi MediaRenderer</modelDescription>
<modelName>Xiaomi MediaRenderer</modelName>
<modelURL>http://www.xiaomi.com/hezi</modelURL>
<UPC>000000000013</UPC>
<UDN>uuid:F7CA5454-3F48-4390-8009-4c3d4ee64f1c</UDN>
<UID>-89219150</UID>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
<serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
<SCPDURL>/dlna/Render/AVTransport_scpd.xml</SCPDURL>
<controlURL>_urn:schemas-upnp-org:service:AVTransport_control</controlURL>
<eventSubURL>_urn:schemas-upnp-org:service:AVTransport_event</eventSubURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
<SCPDURL>/dlna/Render/ConnectionManager_scpd.xml</SCPDURL>
<controlURL>_urn:schemas-upnp-org:service:ConnectionManager_control</controlURL>
<eventSubURL>_urn:schemas-upnp-org:service:ConnectionManager_event</eventSubURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
<serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
<SCPDURL>/dlna/Render/RenderingControl_scpd.xml</SCPDURL>
<controlURL>_urn:schemas-upnp-org:service:RenderingControl_control</controlURL>
<eventSubURL>_urn:schemas-upnp-org:service:RenderingControl_event</eventSubURL>
</service>
<service>
<serviceType>urn:mi-com:service:RController:1</serviceType>
<serviceId>urn:upnp-org:serviceId:RController</serviceId>
<SCPDURL>/dlna/Render/RControl_scpd.xml</SCPDURL>
<controlURL>_urn:schemas-upnp-org:service:RenderingControl_control</controlURL>
<eventSubURL>_urn:schemas-upnp-org:service:RenderingControl_event</eventSubURL>
</service>
</serviceList>
<av:X_RController_DeviceInfo xmlns:av="urn:mi-com:av">
<av:X_RController_Version>1.0</av:X_RController_Version>
<av:X_RController_ServiceList>
<av:X_RController_Service>
<av:X_RController_ServiceType>controller</av:X_RController_ServiceType>
<av:X_RController_ActionList_URL>http://10.23.167.129:6095/</av:X_RController_ActionList_URL>
</av:X_RController_Service>
<av:X_RController_Service>
<av:X_RController_ServiceType>data</av:X_RController_ServiceType>
<av:X_RController_ActionList_URL>http://api.tv.duokanbox.com/bolt/3party/</av:X_RController_ActionList_URL>
</av:X_RController_Service>
</av:X_RController_ServiceList>
</av:X_RController_DeviceInfo>
</device>
<URLBase>http://10.23.167.129:49152/</URLBase>
</root>

这里字段中最主要的是 serviceList 里的内容,它代表这个设备所能提供的 DLNA 服务。

   
serviceId 服务表示符,是服务实例的唯一标识。
serviceType 服务类型就是设备服务类型。
SCPDURL 服务描述文档地址。
eventSubURL 订阅该服务的URL。
服务描述文档

服务描述文档里的地址是需要我们通过当前设备 IP + SCPDURL 拼接而成,这里拼接需要注意分割符不要出现重复的/

http://10.23.167.129:49152/dlna/Render/RenderingControl_scpd.xml

http://10.23.167.129:49152/dlna/Render/AVTransport_scpd.xml

<scpd xmlns="urn:schemas-upnp-org:service-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<actionList>
<action>
<name>GetBlueVideoBlackLevel</name>
<argumentList>
<argument>
<name>InstanceID</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
</argument>
<argument>
<name>CurrentBlueVideoBlackLevel</name>
<direction>out</direction>
<relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
</argument>
...... // 内容太多
<name>Brightness</name>
<dataType>ui2</dataType>
<allowedValueRange>
<minimum>0</minimum>
<maximum>100</maximum>
<step>1</step>
</allowedValueRange>
</stateVariable>
</serviceStateTable>
</scpd>

这里的话如果不是为了百分百实现 UPnP 规范,其实到也不用解析了,因为我们通过标准指令去操作大致是不会出问题的。比如如果设备提供了投屏功能,那么 setUrl,play,pause 等操作都是肯定支持的,也就无需再去获取投屏服务能提供那些功能了。

获取

控制 Control - SOAP

上面说到我们通过解析服务描述文档可以获取设备上服务的操作指令和参数要求。当我们通过 HTTP 请求相对应地址就可以完成控制命令的发送。

我们客户端与服务端进行交互使用的是 Simple Object Access Protocol,SOAP 简单对象访问协议。SOAP 与 SSDP 一样都是基于 HTTP 协议,不过区别在于 SOAP 的 Body 里是由内容的,里面存放的是想要操作的指令和参数,叫做 Action invocation。

在 UPnP 中,把 SOAP 控制/响应信息分成 3 种:

  1. UPnP Action Request

这里需要注意的是我们的 Post 请求头里面一定要带上 SOAPACTION 字段。

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <!--Body内部分根据不同动作不同-->
        <!--动作名称-->
        <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
            <!--输入参数名称和值-->
            <argumentName>in arg values</argumentName>
             <!--若有多个参数则需要提供-->
        </u:actionName>
    </s:Body>
</s:Envelope>
  1. UPnP Action Response-Success
    如果服务端收到请求后成功执行后需要返回成功的回执,这个响应需要在 30 秒内完成,如果超过了可以先应答后续通过订阅事件机制返回。

这是小米盒子设置播放地址以后的回执,这里注意小米盒子它不管你投递过去的视频播放地址能否播放他都会返回如下结果。

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:PlayResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">	           </u:PlayResponse>
    </s:Body> 
</s:Envelope>
  1. UPnP Action Response-Error
    失败了也必须返回错误回执,里面有可能带了具体的错误码。错误码的具体含义可以详见官方文档。
标准指令
  • 设置播放地址

    Post Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#SetAVTransportURI。

    <?xml version='1.0' encoding='utf-8' standalone='yes' ?><s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
        <s:Body>
            <u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">             <InstanceID>0</InstanceID>
                <CurrentURI>This is url</CurrentURI>
                <CurrentURIMetaData>&lt;?xml version='1.0' encoding='utf-8' standalone='yes' ?&gt;&lt;DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/"&gt;&lt;item id="id" parentID="0" restricted="0"&gt;&lt;dc:title&gt;This is title&lt;/dc:title&gt;&lt;upnp:artist&gt;unknow&lt;/upnp:artist&gt;&lt;upnp:class&gt;object.item.videoItem&lt;/upnp:class&gt;&lt;dc:date&gt;2018-09-06T18:23:54&lt;/dc:date&gt;&lt;res protocolInfo="http-get:*:*/*:*"&gt;This is url&lt;/res&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;
                </CurrentURIMetaData>
            </u:SetAVTransportURI>
        </s:Body>
    </s:Envelope>
    

    这里注意 CurrentURIMetaData 里又是一坨 XML,所以有部分数据会被转义掉,媒体信息内可以携带播放标题等内容。

    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/"><item id="id" parentID="0" restricted="0">
        <dc:title>This is title</dc:title>
        <upnp:artist>unknow</upnp:artist>
        <upnp:class>object.item.videoItem</upnp:class>
        <dc:date>2018-06-06T18:42:19</dc:date>
        <res protocolInfo="http-get:*:*/*:*">This is url</res>
        </item>
    </DIDL-Lite>
    
  • 播放 Play

    Post Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#Play。

    <?xml version="1.0" encoding="utf-8" standalone="no"?>
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <s:Body>
            <u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                <InstanceID>0</InstanceID>
                <Speed>1</Speed>
            </u:Play>
        </s:Body>
    </s:Envelope>
    

    这里的话国产大部分 DLNA DMS 设备都是在 seturl 后自动播放,如果为了保证 seturl 后自动播放,可以在上面设置播放地址后在调用一次播放指令。

  • 跳转进度 Seek

    Post Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#Seek。

    <?xml version="1.0" encoding="utf-8" standalone="no"?>
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <s:Body>
            <u:Seek xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                <InstanceID>0</InstanceID>
                <Unit>REL_TIME</Unit>
                <Target>00:33:33</Target>
            </u:Seek>
        </s:Body>
    </s:Envelope>
    
  • 获取播放进度 GetPositionInfo

    Post Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#GetPositionInfo。

    <?xml version="1.0" encoding="utf-8" standalone="no"?>
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <s:Body>
            <u:GetPositionInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                <InstanceID>0</InstanceID>
                <MediaDuration />
            </u:GetPositionInfo>
        </s:Body>
    </s:Envelope>
    

    服务端返回

    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <s:Body>
            <u:GetPositionInfoResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                <Track>0</Track>
                <TrackDuration>00:00:00</TrackDuration>
                <TrackMetaData></TrackMetaData>
                <TrackURI></TrackURI>
                <RelTime>00:00:00</RelTime>
                <AbsTime>00:00:00</AbsTime>
                <RelCount>2147483647</RelCount>
                <AbsCount>2147483647</AbsCount>
            </u:GetPositionInfoResponse>
        </s:Body>
    </s:Envelope>
    

    这里注意小米设备返回的进度总时长总是0,需要我们特殊处理下,而天猫盒子就没有这个问题。

除了这些指令外还有其他跟多的比如暂停,获取播放的媒体信息等功能。这些都是 AvTransport 服务提供的功能。而像 RenderingControl 服务又提供音量调整等功能,这里就不一一列举了。

事件 Eventing - GENA

服务运行时,可能改变有些状态变量的值,控制点需要的话就需要通过订阅或者鉴定多播来及时获取变化的信息,因为 DLNA 协议双方并没有保持一个长连接状态所以需要通过别的方式来相互通讯。

DMC 如果想要获取事件信息有两种方式:

  1. 监听多播获取

    服务的描述文件中的状态变量 <stateVariable> 发生了变化并且该变量的 <sendEvents> 属性为 yes 时,将会产生一个事件消息。如该状态变量的 <multicast> 属性为 yes ,则会多播这个事件。如果是 no 则通过 GENA 方式传递。

  2. 服务双方遵循通用事件通知体系 General Event Notification Architecture GENA 协议,通过 HTTP 传递 XML 信息内容来交互。

GENA 订阅请求

事件订阅说白了就是给服务的 eventSubURL 发送一条包含回调 URL 和订阅期限的请求。回到上面 DDD 里,我拿到订阅地址并拼接起来。

http://10.23.167.129:49152/_urn:schemas-upnp-org:service:AVTransport_event

SUBSCRIBE _urn:schemas-upnp-org:service:AVTransport_event HTTP/1.1
HOST: http://10.23.167.129:49152
CALLBACK: <http://10.23.167.222:233/callback>
NT: upnp:event
TIMEOUT: Second-3600
  1. SUBSCRIBE + publisher path + HTTP/1.1

    SUBSCRIBE 表示开始订阅或者续订,publisher path 发布路径就是 eventSubURL。

  2. HOST

    就是设备 IP 和端口,如果端口没有默认是 80。

  3. CALLBACK

    控制端的回调地址,这里可以看到如果想要实现订阅,等于双方都需要一个 HttpServer,比如 Java 上的 DLNA 库 Cling,它内部集成 Jetty 做 HttpServer。所以如果不像加没必要的依赖则完全可以不用实现订阅功能。

  4. NT

    GENA 规定标头。通知类型必须为 upnp:event。

  5. TIMEOUT

    订阅期限

订阅成功响应

如果订阅成功,则服务 30s 内返回如下的响应。其中 SID 为订阅标识符,必须以uuid开头。订阅成功后需要保存,后续续订和取消订阅均需要提供该标识符。此外还需要保存订阅期限 TIMEOUT: Second-3600

HTTP/1.1 200 OK
Date: Tue, 12 Jun 2018 13:22:25 GMT
Server: Linux/3.10.61+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
SID: uuid:8bd3-44bd-bb62-e35241
TIMEOUT: Second-3600
  1. Http 响应码

  2. 响应时间

  3. 设备系统等信息

  4. SID

    GENA 规定标头,订阅标识符,必须以 uuid 开头。

  5. TIMEOUT

    订阅期限,按照标准应大于 1800 秒。

订阅失败响应

订阅失败将返回错误吗。比如 400 表示错误的标头之类。

续订

续订越订阅请求的区别就是:

  • 续订没有 Callback 和 NT 字段。
  • 续订需要订阅成功的那个 SID。
取消订阅
UNSUBSCRIBE _urn:schemas-upnp-org:service:AVTransport_event HTTP/1.1
HOST: http://10.23.167.129:49152
SID: uuid:8bd3-44bd-bb62-e35241
接收订阅事件

我们订阅了事件以后,一旦有状态变化后,服务单会通过回调地址反响请求我们。所以在我们的 HttpServer 里需要处理接收回调的逻辑。

NOTIFY /callback HTTP/1.1
Host: 10.23.167.222:233
Content-Length: 325
Content-Type: text/xml"
NT: upnp:event
NTS: upnp:propchange   
SID: uuid:8bd3-44bd-bb62-e35241
SEQ: 1
  1. NOTIFY + 请求订阅时的回调地址 + HTTP/1.1

  2. 订阅方的 Host

  3. 消息体内 XML 内容的大小

  4. 消息体内 XML 的格式,必须为 text/xml。

  5. NT

    GENA标头,必须是 upnp:event。

  6. NTS

    GENA 标头,必须是 upnp:propchange。

  7. SID

    GENA 标头,订阅标识符。

  8. SEQ

    UPnP 标头,事件编号。初始化事件消息必须为零。发送给特定订阅者时每条消息必须加一,长度为8字节,超过则重置为一。

消息体

<?xml version="1.0" encoding="UTF-8"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
    <e:property>
        <variableName>new values</variableName>
    </e:property>
</e:propertyset>
  1. 命名空间必须为 urn:schemas-upnp-org:event-1-0。
  2. e:property 里是真正的回调信息。
播放的回调
<?xml version="1.0" encoding="UTF-8"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
    <e:property>
        <LastChange>
            <Event xmlns="urn:schemas-upnp-org:metadata-1-0/AVT/">
                <InstanceID val="0">
                    <TransportState val="PLAYING"/>
                </InstanceID>
            </Event>
        </LastChange>
    </e:property>
</e:propertyset>
停止的回调
<?xml version="1.0" encoding="UTF-8"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
    <e:property>
        <LastChange>
            <Event xmlns="urn:schemas-upnp-org:metadata-1-0/AVT/">
                <InstanceID val="0">
                    <TransportState val="STOPPED"/>
                </InstanceID>
            </Event>
        </LastChange>
    </e:property>
</e:propertyset>

代码实现注意点

整体 DLNA 的逻辑从上到下并没有太复杂的地方,我们主要的工作是在实现对不同 DLNA 服务端设备的兼容上,因为官方并没有给出标准的实现代码,各 DLNA 设备生产厂家对协议的实现上都是不同的,我们需要建立在大量 DLNA 设备的实际测试上才能保证手机控制功能的正常。

  • 设备存活时间不可靠

    我们不能依赖于 DLNA 设备的缓存时间,我们必须实现一个本地设备缓存机制,当多少时间内没有收到 alive 或者我们发出的搜索消息没有收到回应时,应及时移除过期设备。

  • 设备发现不了

    1. DLNA 设备的发现跟大部分零配置网络协议一样是基于 UDP 多播的,在局域网内 UDP 包有可能被路由器拦截导致发现不了。
    2. 如果手机种同时打开2个 DMC 开启设备搜索,有可能导致端口被占用而无法收到消息。
  • seturl 失败

    电视端返回 501 失败,注意 Sony 上一定要加 protocol 内容。

  • 小米设备上重复 seturl 导致第二次无法播放

    解决方式是在描述文件中我们能知道投屏的 DLNA 设备是小米,可以在判断是小米设备的时候,seturl 之前先 stop 一下,这样不管之前有没有视频在播放都不会影响下一次视频播放。

  • DLNA 理论上支持视频,音频,图像等投送,但限于不同设备的实现上的区别,目前尝试了在天猫盒子上是都可以显示的。

  • 理论上只要多媒体投送到 DLNA 设备上之后的一切都已经跟 DMC 无关了,包括视频播放卡顿,无法播放之类的。这里注意一下 UA 问题,很多盒子播放器的 UA 千奇百怪,没有或者变成了 iOS,这样可以会导致视频播放的问题。

  • DMS 向服务端发送指令时,POST 的头里一定要加上 SOAPACTION。

  • 为了干净的代码依赖,不加入 HttpServer 将导致无法订阅事件,这里影响最大的是播放进度获取和盒子端的视频播放暂停状态获取。所以我们需要实现一个定时轮询播放时长和状态的任务。

  • 小米设备上我们获取的播放时长信息里没有视频总时长,前端显示就需要特殊处理。

参考

最新回复 (0)
    • Ai云
      2
        立即登录 立即注册
返回