原文:https://www.uedbox.com/post/65529/
投屏不止是投射视频,还包括常见的多媒体文件。但常见的还是视频投屏功能需求。
视频投屏大致分2种两种:
数字生活网络联盟 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 Universal Plug and Play 协议主要功能是提供了不同设备之间发现,控制,通信的功能。
上面三层主要是 UPnP 设备定义相关,最上层是由 UPnP 设备制造厂商定义的,包含厂家信息或独有的一些功能。
下面三层负责设备的发现,描述,控制等功能。
常见的DLNA库有Cling,CyberGarage,Intel UPnP stack,Platinum 等,具体见 http://www.upnp.org 。
抽象组件
在将 SSDP 等协议之前,我们需要理解 UPnP 协议中基本的几个抽象组件。
协议简述
寻址 Addressing
UPnP 基于 IP 协议,所以跟普通网络设备一样都需要一个唯一的 IP 地址。通常局域网内的 IP 都是通过 DHCP 自动分配,如果没有 UPnP 则会使用 LLA 来为自己找适合的 IP 地址。另外在运行过程中如果发现有 DHCP 服务了,那么就改用 DHCP 分配的地址。
发现 Discovery - SSDP
简单服务发现协议(SSDP,Simple Service Discovery Protocol)是一种应用层协议。它提供了在局部网络里面发现设备的机制。控制点可以通过使用简单服务发现协议,根据自己的需要查询在自己所在的局部网络里面提供特定服务的设备。设备也可以通过使用简单服务发现协议,向自己所在的局部网络里面的控制点宣告它的存在。
简单服务发现协议是在 HTTPU 和 HTTPMU 的基础上实现的协议。
HTTPU & HTTPMU
发现方式
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 有两种发现设备的方式:
接收 NOTIFY 信息
ssdp:alive 存活消息
1 2 3 4 5 6 7 8 |
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 离开消息
1 2 3 4 |
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 |
主动发送搜索消息
多播发送搜索消息
1 2 3 4 5 |
M-SEARCH * HTTP/1.1 ST: ssdp:all HOST: 239.255.255.250:1900 MAN: "ssdp:discover" MX: 5 |
多播搜索响应,返回内容跟 alive 消息区别不大。
1 2 3 4 5 6 7 |
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|
这里字段中最主要的是 serviceList 里的内容,它代表这个设备所能提供的 DLNA 服务。
serviceId | 服务表示符,是服务实例的唯一标识。 |
serviceType | 服务类型就是设备服务类型。 |
SCPDURL | 服务描述文档地址。 |
eventSubURL | 订阅该服务的URL。 |
服务描述文档
服务描述文档里的地址是需要我们通过当前设备 IP + SCPDURL 拼接而成,这里拼接需要注意分割符不要出现重复的/
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
...... // 内容太多 |
这里的话如果不是为了百分百实现 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 字段。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
2、UPnP Action Response-Success
如果服务端收到请求后成功执行后需要返回成功的回执,这个响应需要在 30 秒内完成,如果超过了可以先应答后续通过订阅事件机制返回。
这是小米盒子设置播放地址以后的回执,这里注意小米盒子它不管你投递过去的视频播放地址能否播放他都会返回如下结果。
1 2 3 4 5 |
|
3、UPnP Action Response-Error
失败了也必须返回错误回执,里面有可能带了具体的错误码。错误码的具体含义可以详见官方文档。
标准指令
设置播放地址
Post Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#SetAVTransportURI。
1 2 3 4 5 6 7 8 9 |
|
这里注意 CurrentURIMetaData 里又是一坨 XML,所以有部分数据会被转义掉,媒体信息内可以携带播放标题等内容。
1 2 3 4 5 6 7 8 9 |
|
播放 PlayPost Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#Play。
1 2 3 4 5 6 7 8 9 |
|
这里的话国产大部分 DLNA DMS 设备都是在 seturl 后自动播放,如果为了保证 seturl 后自动播放,可以在上面设置播放地址后在调用一次播放指令。
跳转进度 SeekPost Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#Seek。
1 2 3 4 5 6 7 8 9 10 |
|
获取播放进度 GetPositionInfoPost Header 里的 SOAPACTION 为 urn:upnp-org:serviceId:AVTransport#GetPositionInfo。
1 2 3 4 5 6 7 8 9 |
|
服务端返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
这里注意小米设备返回的进度总时长总是0,需要我们特殊处理下,而天猫盒子就没有这个问题。
除了这些指令外还有其他跟多的比如暂停,获取播放的媒体信息等功能。这些都是 AvTransport 服务提供的功能。而像 RenderingControl 服务又提供音量调整等功能,这里就不一一列举了。
事件 Eventing - GENA
服务运行时,可能改变有些状态变量的值,控制点需要的话就需要通过订阅或者鉴定多播来及时获取变化的信息,因为 DLNA 协议双方并没有保持一个长连接状态所以需要通过别的方式来相互通讯。
DMC 如果想要获取事件信息有两种方式:
发生了变化并且该变量的
属性为 yes 时,将会产生一个事件消息。如该状态变量的
属性为 yes ,则会多播这个事件。如果是 no 则通过 GENA 方式传递。GENA 订阅请求
事件订阅说白了就是给服务的 eventSubURL 发送一条包含回调 URL 和订阅期限的请求。回到上面 DDD 里,我拿到订阅地址并拼接起来。
http://10.23.167.129:49152/_urn:schemas-upnp-org:service:AVTransport_event
1 2 3 4 5 |
SUBSCRIBE _urn:schemas-upnp-org:service:AVTransport_event HTTP/1.1 HOST: http://10.23.167.129:49152 CALLBACK: NT: upnp:event TIMEOUT: Second-3600 |
订阅成功响应
如果订阅成功,则服务 30s 内返回如下的响应。其中 SID
为订阅标识符,必须以uuid开头。订阅成功后需要保存,后续续订和取消订阅均需要提供该标识符。此外还需要保存订阅期限 TIMEOUT: Second-3600
1 2 3 4 5 |
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 |
订阅失败响应
订阅失败将返回错误吗。比如 400 表示错误的标头之类。
续订
续订越订阅请求的区别就是:
取消订阅
1 2 3 |
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 里需要处理接收回调的逻辑。
1 2 3 4 5 6 7 8 |
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 2 3 4 5 6 |
|
播放的回调
1 2 3 4 5 6 7 8 9 10 11 12 |
|
停止的回调
1 2 3 4 5 6 7 8 9 10 11 12 |
|
整体 DLNA 的逻辑从上到下并没有太复杂的地方,我们主要的工作是在实现对不同 DLNA 服务端设备的兼容上,因为官方并没有给出标准的实现代码,各 DLNA 设备生产厂家对协议的实现上都是不同的,我们需要建立在大量 DLNA 设备的实际测试上才能保证手机控制功能的正常。