DLNA ( Digital Living Network Alliance ) 详解

时间:2024-04-17 08:21:26
目录
  1. 大前言
  2. DLNA 背景(来自某百科)
    1. 简介
    2. DLNA 的常见设备类型
    3. DLNA Guide 版本
    4. 通用名词解释
    5. DLNA 交互文档中自定义的名词解释
    6. DLNA 主要用到的技术
  3. UPnP 设备规范( UDA 1.0)
    1. 前言
    2. 简介
      1. What is UPnP™1 Technology?
      2. UPnP™ 论坛组
      3. 关于本文档
      4. Audience
      5. Required vs. recommended
      6. Acronyms
      7. References and resources
    3. 0. Addressing
      1. 0.1 Addressing: Determining whether to use Auto-IP
      2. 0.2 Addressing: Choosing an address
      3. 0.3 Addressing: Testing the address
      4. 0.4 Addressing: Periodic checking for dynamic address availability
      5. 0.5 Addressing: Device naming and DNS interaction
      6. 0.6 Addressing: Name to IP address resolution
      7. 0.7 Addressing references
    4. 1. Discovery
      1. 1.1 Discovery: Advertisement
      2. 1.2 Discovery: Search
      3. 1.3 Discovery references
    5. 2. Description
      1. 2.1 Description: Device description
      2. 2.2 Description: UPnP Device Template
      3. 2.3 Description: Service description
      4. 2.4 Description: UPnP Service Template
      5. 2.5 Description: Non-standard vendor extensions
      6. 2.6 Description: UPnP Template Language for devices
      7. 2.7 Description: UPnP Template Language for services
      8. 2.8 Description: Retrieving a description
      9. 2.9 Description references>
    6. 3. Control
      1. 3.1 Control: Protocols
      2. 3.2 Control: Action
      3. 3.3 Control: Query for variable
      4. 3.4 Control references
    7. 4. Eventing
      1. 4.1 Eventing: Subscription
      2. 4.1.1 Eventing: Subscribing: SUBSCRIBE with NT and CALLBACK
      3. 4.2 Eventing: Event messages
      4. 4.3 Eventing: UPnP Template Language for eventing
      5. 4.4 Eventing: Augmenting the UPnP Template Language
      6. 4.5 Eventing references
    8. 5. Presentation
      1. 5.1 Presentation references
  4. UPnP device protection
    1. 名词解释
    2. 动机
    3. 该协议所提供的能力
    4. 该协议不提供的能力
    5. 注意
  5. DLNA 实战前热身(DLNA 抓包)
    1. 说明
    2. Discovery
    3. Description
    4. Control
    5. Event
    6. Presentation
    7. 说明
  6. DLNA 协议
    1. DLNA 交互设计架构
    2. DLNA 设备模型
    3. 关于 DLNA 涉及的四大服务
    4. DLNA AVTransport 服务状态转移流程
    5. DLNA AVTransport 事件模型
    6. DLNA AVTransport 状态变量以及其作用和值域
    7. DLNA AVTransport 定义的 Action
    8. CSV ( Comma Separated Value ) List
    9. 参考文档
  7. DLNA V4.0 RemoteUserInterface HTML5(基于 HTML5 的远程用户界面 )
    1. 简介
    2. 名词解释
    3. HTML5 RUI 设备功能
    4. HTML5 RUI 设备能力
    5. 模型介绍
    6. RUI-H Server 与 Client
      1. RUI-H Server
      2. RUI-H Client
      3. DeviceProfile 的示例
      4. CompatibleUIs 的示例
    7. 一些交互指南
    8. 参考文档
  8. Platinum 解析
  9. 国内各个投屏软件缺陷公示以及分析 - 不完全指南
    1. 设备发现体验
    2. 爱奇异
    3. netease-cloud-music
    4. bilibili
  10. 用 Node.js 实现 DLNA 测试工具
  11. 参考资料
  12. 最后的最后
大前言

此文首先会先介绍 DLNA 的基础背景知识,然后着重介绍 UDA 1.0,完整的汉化版 UDA 1.0 协议,有需要的朋友可以参考一下,虽然不保证准确,但自己是要运用到工作开发中的,也不能对自己马虎。 接着我会总体介绍一下 DLNA 规范的结构,内容。针对目前国内做得比较好的一款应用对其进行抓包分析。 之后会选取现在的一个开源 Platinum 对其进行分析。 当然最后面还有我自己在开发过程中遇到的国内一些应用的缺陷共识(大家就当八卦看看吧)。

其实我也不太赞同完全将 Refrence 一股脑而看到低,一者我认为对于普通开发人员来说没必要,其实很多东西都是共通的,所以,越看到后面,越会发现,其实 Refrence 都是按照一个思路来写的。 其次 DLNA 其实也不属于一个热门技术,用到的技术原理也不深,现在虽然有标准,但各家出的产品也与标准有差异,甚至在某些应用场景标准还会要妥协,所以适可而止即可。

另外你可能在文章中会多次发现有如下字眼 "发现消息", "广播消息" 很多时候他们是名词,而不是一个动作,请结合上下文理解。

资料包 中包含本文用到的几乎所有 UPnP 方面资料,而且在持续收集中,请酌情下载。

下文中还有一些非官方的简写,也就是我自己为了方便描述文档自定义的,如果在其他地方不能找到该简写也不要在意,如果你找到了,大概是纯属巧合。此外对于我自己定义的简写我会在名词解释部分对其释义进行解释。

DLNA 背景(来自某百科)
简介

数字生活网络联盟(英语:Digital Living Network Alliance,简称:DLNA, 原名叫 Digital Home Working Group, DHWG)是一个由一群消费性电子公司在 2003 6 月份成立,由 intel 牵头、 开发和推广一套在认证标准主持下的多媒体设备中的数字媒体共享的互操作性的准则。 DLNA 认证的设备包括智能手机,平板电脑,PC,电视机和存储服务器。

该小组于 2004 年 6 月发布了第一套准则。该准则包含了几种现有的公共标准, 包括用于媒体管理以及设备发现和控制的通用即插即用(UPnP),以及广泛使用的数字媒体格式以及有线和无线网络标准。

DLNA 与电缆,卫星​​和电信服务提供商合作,在数据传输的每一端提供链路保护。 数字版权管理(DRM)安全性的额外层使广播运营商能够使消费者在多媒体设备上共享其内容,而不会遭受盗版风险。 DLNA 在 2014 年 3 月公开发布了 VidiPath 指南,最初称为 “ DLNA CVP-2 指南”。 VidiPath 使消费者能够在各种设备上观看订阅的电视内容,包括电视,平板电脑,电话,蓝光播放器,机顶盒(STB),个人计算机(PC)和游戏机,而服务提供商无需任何其他中间设备。

截至 2014 年 9月,超过 25,000 种不同的设备型号获得了 “DLNA认证” 状态,其包装上带有徽标并确认了它们与其他设备的互操作性。 据估计,到2017年,将在用户家中安装超过 60 亿种 DLNA 认证的设备,从数码相机到游戏机和电视。 截至 2015 年 6 月,该组织声称拥有 “200多家公司” 的会员资格。DLNA 在 2017 年 1 月 5 日在其网站上宣布: “该组织已履行其使命,并将解散为一个非营利性贸易协会”。

其认证计划将由俄勒冈州波特兰市的SpireSpark International进行。

DLNA 的常见设备类型
  • DMS 存储内容并将其提供给联网的数字媒体播放器(DMP)和数字媒体渲染器(DMR)。示例包括PC和网络附加存储(NAS)设备。
  • DMP 在数字媒体服务器(DMS)上查找内容,并提供播放和渲染功能。示例包括电视,立体声音响和家庭影院,无线监视器和游戏机。
  • DMR 播放数字媒体控制器(DMC)指示的内容,数字媒体控制器将从数字媒体服务器(DMS)查找内容。 示例包括电视,音频/视频接收器,视频显示器和音乐的远程扬声器。 单个设备(例如电视,A/V 接收器等)有可能同时充当 DMR(从 DMS 接收“推送”内容)和 DMP(从 DMS 提取“内容”)。
  • DMC 在数字媒体服务器(DMS)上查找内容,并指示数字媒体渲染器(DMR)播放内容。内容不会从 DMC 流过。 例如平板电脑,支持Wi-Fi的数码相机和智能手机。
  • DMPr 通常,具有打印功能的数字媒体播放器(DMP)和数字媒体控制器(DMC)可以打印到 DMPr。 示例包括联网的照片打印机和联网的多合一打印机。
  • M-DMS 存储内容并将其提供给有线/无线联网的移动数字媒体播放器(M-DMP)和数字媒体渲染器。 示例包括手机和便携式音乐播放器。
  • M-DMP 在数字媒体服务器(DMS)或移动数字媒体服务器(M-DMS)上查找和播放内容。 示例包括设计用于查看多媒体内容的移动电话和移动媒体平板电脑。
  • M-DMU 将内容发送(上传)到数字媒体服务器(DMS)或移动数字媒体服务器(M-DMS)。 示例包括数码相机和移动电话。
  • M-DMD 查找和存储(下载)来自数字媒体服务器(DMS)或移动数字媒体服务器(M-DMS)的内容。 示例包括便携式音乐播放器和移动电话。
  • M-DMC 在数字媒体服务器(DMS)或移动数字媒体服务器(M-DMS)上查找内容,并将其发送到数字媒体渲染器(DMR)。 示例包括个人数字助理(PDA)和移动电话。
  • M-NCF 在移动手持设备网络连接和家庭网络连接之间建立桥梁。
  • MIU 提供家庭网络和移动手持设备所需的媒体格式之间的内容转换。
DLNA Guide 版本
  • 1.5 2004/6 颁布,包含 DLNA 的体系结构和协议,媒体格式; 2 种设备类别( DMP,DMS);大约50种媒体格式配置文件。
  • 1.5 2006/3 颁布,包含 DLNA 的架构和协议,媒体格式和连接安全; 12 种设备类别和 5 种设备功能;约 250 种媒体格式配置文件
  • 2.0 2015/8 颁布,包含包括诸如 EPG,内容同步,RUI,WPS,媒体格式,预定录制,DRM之类的主题
  • 3.0 2015/8 颁布,延长响应时间,提高电源效率,支持HEVC
  • 4.0 2016/6 颁布,解决了PC,电视和移动设备之间的“不支持媒体格式”问题,同时支持超高清电视内容流
通用名词解释

注意,以下内容并不是严格按照 DLNA 协议规范中的文字翻译所得,有些来源于标准文档和各个百科。 意图详尽的在一句话内给读者一个大概映像,但具体的细节还是建议读者追本溯源。

  • AC 3( Audio Coding 3 ) AC3( 全称 Audio Coding3 音频编码 3 )是杜比数码的同义词,杜比数码是一种高级音频压缩技术, 它最多可以对6个比特率最高为448kbps的单独声道进行编码。杜比 AC-3 提供的环绕声系统由 5 个全频域声道和 1个 超低音声道组成,被称为 5.1 声道。 5 个声道包括左前、*、右前、左后、右后。低音声道主要提供一些额外的低音信息,使一些场景,如爆炸、撞击等声音效果更好。
  • ADU ( Application Data Unit ) 被作为 RTP 传输的单元,对于每个媒体流,ADU的定义都不同。 对于音频媒体流,ADU 通常是音频帧。 对于视频媒体流,ADU 通常是“切片”(例如,NAL单元), 或者在某些情况下是完整的视频图片。同样作为特殊情况,当使用 MPEG-2 TS 封装时, 每个 TS 数据包都是一个 ADU。
  • ACK ( Acknowledge ) 通常用于描述一个网络数据包被成功接收,而响应给用户的消息。
  • AP ( Access Point ) 无线局域网(WLAN)上经过特殊配置的网络基础结构设备。 接入点充当WLAN无线电信号的*发送器和接收器。 家庭网络中使用的AP通常是带有内置网络适配器,天线和无线电发射器的小型专用硬件设备。 这些AP支持Wi-Fi无线通信标准。
  • ARP ( Address Resolution Protocol ) TCP/IP 家族的一个协议,用于通过 IP 地址解析硬件地址, 如以太网地址( mac 地址)。
  • ATSC (Advanced Television Systems Committee) 高级电视系统委员会,数字电视广播的标准机构之一。
  • AV ( Audio with Video ) 泛指任何同时携带声音和图片的媒体内容。
  • AVP ( Audio/Video Profile ) 音频和视频的 RTP 配置文件,用于 RTP 媒体内容传输。
  • AVPF ( Extended Audio/Visual Profile for RTCP-based Feedback ) 基于 RTCP 反馈的一套可扩展音视频配置文件,用于 RTP 流媒体传输。
  • AVT ( AVTransport:1 Service ) AVTransport 服务是 UPnP 服务,它为常见的传输操作(如播放,停止,暂停,下一个,上一个和搜索)提供基于网络的控制。 AVTransport 服务规范是标准的 UPnP DCP。
  • BD_ADDR ( Bluetooth Device Address ) 蓝牙设备地址,由 48 bit ( 6 个字节 )标识的蓝牙硬件地址。地址的分配由蓝牙协议委员会定义。
  • PAN ( Personal Area Network ) 蓝牙个人局域网,蓝牙 Profile 中的一个协议。
  • BNEP ( Bluetooth Network Encapsulation Protocol ) 蓝牙封包协议,蓝牙 PAN 配置文件中的概念。
  • L2CAP ( Logical Link Control and Adaptation Protocol ) 蓝牙链路层的一个协议
  • BT 蓝牙的英文简称
  • BK ( Background User Priority ) QoS 等级标志, 标识 Background User 的优先级。
  • BE ( Best-Effort User Priority ) QoS 等级标志, 标识 BestEffort User 的优先级。
  • CDB ( Coded Data Buffer ) 用于 RTP Media 传输,在解码之前用于存储编码数据的缓冲区空间。
  • DCP ( Device Control Protocol ) 设备控制协议, 是一套 UPnP 论坛标准化的规范。 UPnP 工作委员会制定的相关规范通常由工作委员会的名称来标识。 例如,UPnP AV 1 DCP
  • CDS ( ContentDirectory:1 Service ) ContentDirectory 是 UPnP 的一个服务,它提供基于网络的发现的内容, ContentDirectory 服务规范是标准的 UPnP DCP.
  • CE ( Consumer Electronics ) 泛指放置在家中的一类设备,如 DVD, DVR, PVR, PDA, TV, 机顶盒,电话机,手机。
  • CMS ( ConnectionManager:1 Service ) ConnectionManager 是 UPnP 的一个服务,它提供传输协议和媒体格式方面的内容, ConnectionManager 服务规范是标准的 UPnP DCP.
  • CNAME ( Canonical Name ) RTP 媒体传输使用的,
  • CP ( UPnP Control Point ) UPnP 控制点。 详见 UPnP UDA V1.0 文档。
  • DA ( Device Architecture 1.0 ) 详见 UPnP UDA V1.0 详见文档。
  • SSRC ( Synchronization Source ) RTP 通信中的全球唯一标识符,其由 32bit 的随机数组成, 主要是用于区分哪些 RTP 包是属于同一会话的。
  • CSRC ( Contributing Source ) RTP 中的概念, CSRC 会由 Mixer 添加到 RTP 封包中,用于区分生产者(请参阅下文)。 Mixer 将一个带 SSRC 的标识符列表(此列表称为CSRC列表)插入到流媒体源中,用于生成在 RTP 头中带特殊字段的封包。 一个示例应用程序是音频会议,其中 Mixer 将谁在讲话的信息插入到 Outgoing 的封包中, 即使所有音频数据包包含相同的 SSRC 标识符(混合器的标识符),接收器也可以指示当前讲话者。
  • DDC ( Device Discovery and Control ) 《Interoperability Guidelines》中的一节定义了发现和控制设备的基础互操作性体系结构。
  • DH 1-5 ( Data - High Rates 1 through 5 ) 蓝牙 Spec 中物理层的高频通信封包类型。
  • DM 1-5 ( Data - Medium Rates 1 through 5 ) 蓝牙 Spec 中物理层的中频通信封包类型。
  • DHCP ( Dynamic Host Configuration Protocol ) 一个在网络中帮助网络节点,自动分配 IP 地址以及相关信息的协议。
  • DIDL ( Digital Item Declaration Language ) 一个 XML 规范文档,用于描述数字内容(文档)的元数据(metadata)的格式。
  • DIDL-Lite ( Digital Item Declaration Language - Lite ) 一个 XML 规范文档,用于在 UPnP 传输中描述数字内容(文档)的元数据(metadata)的格式。 是 DIDL 的子集和 UPnP 自定义的一些属性集合的并集,
  • DLNA ( Digital Living Network Alliance ) DLNA 协议,由 DLNA 协议委员会制定的协议。
  • DLNAQOS_UP ( DLNA QoS User Priority ) 一个又 DLNA 委员会定义的一套 QoS 标准,用于将基础的 802.1Q 用户优先级和 WMM 访问类别与 DLNA 通信类型相关联。
  • DMC ( Digital Media Controller ) DLNA 定义的一套用于家庭网络环境的设备, 被用于发现环境中的 DMS 和 DMR, 并让 DMS 和 DMR 建立连接。
  • DMP ( Digital Media Player ) DLNA 定义的一套用于家庭网络环境的设备, 用于连接暴露在网络环境中的 DMS 并展示以上内容。
  • DMPr ( Digital Media Printer ) DLNA 定义的一套用于家庭网络环境的设备, 可通过 DLNA 协议给环境中提供文档或者图片打印服务。
  • DMR ( Digital Media Renderer ) DLNA 定义的一套用于家庭网络环境的设备, 用于连接和展示由 DMC 配置内容。
  • DMS ( Digital Media Server ) DLNA 定义的一套用于家庭网络环境的设备, 用于向家庭的网络设备提供内容分发的服务。
  • DNS ( Domain Name System ) 是互联网的一项服务。它定义了一个协议,可用于将互联网中的域名和 IP 地址互相解析。
  • DSCP ( Differentiated Services (DiffServ) Code Point ) 差分服务代码点(Differentiated Services Code Point), IETF 于 1998 年 12 月发布了 Diff-Serv(Differentiated Service)的 QoS 分类标准。 它在每个数据包 IP 头部的服务类别 TOS 标识字节中,利用已使用的 6 比特和未使用的 2 比特,通过编码值来区分优先级。
  • DVB ( Digital Video Broadcasting ) 数字视频广播是于 1993 年建立起来的一种面向市场的数字服务体系结构,旨在推广基于 MPEG-2 编码国际标准的电视服务。 全世界已有 25 个国家超过 200 个组织加入到 DVB 项目中。数字视频广播是一种数字电视传输技术标准的总称。
  • VCD ( Video Compact Disc ) 是一种在光碟(Compact Disk)上存储视频信息的标准。 VCD 可以在个人电脑或 VCD 播放器以及大部分 DVD 播放器中播放。 VCD 标准由索尼、飞利浦、JVC、松下等电器生产厂商联合于1993年 [1] 制定,属于数字光盘的白皮书标准。
  • DVD ( Digital Versatile Disc ) 高密度数字视频光盘。它是比 VCD 更新一代的产品。 DVD 分别采用 MPEG—2 技术和 AC—3 标准对视频和音频信号进行压缩编码。 它可以记录 135 分钟的图像画面。与 VCD 不同的是它的图像清晰度可达 720 线。
  • DVR ( Digital Video Recorder ) 数字视频录像机,它是一套进行图像存储处理的计算机系统,具有对图像/语音进行长时间录像、录音、远程监视和控制的功能。 DVR 采用的是数字记录技术,在图像处理、图像储存、检索、备份、以及网络传递、
  • ES ( Elementary Stream ) Elementary Stream(基本码流)-由压缩器输出的用于传送 单路视音频信号的原始码流。
  • HD ( High Definition ) 是指垂直分辨率大于等于 720 的图像或视频,也称为高清图像或高清视频, 尺寸一般是 1280×720 和 1920×1080。“高清”的全称为“高清晰度”。
  • HDTV ( High Definition Television ) “高清晰度电视”,源于DTV(Digital Television)“数字电视”技术,采用数字信号,拥有最佳的视频、音频效果。 HDTV有三种显示格式,分别是:720P(1280×720,非交错式,场频为24、30或60), 1080i(1920×1080,交错式,场频60), 1080P(1920×1080,非交错式,场频为24或30)。
  • HID ( Home Infrastructure Device ) 家庭基础设施设备,指家中支持 DLNA 设备的一组设备, 她们实现了不同设备之间的交互, 这里的这一类设备统称 M-NCF 和 MIU。
  • HND ( Home Network Device ) 家庭网络设备,家庭网络环境中所有支持 DLNA 的设备的统称, 这里的设备包括: DMS, DMP, DMR, DMC, and DMPr.
  • HTTP ( Hyper Text Transfer Protocol ) 超文本传输协议,一个通过 Internet 传输文件的协议,由 HTTP 客户端和 HTTP 服务端构成。
  • ICMP ( Internet Control Message Protocol ) Internet 控制报文协议。它是 TCP/IP 协议簇的一个子协议,用于在 IP 主机、路由器之间传递控制消息。 控制消息是指网络通不通、主机是否可达、 路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
  • IP ( Internet Protocol ) 是 TCP/IP 体系中的网络层协议。设计 IP 的目的是提高网络的可扩展性: 一是解决互联网问题,实现大规模、异构网络的互联互通; 二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。 根据端到端的设计原则,IP 只为主机提供一种无连接、不可靠的、尽力而为的数据报传输服务。
  • IPV4 ( Internet Protocol version 4 ) 网际协议版本 4,又称互联网通信协议第四版,是网际协议开发过程中的第四个修订版本, 也是此协议第一个被广泛部署的版本。IPv4 是互联网的核心,也是使用最广泛的网际协议版本, 其后继版本为 IPv6,直到 2011 年,IANA IPv4 位址完全用尽时,IPv6 仍处在部署的初期。
  • TCP ( Transmission Control Protocol ) 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议, 由 IETF 的 RFC 793 定义。 TCP 旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。 TCP 假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。
  • UDP ( User Datagram Protocol ) Internet 协议集支持一个无连接的传输协议, 该协议称为用户数据报协议(UDP,User Datagram Protocol)。 UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。 RFC 768 描述了 UDP。
  • LAN ( Local Area Network ) 与终端用户接近的网络,例如在家庭或办公室内。
  • IGD ( Internet Gateway Device ) 一个多功能网络基础设施,它负责将本地网络的数据包路由或者桥接到 internet.
  • IPR ( Intellectual Property Rights ) 知识产权
  • JPEG ( Joint Photographic Experts Group ) 是JPEG标准的产物,该标准由国际标准化组织(ISO)制订,是面向连续色调静止图像的一种压缩标准。 后缀名为 \'jpg\' 或者 ‘jpeg’;
  • LFE ( Low Frequency Enhancement ) DVB 指定的一种传输低频声音信息的一种方式。
  • LMP ( Link Management Protocol ) 链路管理协议,用于链接设置和控制。 这里指的是蓝牙 spec 里面的链路管理协议。
  • LPCM ( Linear Pulse Code Modulation ) 线性脉冲编码调制,是一种非压缩音频数字化技术,是一种未压缩的原音重现, 在普通 CD、DVD 及其他各种要求最高音频质量的场合中已经得到广泛的应用。
  • M-DMC ( Mobile Digital Media Controller ) 支持 DLNA 的移动手持式设备, 其作用是查找 M-DMS 暴露的内容并将其与 DMR 的呈现能力匹配,并建立 M-DMS 与 DMR 之间的连接。
  • M-DMD ( Mobile Digital Media Downloader ) 支持 DLNA 的移动手持式设备, 具有从 M-DMS 下载内容的作用。
  • M-DMP ( Mobile Digital Media Player ) 支持 DLNA 的移动手持式设备, 其作用是查找 M-DMS 公开的内容并在本地呈现内容。
  • M-DMS ( Mobile Digital Media Server ) 支持 DLNA 的移动手持式设备, 负责在整个家庭中公开和分发内容。
  • M-DMU ( Mobile Digital Media Uploader ) 支持 DLNA 的移动手持式设备, 具有将内容上载到 M-DMS 的作用。
  • M-NCF ( Mobile Network Connectivity Function ) 支持 DLNA 的移动手持式设备, 它通过桥接 HND 和 MHD 设备之间的网络连接层来提供互操作性。
  • MF ( Media Formats ) 媒体格式类型
  • MHD ( Mobile Handheld Device ) “移动手持设备” 是一种设备类别,它将所有适用的 DLNA 设备类别与移动手持设备的环境特征(要求)组合在一起。 此设备类别中的设备类别为:M-DMS,M-DMP,M-DMD,M-DMU 和 M-DMC。
  • MHP ( Multimedia Home Platform ) 由 DVB 联盟制定的一种标准,DVB-MHP 的工作不仅覆盖应用程序接口 API, 而且还包括家庭数字网络(IHDN)和本地集群,其目的是标准化家庭平台,这对于未来成功应用交互式多媒体是很关键的。 它同时也可以看作是 DVB 纯广播工作到交互式 TV 应用的自然升级,推动了电视业务从模拟电视到数字化电视的过渡。
  • MIME ( Multipurpose Internet Mail Extension ) 多用途互联网邮件扩展类型, 是一种 Internet 协议,允许通过 Internet 作为电子邮件附件发送二进制文件。 是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。 MIME 这包括图形,照片,声音,视频文件和格式化的文本文档。
  • MIU ( Media Interoperability Unit ) 泛指一类 DLNA 设备,提供家庭网络和移动手持设备所需的媒体格式之间的内容转换。
  • MM ( Media Management ) 媒体内容管理,见 DLNA GuideLine 互操作性指南中的标题部分。
  • MPEG-2 ( Moving Picture Experts Group phase 2 ) MPEG-2 是 MPEG(Moving Picture Experts Group,运动图像专家组)组织制定的视频和音频有损压缩标准之一, 它的正式名称为 “基于数字存储媒体运动图像和语音的压缩标准”。 与 MPEG-1 标准相比,MPEG-2 标准具有更高的图像质量、更多的图像格式和传输码率的图像压缩标准。 MPEG-2 标准不是 MPEG-1 的简单升级,而是在传输和系统方面做了更加详细的规定和进一步的完善。 它是针对标准数字电视和高清晰电视在各种应用下的压缩方案,传输速率在 3 Mbit/s ~ 10 Mbit/s 之间。
  • MRCP ( MediaRenderer:1 Control Point ) UPnP 控制点,向 MRD 发出操作。
  • MRD ( MediaRenderer:1 Device ) MediaRenderer 设备(也称为 MediaRenderer )是 UPnP 设备,可提供基于网络的媒体内容呈现控制。 MediaRenderer 必须具有 RenderingControl 服务和 ConnectionManager 服务。 MediaRenderer 规范是标准的 UPnP DCP。
  • MSCP ( MediaServer:1 Control Point ) 向 MSD 发出 Action 的 UPnP AV 控制点。
  • MSD ( MediaServer:1 Device ) MediaServer 设备(也称为 MediaServer)是 UPnP 设备,可提供基于网络的内容发现。 MediaServer 必须具有 ConnectionManager 服务和 ContentDirectory 服务。 MediaServer 规范是标准的 UPnP DCP。
  • MT ( Media Transport ) 媒体内容传输,见 DLNA GuideLine 互操作性指南中的标题部分。
  • NAL Unit ( Network Abstraction Layer Unit ) 一个描述特定于 H.264 RTP 有效负载格式的术语。 它是一个 1 字节的标头和有效载荷字节字符串(参考: http://www.ietf.org/rfc/rfc3984.txt)。
  • NAP ( Network Access Point ) 蓝牙 PAN 规范中的概念, 是 Bluetooth 网络与其他网络( LAN 或 Internet ) 之间具有相当于桥接器或路由器功能的 Bluetooth 无线设备,可以是笔记本电脑、移动电话等。 Bluetooth 设备可以连接到这个网络访问点 NAP,并通过 NAP 访问其他网络上的共享资源,NAP 角色为连接的蓝牙设备提供了网络服务
  • NC (Networking and Connectivity ) 网络和连接,见 DLNA GuideLine 互操作性指南中的标题部分。
  • NC-PS ( Network Connectivity Power Saving ) 网络连接-节能
  • NID ( Network Infrastructure Device ) 在家庭网络中提供支持功能的设备,例如接入点,网桥,Internet网关,路由器和交换机,但不是规范性 HID 设备类别的成员。 这些设备可促进 DLNA 设备的良好用户体验,但目前仅在本文档中由附录 A 网络基础结构设备(NID)建议中的信息性建议涵盖。
  • NTSC* ( National Television Systems Committee ) 国家电视系统委员会(NTSC)是由美国联邦通信委员会(FCC)于 1940 年成立的标准化机构,旨在对模拟电视广播进行标准化。 该委员会提出的标准就是以 NTSC 自己命名的。 NTSC 成为北美,南美部分地区和亚洲使用的主要模拟电视系统。 该标准以及大多数模拟电视广播已于 2009 年 6 月 12 日被淘汰并停止使用,以支持数字电视。
  • OSI ( Open Systems Interconnection ) 开放系统互连是一个参考模型,说明了如何通过电信网络传输消息。 它不包括详细的界面。 相反,它可以作为网络创建者的指南,使他们的产品与其他创建者的产品兼容。 开放系统互连包括七个功能,称为功能层,当通过网络发送消息时应执行这些功能
  • PAL ( Phase Alternating Line ) 模拟电视信号的广播和接收的标准。
  • PANU ( PAN User ) PAN User 即 PAN 用户,PANU 角色相对于 GN 角色和 NAP 角色支持客户端角色,并支持与 GN / NAP 的连接
  • PC ( Personal Computer ) 个人电脑
  • PCR ( Program Clock Reference ) 请参考 MPEG-2 标准 13818-1.
  • PNG ( Portable Network Graphics ) " It is a coding standard for compression of still images (pictures).
  • PrCP ( Printer Control Point ) 向支持 PrD 的设备发送 Action 的 CP
  • PrD ( Printer Device ) 图像打印机设备是 UPnP 设备,可为打印提供基于网络的控制。 至少,打印机设备必须具有 PrintEnhanced 服务。 打印机设备规范是标准的 UPnP DCP
  • PS ( Program Stream ) 在这里通常代之 MPEG-2 AV 媒体流的一种格式,请参考 MPEG-2 中的定义。
  • PVR ( Personal Video Recorder ) 消费者个人 video 录影设备。
  • QoS ( Quality of Service ) 为网络提供可预测结果的能力提供保证。
  • RCS ( RenderingControl:1 Service ) RenderingControl 服务是 UPnP 服务,它提供基于网络的控制来调整渲染属性,例如音量,亮度,对比度和静音。 RenderingControl 服务规范是标准的 UPnP DCP。
  • RTP ( Real Time Protocol ) 提供端到端网络传输功能以传输实时数据的媒体传输, 例如 AV。 它提供诸如有效负载类型识别,序列编号,时间戳和交付监视之类的服务。
  • RTCP ( Real-time Transport Control Protocol ) 是实时传输协议(RTP)的一个姐妹协议。RTCP 由 RFC 355 0定义(取代作废的RFC 1889)。 RTP 使用一个 偶数 UDP port ;而RTCP 则使用 RTP 的下一个 port,也就是一个奇数 port。 RTCP 与 RTP 联合工作,RTP 实施实际数据的传输,RTCP 则负责将控制包送至电话中的每个人。 其主要功能是就 RTP 正在提供的服务质量做出反馈。
  • RTSP ( Real Time Streaming Protocol ) RTSP(Real Time Streaming Protocol),RFC2326,实时流传输协议,是 TCP/IP 协议体系中的一个应用层协议,由哥伦比亚大学、网景和 RealNetworks 公司提交的 IETF RFC 标准。 该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。 RTSP 在体系结构上位于 RTP 和 RTCP 之上,它使用 TCP 或 UDP 完成数据传输。 HTTP 与 RTSP相比,HTTP 请求由客户机发出,服务器作出响应;使用 RTSP 时,客户机和服务器都可以发出请求,即 RTSP 可以是双向的。 RTSP 是用来控制声音或影像的多媒体串流协议,并允许同时多个串流需求控制,传输时所用的网络通讯协定并不在其定义的范围内, 服务器端可以自行选择使用 TCP 或 UDP 来传送串流内容,它的语法和运作跟 HTTP 1.1 类似,但并不特别强调时间同步,所以比较能容忍网络延迟。 而前面提到的允许同时多个串流需求控制(Multicast),除了可以降低服务器端的网络用量,更进而支持多方视讯会议(Video Conference)。 因为与 HTTP1.1 的运作方式相似,所以代理服务器〈Proxy〉的快取功能〈Cache〉也同样适用于 RTSP,并因 RTSP 具有重新导向功能, 可视实际负载情况来转换提供服务的服务器,以避免过大的负载集中于同一服务器而造成延迟。
  • SCPD ( Service Control Protocol Description ) 这是描述 UPnP 服务的 XML 编码文件。 这也称为服务描述文件。
  • SDES ( Session Description Item ) RTCP 中的概念
  • SDP ( Session Description Protocol ) RTCP 中的概念
  • SOAP ( Simple Object Access Protocol ) 基于XML的消息传递协议,用于通过网络交换服务请求和响应。
  • SPTS ( Single Program Transport Stream ) RTCP 中的概念
  • SSDP ( Simple Service Discovery Protocol ) 这里指 UPnP 中使用的设备发现协议。
  • TIFF ( Tagged Image File Format ) 是一种灵活的位图格式,主要用来存储包括照片和艺术图在内的图像,最初由 Aldus 公司与微软公司一起为 PostScript 打印开发。 TIFF 与 JPEG 和 PNG 一起成为流行的高位彩色图像格式。 TIFF 格式在业界得到了广泛的支持,如 Adobe 公司的 Photoshop、The GIMP Team 的 GIMP、Ulead PhotoImpact 和 Paint Shop Pro 等图像处理应用、 QuarkXPress 和 Adobe InDesign 这样的桌面印刷和页面排版应用,扫描、传真、文字处理、光学字符识别和其它一些应用等都支持这种格式。 从 Aldus 获得了 PageMaker 印刷应用程序的 Adobe 公司控制着TIFF规范。
  • TS ( Transport Stream ) 在文中指 MPEG-2 AV 流的格式,现在指 TS 流文件,是一种 DVD 的文件格式, MPEG2-TS 格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。
  • TTS ( Timestamped Transport Stream ) 带时间戳的流。
  • UP ( User Priority ) WMM 规范中 QoS 控制字段的 3 位字段,以及 802.1Q 的 VLAN 标签报头中的标签控制信息字段,用于定义数据包的相对优先级。 请参考:( https://standards.ieee.org/getieee802/index.html )( https://www.wi-fi.org/ 对 WMM 的定义 )(这两个文件都是付费的)
  • URI ( Uniform Resource Identifier ) W3C 对 Internet 上当前对象和将来对象的名称和地址语法的编码。 URI 以其最基本的形式,由方案名称(例如文件,http,ftp,news,mailto,gopher)组成,后跟冒号,其后是路径,其性质由其之前的方案确定。 URI 是 URN,URL 和所有其他统一资源标识符的总称。
  • URL ( Uniform Resource Locator ) 同一资源标识符。
  • UTF( Unicode Transformation Format ) Unicode 转换格式, 其中,UTF-8 是 UTF 中最常用的转换格式,是 UNICODE 的一种变长字符编码,由 Ken Thompson 于1992年创建。 现在已经标准化为 RFC 3629。UTF-8 用 1 到 6 个字节编码 UNICODE 字符。
  • VI( Video User Priority ) A priority-based QoS level for Video User Priority.
  • VLAN 虚拟局域网(VLAN)是一组逻辑上的设备和用户,这些设备和用户并不受物理位置的限制,可以根据功能、部门及应用等因素将它们组织起来, 相互之间的通信就好像它们在同一个网段中一样,由此得名虚拟局域网。 VLAN是一种比较新的技术,工作在 OSI 参考模型的第 2 层和第 3 层,一个 VLAN 就是一个广播域,VLAN 之间的通信是通过第3层的路由器来完成的。
  • VO ( Voice User Priority ) 一个基于语音用户优先级的 QoS 级别。
  • WAN ( Wide Area Network ) 万维网,通常只整个 internet。
  • WMM Wireless Multimedia Extensions (WME), 更为人所熟知的名字是 Wi-Fi Multimedia (WMM), 是基于 IEEE 802.11e 标准的 Wi-Fi Alliance 互操作性认证。 它为 IEEE 802.11 网络提供基本的服务质量(QoS)保证。 WMM 根据四个访问类别(Access Categories AC)对流量进行优先级排序:语音(AC_VO),视频(AC_VI),尽力而为(AC_BE)和背景(AC_BK)。 但是,它不能保证吞吐量。 它适用于需要 QoS 的明确定义的应用程序,例如 Wi-Fi 电话(VoWLAN)上的IP语音(VoIP)。 参考:传送门
  • XML ( Extensible Markup Language ) 是一种文本标记语言,用于描述数据之间的结构和信息交换。
DLNA 交互文档中自定义的名词解释
  • ΔTpcr
  • Authentication
  • Authorization
  • Bearer
  • Cacheable Content
  • Channel
  • Content
  • Content Binary
  • Content Transformation
  • Content Source
  • Content Receiver
  • Decodeer Friendly Point
  • Device Capability
  • Device Category
  • Device Class
  • Device Function
  • Device Option
  • Device Type
  • DLNA QOS
  • Full TS
  • Ideal Network Conditioins
  • Instance state variable
  • interactive Transfer
  • kHz
  • Link-level bearer
  • Media Class
  • Media Collection File
  • Media Format
  • Media Stream
  • Media Transfer Mode
  • Native Device
  • Network De-jitter Buffer
  • Non-Cacheable Content
  • Partial SPTS
  • Post-decoder Buffer
  • Pre-decoder Buffer
  • Printer
  • Printing Controller
  • Receiver Buffer
  • Receiving Endpoint
  • Rendering Endpoint
  • RTP Media Transport
  • RTP Session
  • RTP Stream
  • RTSP Session
  • Scalling
  • Serving Endpoint
  • Streaming Transfer
  • System Usage
  • Test Conditions
  • Transfercoding
  • Transrating
  • UPnP
  • Virtual Instance
  • Virtual Server
  • VLAN Tag
DLNA 主要用到的技术
  • Conectivity Ethernet*, 802.11, and Bluetooth(好像很少看到蓝牙支持的场景)
  • Networking IPv4 Suite
  • Device Discovery and Control UPnP* Device Architecture v1.0(见前面介绍 UPnP 的部分,已经说得很详细了)
  • Media Management and Control UPnP AV v1 and UPnP Printer:1
  • Media Formats Required and Optional Format Profiles(后续有时间补充)
  • Media Transport HTTP (必须具备) and RTP (可选)(现在 HTTP2 和 HTTP3 已经开始流行了,一个是为了加快响应速度,二者减轻网络压力这两个都最好支持上)
UPnP 设备规范( UDA 1.0)
前言

本文档合并了几个单独的文档,这些文档构成了 UPnP 设备体系结构 1.0 版的整体,包括以前针对 AutoIP,SSDP,HTTPU/MU,FXPP 和 GENA 的单独规范, 以及《 UPnP Vendor Implementation Guide》中的说明。 它不引入任何新的技术要求,而仅构成编辑上的说明。

简介
What is UPnP™1 Technology?

UPnP™技术定义了一种架构,该架构用于构建各种形式的智能设备,无线设备和 PC 设备普遍对v等网络连接。 它旨在为家庭,小型企业,公共场所或连接到 Internet 的临时或非托管网络带来易于使用,灵活,基于标准的连接。 UPnP技术提供了一种分布式,开放的网络体系结构,该体系结构利用 TCP/IP 和 Web 技术来实现无缝的邻近网络, 以及在联网设备之间进行控制和数据传输。

UPnP Device Architecture(UDA)不仅仅是即插即用外围设备模型的简单扩展。 它的设计旨在使能众多厂商支持零配置,“无感” 联网以及服务的自动发现。 这意味着设备可以动态加入网络,获取IP地址,申明其功能以及了发现了解其他设备的存在和功能。 最终,设备也可以自动顺畅地离开网络,而不会留下任何不必要的状态。

UPnP 体系结构利用了普遍的 Internet 协议技术,例如 IP,TCP,UDP,HTTP 和 XML。 像 Internet 合约基于声明性的有线协议一样,UPNP 以 XML 表示并通过 HTTP 协议进行通信。 使用互联网协议是 UDA 的一个不错的选择,因为它具有跨不同物理媒体的可靠能力, 可以实现现实世界中的多供应商互操作,并且可以与 Internet 以及许多家庭和办公室内部网实现协同作用。 UPnP体系结构已明确设计为适应这些环境。 此外,作为一个桥接万物的协议, UDA 还解决了设备因为成本,技术或传统限制设备无法获取 IP 时的场景

为什么 UPnP 技术是 “普遍性” 的? 没有设备驱动程序, 使用通用协议。 UPnP 网络是独立的(媒体)。 UPnP 设备可以使用任何编程语言在任何操作系统上实现。 UDA 在程序/接口设计上没有设置任何限制。 操作系统供应商可以为其客户的需求实现合适的 API。

UPnP™ 论坛组

UPnP 论坛则是一项行业计划,旨在使来自许多不同的供应商的独立设备和 PC 之间实现轻松而强大的连接。 UPnP 论坛寻求开发一套用于描述设备协议的标准。基于 XML 的设备的标准架构用于在可扩展网络环境中,实现设备到设备互相操作。

The UPnP Implementers Corporation(UIC)由UPnP 论坛中的各个行业成员公司组成, 采用统一的设备互连技术标准以对这些设备进行测试和认证。 UIC 开发和管理测试和认证过程,管理 UPnP 徽标的分发,并向 UIC 成员和其他有关 UPnP 设备认证的相关方提供相关信息。 UPnP 设备认证对拥有 UPNP 设备和已支付 UIC 会费的 UPnP 论坛和 UIC 的成员开放。 有关更多信息,请参见 http://www.upnp-ic.org。

UPnP论坛已经建立了专门领域知识领域的工作委员会。 这些工作委员会负责创建建议的设备标准,构建示例实现以及构建适当的测试套件。 本文档指出了UPnP论坛工作委员会职权范围内的特定技术决策。

UPnP 供应商可以建立具有互操作性以及共享知识产权和徽标计划的好处的合规设备。 除了徽标程序之外,供应商还可以构建遵循本文定义的 UPnP 设备架构的设备,而无需使用正式的标准程序。 如果供应商制造非标准设备,则他们将决定由UPnP论坛工作委员会决定的技术决定(没看懂,原文贴后面了)。If vendors build non-standard devices, they determine technical decisions that would otherwise be determined by a UPnP Forum working committee.

关于本文档

本文包含的 UDA(以前称为DCP框架)定义了控制器或控制点与设备之间进行通信的协议。 为了进行发现,描述,控制,事件和演示,UPnP设备体系结构使用以下协议栈():

在最上层,消息在逻辑上仅包含有关其设备的 UPnP 供应商特定信息。 在第二层, UPnP 论坛工作委员会定义补充供应商的内容的信息。 来自以上各层的消息以特定于 UPnP 的协议托管, 例如本文档中定义的简单服务发现协议(SSDP)和通用事件通知体系结构(GENA),以及所引用的其他协议。 以上消息是通过 HTTP(通过 UDP 的多播或单播来传输)或通过在 TCP 上的 HTTP 传递的。 最终,以上所有消息均通过 IP 网络传递。 本文档的其余部分详细描述了每个协议层的内容和格式。 作为参考,以上[方括号]中的颜色表示在本文档中哪个协议定义了特定的消息组件。

UDA 定义了两种设备的一般分类:受控设备(或简称为“设备”)和控制点。 受控设备充当服务器的角色,响应来自控制点的请求。 控制点和受控设备都可以在包括个人计算机和嵌入式系统在内的各种平台上实现。 被控设备,控制点两者可以在同一网络终端上同时运行。

UPnP 网络的基础是 IP 寻址(IP addressing)。 每个设备必须具有动态主机配置协议(DHCP)客户端,并在设备首次连接到网络时搜索 DHCP 服务器。 如果 DHCP 服务器可用,则该设备必须使用分配给它的IP地址。 如果没有可用的DHCP服务器,即网络不受管理,则设备必须使用自动 IP ( AutoIP )来获取地址。 简而言之,“自动IP” 定义了设备如何从一组保留的地址中智能地选择 IP 地址, 以及如何在托管网络和非托管网络之间轻松移动。 如果在进行 DHCP 事务处理期间,设备(例如,通过DNS服务器或DNS转发)获得了域名, 则设备应在后续的网络操作中使用该名称; 否则,设备应使用其IP地址。

给定一个IP地址,UPnP 网络中的步骤 1 是发现( discovery )。 将设备添加到网络后,UPnP 发现协议允许该设备将其服务通告给网络上的控制点。 同样,当将控制点添加到网络时,UPnP 发现协议允许该控制点搜索网络上感兴趣的设备。 两种情况下的基本交换是发现消息,该发现消息包含关于设备或其服务之一的一些基本细节, 例如,设备的类型,标识符以及指向更详细信息的指针(URL)。 下面有关发现的部分说明了设备如何播发,控制点搜索方式以及发现消息格式的详细信息。

UPnP 组网中的第 2 步为描述( description )。 控制点发现设备后,控制点对设备的了解仍然很少。 为了使控制点了解有关设备及其功能的更多信息,或与设备进行交互, 控制点必须从设备在发现消息中提供的 URL 检索设备的描述。 设备可能包含其他逻辑设备以及功能单元或服务, 并且包括特定于供应商的制造商信息。例如型号名称和编号,序列号,制造商名称,特定于供应商的网站的 URL 等 (设备的 UPnP 描述以 XML 表示)。 该描述还包括任何嵌入式列表。设备或服务,以及用于控制,事件和演示的 URL。 对于每个服务,描述都包括服务响应的命令或动作的列表,以及每个动作的参数或自变量。 服务描述还包括(状态)变量列表;这些变量在运行时对服务状态进行建模,并根据其数据类型,范围和事件特征进行描述。 下文“描述”部分说明了如何描述设备以及控制点如何检索这些描述。

UPnP网络中的第三步 3 是控制。 在控制点检索到设备的描述之后,控制点可以将操作发送到设备的服务。 为此,控制点将适当的控制消息发送到服务的控制 URL(在设备描述中提供)。 控制消息还使用简单对象访问协议(SOAP)用 XML 表示。 像函数调用一样,响应于控制消息,服务将返回任何特定于操作的值。 动作的效果(如果有的话)是通过描述服务运行时状态的变量的变化来建模的。 下面有控制的部分的操作说明,状态变量和控制消息格式的描述。

UPnP 网络中的步骤 4 正在事件(eventing)。 服务的 UPnP 描述包括服务响应的操作列表和在运行时对服务状态建模的变量列表。 这些变量更改时,服务将发布更新,并且控制点可以订阅以接收此信息。 该服务通过发送事件消息( eventing msg )来发布更新。 事件消息包含多个状态变量的名称以及这些变量的当前值。这些消息也以XML表示。 当控制点首次订阅时,会发送一个特殊的初始事件消息。 该事件消息包含所有事件变量的名称和值,并允许订户初始化其服务状态模型。 为了支持具有多个控制点的方案,事件设计为使所有控制点均等地了解任何操作的效果。 因此,并且无论状态变量为何更改,被控点因向所有订户发送所有事件消息。 订阅者会受到所有已经变更的消息。下面有事件的部分说明了订阅和事件消息的格式。

UPnP 网络中的第 5 步是展示(presentation)。 如果设备具有用于展示的 URL,则控制点可以从该 URL 检索页面, 将该页面加载到浏览器中,并根据页面的功能,允许用户控制设备和/或查看设备状态。 完成这些操作的程度取决于演示页面和设备的特定功能。 下面有 “演示” 的部分说明了检索演示页面的协议。

Audience

本文档的读者包括 UPnP 设备供应商(vendor),UPnP 论坛工作委员会的成员以及需要了解 UPnP 协议技术细节的其他任何人。

本文档假设读者熟悉 HTTP,TCP,UDP,IP 协议家族; 本文档未尝试解释它们。 本文档还假定大多数读者将不熟悉 XML,尽管它不是 XML 教程,但鉴于 XML 在 UPnP 设备体系结构中的中心地位,将详细解决与 XML 有关的问题。 本文档不假定读者对各种编程或脚本语言的理解。

Required vs. recommended

在本文档中,特性被描述成 Required(要求), Recommended(推荐), or Optional(可选) 三个等级。

  • Required (or Must or Shall)

    必须实现这些基本功能,以符合 UPnP 设备架构(UDA)。 短语“不应该(shall not)”和“不得( must not )”表示禁止的行为,如果执行,则表示该实现不符合要求。

  • Recommended (or Should).

    这些功能增加了 UPnP 设备体系结构支持的功能,应予以实现。 推荐的功能通常利用 UPnP 设备架构的功能,而不会造成较大的成本增加。 请注意,对于合规性测试,如果实施了推荐功能,则它必须满足指定要求才能符合这些准则。 某些推荐功能可能会在将来成为要求。 短语“不应”表示允许但不建议的行为

  • Optional (or May).

    UPnP 设备体系结构既不需要也不推荐这些功能,但是如果实现了该功能,则必须满足指定的要求才能符合这些准则。 这些功能将来不太可能成为必需。

Acronyms
  • ARP Address Resolution Protocol
  • CP Control Point
  • DCP Device Control Protocol
  • DHCP Dynamic Host Configuration Protocol
  • DNS Domain Name System
  • GENA General Event Notification Architecture
  • HTML HyperText Markup Language
  • HTTP Hypertext Transfer Protocol
  • HTTPU HTTP (Unicast over UDP)
  • HTTPMU HTTP (Multicast over UDP)
  • SOAP Simple Object Access Protocol
  • SSDP Simple Service Discovery Protocol
  • UDA UPnP™ Device Architecture
  • UPC Universal Product Code
  • URI Uniform Resource Identifier
  • URL Uniform Resource Locator<
  • URN Uniform Resource N me
  • UUID Universally Unique Identifier
  • XML Extensible Markup Language
References and resources

本文档的每个部分都包含有关特定主题资源的其他信息。

0. Addressing

寻址是 UPnP™ 网络的第 0 步。 通过寻址,设备可以获得网络地址。 寻址成功才能使控制点在步骤 1 (discovery)在找到有感兴趣的设备, 步骤 2(description)在控制点了解设备功能, 步骤 3(control)在控制点向设备发送命令, 步骤4(eventing)控制点监听设备状态变化, 步骤5(presentation)控制点显示设备用户界面。

UPnP 网络的基础是 IP 寻址。 每个本身不实现 DHCP 服务器的 UPnP 设备都必须具有动态主机配置协议(DHCP)客户端, 并在设备首次连接到网络时搜索 DHCP 服务器(如果设备本身实现DHCP服务器,则它可以分配本身就是它所控制的池中的地址)。 如果 DHCP 服务器可用,即管理网络,则该设备必须使用分配给它的 IP 地址。 如果没有DHCP服务器可用,即网络不受管理; 设备必须使用自动 IP 寻址(Auto-IP)来获取地址。

此处定义的自动 IP 是定义设备(a)确定 DHCP 是否不可用,以及(b)从一组本地链路 IP 地址中智能地选择 IP 地址。 这种地址分配方法使设备可以轻松地在托管和非托管网络之间移动。

本节定义了自动 IP 的操作。 本节中描述的操作在下面列出的参考文档中进行了详细说明。 如果本文档和参考文档之间存在冲突,则始终以参考文档为准。

0.1 Addressing: Determining whether to use Auto-IP

支持自动 IP 并的设备被配置为首先通通过 DHCP 发送 DHCPDISCOVER 消息请求 IP 地址开始。 此 DHCP 客户端应侦听 DHCPOFFER 的时间量取决于厂商自己的实现。 如果在此期间收到 DHCPOFFER,则设备必须进行继续动态地址分配过程。 如果没有收到有效的 DHCPOFFER,则设备开始自动配置 IP 地址流程。

0.2 Addressing: Choosing an address

为了使用自动 IP 自动配置 IP 地址,设备需要实现有关的算法来选择 169.254.0.0/16 范围内的地址。 此范围中的前 256 个地址和后 256 个地址被保留,不能使用。

经过算法选择的地址必须测试所选的地址在局域网中的唯一性,以确定该地址是否已被使用。 如果该地址正被另一个设备使用,则必须选择并测试另一个地址,直到达到厂商自己定义的最大重试次数。 当多个设备试图分配地址时,应将地址选择随机化以避免冲突。 建议设备使用伪随机算法(分布在从 169.254.1.0 到 169.254.254.255 的整个地址范围内)选择地址, 以最大程度地减少同时加入网络的设备选择相同地址的可能性,并在检测到冲突时按相同顺序选择备用地址。 可以使用设备的以太网硬件 MAC 地址来作为伪随即算法的种子。

0.3 Addressing: Testing the address

要测试所选的地址,设备必须使用地址解析协议(ARP)探针。 ARP 探针是一个 ARP 请求,其中设备 IP 地址用作发件人的 IP 地址,而发件人的 MAC 地址设置为 0s。 ( hwsrc == 自己 MAC 地址, psrc == 0.0.0.0, pdst == 探测的 IP 地址,也就是自己随机选择出来的 IP 地址 ) 然后,设备将侦听 ARP 探针或针对同一 IP 地址的其他 ARP 探针的响应。 如果看有一个使用设备当前选择的 IP 的 ARP 数据包,则设备必须考虑使用其他地址。 可以重复进行 ARP 探测,以确保该地址尚未使用。 建议每两秒钟发送四次探测。

成功配置链接本地地址后,设备应发送两个自己的 ARP 报文,间隔 2 秒,这次将配置的 IP 填充为了发送方 IP 地址。 这些 ARP 的目的是确保网络上的其他主机没有因为以前可能使用相同地址而遗留的陈旧 ARP 缓存。

拥有非易失性存储的设备可以记录其选择的 IP 地址, 并在下一次引导时将其用作探测时的第一个候选地址,以提高地址的稳定性并减少解决地址冲突的需要。

当设备正在发送 ARP 探针并侦听答复时,地址冲突检测不应局限于地址测试阶段。 地址冲突检测是一个持续不断的过程,只要设备使用链接本地地址,该过程就一直有效。 在任何时候,如果设备接收到一个 ARP 数据包,该 ARP 数据包具有自己的 IP 地址作为发送方 IP 地址, 但发送方硬件地址与自己的硬件地址不匹配,则该设备应将此视为地址冲突并应做出响应 如以下(a)或(b)中所述:

  • a: 立即配置新的本地链接IP地址,或者
  • b: 如果设备当前具有活动的 TCP 连接或其他原因希望保留相同的IP地址, 并且最近(例如,在过去的十秒钟内)没有看到任何其他冲突的 ARP 数据包, 则它可以选择尝试保护其地址一次 ,方法是记录收到冲突的 ARP 数据包的时间, 然后广播一个 gratuitous ARP,并为将自己的 IP 和硬件地址作为 ARP 的源地址。 但是,如果在此后的短时间内(例如十秒钟之内)收到另一个冲突的ARP数据包, 则设备应立即如上所述配置新的自动IP地址。

设备应按照上述(a)或(b)中的说明响应冲突的 ARP 数据包; 它不应忽略冲突的 ARP 数据包。 要从一个 IP 地址切换到新的 IP 地址, 设备应尽可能取消在前一个地址上发布的所有未完成的广告, 并且必须在新地址上发布新的广播。 发现部分介绍了广播及其取消。

成功配置自动 IP 地址后, 应使用链接级广播而不是链接级单播发送包含自动 IP 源地址的所有后续 ARP 数据包(答复和请求), 以便及时发现重复地址。 作为替代方案,无法发送广播 ARP 答复的设备应发送单播 ARP 答复, 但随后忽略遵循 RFC 826 中有关记录来自已接收ARP请求的发件人信息的指令。 这意味着,在未能记录发送方信息的情况下,该设备很可能稍后会发送自己的广播ARP请求, 从而使使用相同IP地址的另一设备能够检测到冲突并对其进行响应。

源或目标地址在 169.254.0.0/16 范围内的 IP 数据包不得发送到任何路由器进行转发。 具有多播目标地址和自动IP源地址的IP数据报不应从本地链路转发出去。 设备和控制点可以假定所有 169.254.0.0/16 目标地址都在链接上并且可以直接访问。 169.254.0.0/16 地址范围一定不能划分子网。

0.4 Addressing: Periodic checking for dynamic address availability

自动配置 IP 地址的设备必须定期检查 DHCP 服务器的存在。 这是通过发送 DHCPDISCOVER 消息来完成的。 进行此检查的频率取决于实现方式,但是每 5 分钟检查一次将在所需的网络带宽和连接性维护之间保持平衡。 如果收到DHCPOFFER,则设备必须继续进行动态地址分配。 DHCP分配的地址到位后,设备可以释放自动配置的地址, 但也可以选择在一段时间内(或无限期地)维护该地址以保持连接性。

要从一个 IP 地址切换到新的 IP 地址,设备应尽可能取消在前一个地址上发布的所有未完成的广告, 并且必须在该新地址上发布新的广告。 发现部分介绍了广告及其取消。

0.5 Addressing: Device naming and DNS interaction

设备具有网络的有效 IP 地址后,就可以通过该地址在该网络上对其进行定位和引用。 最终用户可能需要定位和识别设备。 在这些情况下,设备的友好名称比 IP 地址更容易被人使用。 如果 UPnP 设备选择向 DHCP 服务器提供主机名并向 DNS 服务器注册,则该设备应确保所请求的主机名是唯一的, 或者应为用户提供一种更改所请求的主机名的方法。 UPnP设备通常不提供主机名,而是使用文字(数字)IP地址提供URL。

而且,名称比 IP 地址更加静态。 当设备的 IP 地址更改时,按名称引用设备的客户端不需要任何修改。 设备的 DNS 名称到其 IP 地址的映射可以手动输入,也可以根据 RFC 2136 动态输入到 DNS 数据库中。 尽管支持动态 DNS 更新的设备可以直接在 DNS 中注册其 DNS 记录, 但也可以配置 DHCP 服务器进行注册 DNS 记录代表这些DHCP客户端。

0.6 Addressing: Name to IP address resolution

如果一台设备需要通过 DNS 联系另外一台设备,那么我们先要从 DNS 的名字中取得设备的 IP 地址。 设备根据 RFC1034 和 1035 向预配置的 DNS 服务器提交 DNS 查询, 并从 DNS 服务器接收包含目标设备 IP 地址的响应。 可以使用 DNS 服务器列表对设备进行静态预配置。 或者,可以通过DHCP,或在通过DHCPINFORM消息分配地址之后,为设备配置 DNS 服务器列表。

0.7 Addressing references
1. Discovery

发现是 UPnP™ 网络中的第一步。 发现是在寻址(第 0 步)之后获得的,其中设备获得了网络地址。 通过发现,控制点可以找到有趣的设备。 发现可以进行描述(第 2 步),其中控制点了解设备功能,进行控制(第 3 步),控制点向设备发送命令, 进行事件(第 4 步),控制点侦听设备的状态变化, 和演示(第 5 步),其中控制点显示设备的用户界面。

发现是 UPnP 网络的第一步。将设备添加到网络后,UPnP 发现协议允许该设备将其服务通告给网络上的控制点。 同样,当将控制点添加到网络时,UPnP 发现协议允许该控制点搜索网络上感兴趣的设备。 两种情况下的基本交换是发现消息,该消息包含有关设备或其服务之一的一些基本细节, 例如,设备的类型,设备的唯一的标识符,及指向更详细信息的指针。

将新设备添加到网络后,它会用多播的方式广播一些消息,这些消息会宣传其自身,其嵌入式设备及其服务。 任何感兴趣的控制点都可以侦听标准多播地址,以获取有关新功能可用的通知。

同样,将新的控制点添加到网络后,它会用多播的方式广播一条搜索消息,搜索有趣的设备,服务或两者。 所有设备必须侦听这些消息的标准多播地址,并且如果它们的任何嵌入式设备或服务符合发现消息中的搜索条件, 则必须做出响应。

重申一下,控制点可以获悉感兴趣的设备,因为该设备发送了广告宣传自己的消息, 或者因为该设备响应了搜索消息。 在任何一种情况下,如果控制点对设备感兴趣并希望了解更多信息, 则控制点将使用发现消息中的信息来发送描述查询消息。 “描述”部分详细说明了描述消息。

当设备从网络中删除时,如果可能的话,它应该多播多条发现消息,以撤销其先前的公告, 从而有效地声明其嵌入式设备和服务将不再可用。 更改设备的IP地址后,它应也应该撤消所有之前的公告,并使用新的IP地址发布广告。

对于具有多个网络接口的设备和控制点, 应该在所有启用 UPnP 网络的网络接口上发送 UPnP 通告和搜索。 每个广播消息或搜索消息都必须在 LOCATION 标头中指定一个可在该接口*问的地址

为了避免网络拥塞,每个多播消息的每个 IP 数据包的生存时间(TTL)应该默认为4,并且应该是可配置的。 当 TTL 大于 1 时,多播消息可能会遍历多个路由器。 因此,使用非 AutoIP 地址的控制点和设备必须发送 IGMP Join 消息, 以便路由器将多播消息转发给它们 (使用 Auto-IP 地址时,这是不必要的,因为具有 Auto-IP 地址的数据包将不会被路由器转发)。

发现在不同版本的 UPnP 网络的设备和控制点的互操作性中起着重要作用。 UPnP 设备体系结构(在此定义)同时具有主要版本和次要版本,通常写为 major.minor, 其中,主要和次要均为整数(例如,版本2.10 比版本2.2更新)。 次要版本的提升必须是同一主版本的较早次要版本的兼容超集。 主要版本中的进步不一定是早期版本的超集,也不保证其向后兼容。 版本信息在发现和描述消息中传达。 在前一种情况下,每个发现消息都包括设备支持的 UPnP 网络版本(在 SERVER 标头中); 相关的发现消息中还包含支持的设备和服务类型的版本。 作为备份,后者也包含相同的信息。 本节将说明发现消息中版本信息的格式以及对发现消息的特定要求,以保持与次要版本的改进的兼容性。

本节的其余部分详细解释 UPnP 发现协议(SSDP), 列举设备如何通告和撤消其通告以及控制点搜索和设备如何响应。

1.1 Discovery: Advertisement

将设备添加到网络后,该设备会将其服务通告给控制点。 它通过将发现消息多播到标准地址和端口(239.255.255.250:1900)来实现此目的。 控制点侦听此端口以检测网络上何时有新功能可用。 为了公布其功能的全部范围,设备会多播设备自身的服务相对应的发现消息。 每个消息都包含特定于设备(或服务)的信息和有关其封闭设备的信息。 消息应应该包含该消息的过期信息; 如果设备仍然可用,则应重新发送广告。 如果设备不可用,则设备应明确取消其广告,但是如果设备无法执行此操作,则广告将自行失效。

1.1.1 Discovery: Advertisement protocols and standards

为了发送(和接收)广播,设备(和控制点)需要遵从以下提到的 UPNP 的子集。 (UPnP 的完整协议栈在文章开头有提及)

在最上层,发现消息包含供应商的信息,例如,用于设备描述和设备标识符的URL。 从协议栈向下看,供应商内容将由 UPnP 论坛工作委员会提供的信息(例如设备类型)进行补充。 来自以上各层的消息以本文档中定义的 UPnP 的协议承载。 同样的,上述消息是通过使用 HTTPMU( HTTP 多播变体) 的头部或者方法进行封装传递。 HTTP 消息是通过 UDP over IP 传递的。 作为参考,上方[方括号]中的颜色表示哪种协议定义了下面列出的发现消息中的特定标头和值。

1.1.2 Discovery: Advertisement: Device available -- NOTIFY with ssdp:alive

将设备添加到网络后,它会多播发现消息,以通告其根设备,任何嵌入式设备和所有服务。 每个发现消息包含四个主要组件:

    1. 在 NT(通知类型)标头中发送的潜在搜索目标(例如设备类型),
    2. 在 USN(唯一服务名称)标头中发送该设备综合服务的组合标识符,

在 LOCATION 标头中发送的有关该设备(或在服务中为封闭设备)的更多信息的 URL,

在 CACHE-CONTROL 标头中发送的广播消息的有效期限。

为了公布其功能,设备会广播多次自身服务的多条发现消息。 具体来说,根设备必须多播:

  • 更设备(服务)的发现消息。
  • 两条为两个嵌入设备发送的搜索消息。
  • 设备各自的服务。

** 请注意,USN 标头的前缀(在双冒号之前)必须与设备描述中的 UDN 元素的值匹配。 (“描述”部分说明了UDN元素。)

** 请注意,此NT标头的值必须与设备描述中的UDN元素的值匹配。

如果根设备具有 d 个嵌入式设备和 s 个嵌入式服务,但只有 k 种不同的服务类型, 那么该设备将可达到 3 + 2d + k 个请求。 如果特定设备或嵌入式设备包含特定服务类型的多个实例, 则仅需要通告一次该服务类型(而不是每个实例一次)。 这会将设备功能的全部范围通告给感兴趣的控制点。 这些消息必须按照到期时间粗略的排好先后顺序,然后按照序列顺序发送出去; 严格的顺序无关紧要,但是单独的刷新或取消消息是禁止的。 (不知道翻译对不对,原文在这里: order is unimportant, but refreshing or canceling individual messages is prohibited)

更新的 UPnP 设备和服务类型需要与该类型的先前版本完全向后兼容。 设备必须公布每种受支持类型的最高受支持版本。 例如,如果设备支持“音频”服务的版本2,即使它也支持版本 1,它也只会发布版本 2。 由此,固定版本的设备或服务的控制点也可以与更高版本进行交互。 但由于仅具有向后兼容性要求,因此只能使用较低版本中定义的功能。 例如,如果控制点仅支持“音频”服务的版本“ 1”,并且设备宣传其支持“音频”服务的版本“ 2”, 则控制点应识别并能够使用该设备 。

为广播选择合适的有效时间是在最小化网络流量和最大化设备状态新鲜度之间的平衡。 相对较短的有效时间(最短为1800秒)将确保控制点具有当前设备状态, 但会增加网络流量; 较长的持续时间(例如一天左右)会损害设备状态的新鲜度,但可以大大减少网络流量。 通常,设备供应商应该选择一个与预期设备使用率相对应的值: 对于短期内预计将成为网络一部分的设备,有效期的时间应该设置比较短; 对于预期长期成为网络成员的设备,有效期时间应该设置较长。 频繁连接和离开网络的设备(例如移动无线设备)应使用较短的有效期时间,以便控制点可以更准确地了解其可用性。 初始的广告集应具有比较合适的有效时间,并且整个组应尽快发送。 广播的后续刷新可能会随时间分布,而不是作为一个组发送。

设备应在发送初始广告集之前随机的等待小于 100 毫秒的间隔,以减少网络风暴的可能性; 该随机间隔也应在设备获得新 IP 地址或安装新网络接口的情况下应用。

由于 UDP 的不可靠特性,设备应多次发送上述每个发现消息,尽管为避免网络拥塞, 发现消息的发送次数不应超过 3 次。另外,设备必须在 CACHE-CONTROL 标头中指定的超市时间到期之前, 定期重新发送其广告。 建议以小于广播到期时间一半的随机分布间隔进行这种广告刷新, 以便为在广告到期之前从为丢失的广播提供恢复的机会,并随着时间的流逝在网络上随机分布多个设备的广告刷新, 以避免网络流量激增。 但请注意,UDP 数据包的长度也是有界的(在某些实现中可能只有512字节)。 每个发现消息必须完全适合单个UDP数据包。而且,不保证上述 3 + 2d + k 个消息将以特定顺序到达。

将设备添加到网络后,它必须使用以下格式广播自己的请求,其中 NTS 头字段的值应该为 ssdp:alive, Method 字段应该为 NOTIFY。其中斜体代表待填充字段。

NOTIFY 方法的请求没有负载(body)部分,但是在头部后了空行一定不能少。(结尾是两个 \r\n)

IP 包的 TTL 默认为 4,但也可以更具具体的情况进行调整。

下面会详细介绍一下上面请求体中各个字段的作用。如非特殊说明,所有的头字段的 Key( field ) 都为大些( UpperCase )。

  • request line
    • NOTIFY 发送 notifications 和 events 的方法
    • * 必须具备 | 请求通用而非特定资源
    • HTTP/1.1 HTTP 的版本
  • headers
    • HOST 必须具备 | Internet 号码分配机构(IANA)为 SSDP 保留的多播信道和端口(239.255.255.250:1900)。 如果在该字段中省略了 1900 这个端口号, 那么接受端应该默认使用 1900 这个端口。
    • CACHE-CONTROL 必须具备 | 指定广播的有效秒数的 max-age 指令。 超过此时间之后,控制点应假定设备(或服务)不再可用。 | 该字段应大于或等于 1800 秒(30分钟)。 但可由 UPnP 供应商配置指定。 | 数据类型为整数。
    • LOCATION 需要包含 | 包含指向根设备的 UPnP 描述的 URL。 通常,在非托管网络中,主机部分包含的是 IP 地址字符串,而不是域名。 由 UPnP 供应商指定。单一网址。
    • NT 需要支持 | 指定 Notification 的类型,具体请参考一下 NT 那张表
    • NTS 需要支持 | Notification 的子类型,目前必须是该字段 ssdp:alive
    • SERVER 需要支持。 | 包含操作系统名称,操作系统版本,UPnP / 1.0,产品名称和产品版本的串联。 这些信息由 UPnP 供应商自行指定。 | 数据类型:字符串。 | 该头字段必须准确反映设备支持的 UPnP 设备体系结构的版本号。 控制点必须准备好接受比控制点本身实现的更高的次要版本号。 | 例如,实现 UDA 版本 1.0 的控制点将能够与实现 UDA 版本 1.1 的设备进行互操作。
    • USN 需要支持。 | 唯一服务名称。标识设备或服务的唯一实例。 必须是以下之一。 | 该字符串的前缀(在双冒号之前)必须与设备描述中的 UDN 元素的值匹配。 (“描述”部分将介绍了 UDN 元素。) | 仅存在单个 URI。
  • NT 支持的可选项
    • upnp:rootdevice 根设备,此类消息在一个设备中只会出现一条。
    • uuid:device-UUID 每一个设备只会发送一次,根设备或者嵌入设备 | UUID 由 UPNP 供应商(vendor)自己决定。
    • urn:schemas-upnp-org:device:deviceType:v 每一个设备只会发送一次, 根设备或者嵌入式设备。 | 设备类型和版本号由 UPnP 供应商自己确定。 | 指定的版本号为最高支持的版本
    • urn:schemas-upnp-org:service:serviceType:v 每个服务发送一次。 | 服务类型和版本号有 UPnP Forum 制定, | 指定的版本号为最高支持的版本
    • urn:domain-name:device:deviceType:v 每一个设备只会发送一次, 根设备或者嵌入式设备。 | 域名的定义由 UPnP 供应商决定, | 指定的版本号为最高支持的版本 | 域名中的句点字符必须根据 RFC 2141 替换为连字符。
    • urn:domain-name:service:serviceType:v 每一个服务都会发送一次。 | 域名,服务类型和版本号都由 UPnP 供应商自己定义, | 这里指定的版本号为最高支持的版本 | 域名中的句点字符必须根据 RFC 2141 替换为连字符。
  • USN 支持的可选项
    • uuid:device-UUID::upnp:rootdevice 根设备发送一次 | 设备 UUID 由供应商决定
    • uuid:device-UUID 每一个设备都会发送一次, 根设备或者嵌入设备 | 设备 UUID 由供应商决定
    • uuid:device-UUID::urn:schemas-upnp-org:device:deviceType:v 每一个设备都会发送一次, 根设备或者嵌入设备 | 设备 UUID 由供应商决定 | 设备的类型和版本号有 UPnP 委员会确定 | 这里指定的版本号为最高支持的版本
    • uuid:device-UUID::urn:schemas-upnp-org:service:serviceType:v 每一个服务都会发送一次 | 设备 UUID 由供应商决定 | 设备的类型和版本号有 UPnP 委员会确定 | 这里指定的版本号为最高支持的版本
    • uuid:device-UUID::urn:domain-name:device:deviceType:v 每一个设备都会发送一次, 根设备或者嵌入设备 | 设备 UUID, 域名,设备类型,版本由供应商决定 | 这里指定的版本号为最高支持的版本 | 域名中的句点字符必须根据 RFC 2141 替换为连字符。
    • uuid:device-UUID::urn:domain-name:service:serviceType:v 每一个服务都会发送一次 | 设备 UUID, 域名,服务类型,版本由供应商决定 | 这里指定的版本号为最高支持的版本 | 域名中的句点字符必须根据 RFC 2141 替换为连字符。

NOTIFY 的请求不需要响应。

1.1.3 Discovery: Advertisement: Device unavailable -- NOTIFY with ssdp:byebye

当要将某个设备及其服务从网络中删除时,该设备应多播一个 ssdp:byebye 消息, 该消息对应于其多播的尚未到期的每个 ssdp:alive 消息。 如果设备突然从网络中删除,则可能无法组播消息。 作为后备,发现消息必须在 CACHE-CONTROL 标头中包含一个到期值(如上所述)。 如果不重新发布,发现消息最终将会自行失效,并且必须从任何控制点缓存中删除。

(注意:当一个控制点从网络中推出时,不需要 discovery 相关的请求发出。)

当设备将要从网络中删除时,应通过为发送的每个 ssdp:alive 消息发送一个多播请求,显式撤消其发现消息。 每个多播请求在 NTS 标头中必须具有以下格式的 NOTIFY 和 ssdp:byebye 方法。 斜体值是实际值的占位符

(该请求无 Body 字段,但是后面的空行不能省略,即 IP 负载字段应该以 \r\n\r\n 结尾)

IP 包的 TTL 应该设置成 4, 但是该字段 UPnP 可以根据实际情况修改。

下面将会根据上叙协议中各个字段的详细解释。 除非特殊说明,上叙头中的字段都是大小写敏感的。

  • Request line
    • NOTIFY 发送 notifications 和 events 的方法
    • * 请求通用而非特定资源 | 必须具备
    • HTTP/1.1 HTTP 的版本
  • headers
    • HOST 必须具备 | Internet 号码分配机构(IANA)为 SSDP 保留的多播信道和端口(239.255.255.250:1900)。 如果在该字段中省略了 1900 这个端口号, 那么接受端应该默认使用 1900 这个端口。
    • NT 需要支持 | (请参考 ssdp:alive 中的说明) | 这里只包含单个 URI
    • NTS 需要支持 | Notificatioin 的子类型 | 该字段必须为 ssdp:byebye | 这里只包含单个 URI
    • USN 需要支持 | 唯一的服务名字 | (请参考 ssdp:alive 中的说明) | 这里只包含单个 URI

(该 NOTIFY 信息不要服务端响应)

由于 UDP 的不可靠特性, 设备应多次发送上述每个消息。 作为后备,如果控制点无法接收到有关设备或服务不可用的通知, 原始发现消息最终将失效,从而产生相同的效果。

1.2 Discovery: Search

将控制点添加到网络后,UPnP 发现协议允许该控制点搜索网络上感兴趣的设备。 它通过在保留的地址和端口(239.255.255.250:1900)上多播搜索消息, 该搜索消息的特征码或目标等于设备或服务的类型或标识符。 来自设备的响应与新设备进入网络时广播的消息基本相同,区别是,前者是单播的,后者是组播的。

1.2.1 Discovery: Search protocols and standards

当要搜索设备(并由控制点发现), 控制点(和设备)使用如下的 UPnP 协议栈的子集。 (本文档开头列出了整个 UPnP 协议栈。)

在协议顶层,搜索消息包含特定于供应商的信息,例如控制点,设备和服务标识符。 协议栈向下一层,供应商内容将由 UPnP 论坛工作委员会提供的信息进行补充,例如设备或服务类型。 来自以上各层的消息都将托管在本文档中定义的 UPnP 协议中。 相应的,搜索请求是通过 HTTP 的多播变体传递的,该变体已使用其他方法和标头进行了扩展。 而搜索响应是通过 HTTP 的单播变体(已扩展)传递的。 两种 HTTP 消息都是通过 UDP over IP 传递的。 作为参考,上方[方括号]中的颜色表示哪种协议定义了下面列出的发现消息中的特定标头和值。

1.2.2 Discovery: Search: Request with M-SEARCH

当一个控制点加入网络之后,它应该发送一个 M-SEARCH 类型的多播请求。 该请求服从下面的格式。Values in italics are placeholders for actual values.

( M-SEARCH 请求没有负载(body 字段),但是结尾的空行必须存在,即数据包应该以 \r\n\r\n 结束。 )

IP 包的 TTL 应该设置成 4, 但是该字段 UPnP 可以根据实际情况修改。

下面将会根据上叙协议中各个字段的详细解释。除非特殊说明,上叙头中的字段都是大小写敏感的。

  • Request line
    • NOTIFY 发送 notifications 和 events 的方法
    • * 请求通用而非特定资源 | 必须具备
    • HTTP/1.1 HTTP 的版本
  • headers
    • HOST 必须具备 | Internet 号码分配机构(IANA)为 SSDP 保留的多播信道和端口(239.255.255.250:1900)。 如果在该字段中省略了 1900 这个端口号, 那么接受端应该默认使用 1900 这个端口。
    • MAN 该字段服从 HTTP 扩展框架要求。 | 与 NTS 和 ST 标头不同, MAN 标头的值用双引号引起来; | 它定义扩展的范围(名称空间)。 字段值必须为 “ssdp:discover”。
    • MX 需要支持,该字段指定最大的等待时长,单位为 s, 推荐值在 1 - 120 s 之间。 | 设备的响应该在经过 0 - MAX 之间随机的一段时间后发出, | 而且不应该为迁就网络状况而去增大该值,(下面会说明原因) | 这个字段由 UPnP 供应商指定, | 数据类型为整数
    • ST 需要支持,指定搜索的目标。 | 必须从以下几个中选择一个(见接下来的 ST 表) | 仅仅包含一个指向资源的 URI
  • ST 中包含的可选项
    • ssdp:all 搜索所有的服务
    • upnp:rootdevice 仅搜索根设备
    • uuid:device-UUID 搜索 UUID 指定的设备,UUID 由供应商决定
    • urn:schemas-upnp-org:device:deviceType:v 搜索任何 deviceType 类型的设备, DeviceType 由 UPnP 委员会定义
    • urn:schemas-upnp-org:service:serviceType:v 搜索任何 serviceType 类型的设备, serviceType 由 UPnP 委员会定义
    • urn:domain-name:device:deviceType:v 搜索任何服从该域名,设备类型的设备, | 这里的域名和设备类型有 UPNP 供应商定义 | 域名中的句点字符必须根据 RFC 2141 替换为连字符。
    • urn:domain-name:service:serviceType:v 搜索任何服从该域名,服务类型的服务, | 这里的域名和服务类型有 UPNP 供应商定义 | 域名中的句点字符必须根据 RFC 2141 替换为连字符。

由于 UDP 的不可靠特性,控制点应多次发送各个 M-SEARCH 消息。 作为备用,为防止设备收不到控制点发送的 M-SEARCH 消息的可能性, 设备应定期重新发送其广告(请参见上面 NOTIFY 中带有 ssdp:alive 的 CACHE-CONTROL 标头)。

控制点应该在 MX s 内一直等待设备发出响应。 MX 间隔上响应的随机分布意味着响应者可能在收到 M-SEARCH 请求后的 MX 秒发送响应。 因此供应商应该基于观察到的响应者的数量,然后根据经验调一个合适的 MX 值。 也因为上叙原因,因此无法通过增加 MX 值来解决影响流量传播的网络特征。 请求者可以根据观察到的网络行为,通过启发式方法适应网络特征(确切的启发式方法不在本文范围内)。 算法最终应该会导致请求者等待 M-SEArCH 的时间会略微超过 MX 的时间,从而适应网络的传输特性。 以最小化丢失设备的响应。

需要设备和服务类型的更新版本与以前的版本完全向后兼容。 设备必须响应任何受支持版本的 M-SEARCH 请求。 例如,如果设备实现“ urn:schemas-upnporg:service:Audio:2”, 则该设备也应该响应类型为 “ urn:schemas-upnp-org:service:Audio:1” 的搜索请求。 响应中应指定与搜索请求中包含的版本相同的版本。 如果控制点搜索特定版本的设备或服务并且未收到任何响应(大概是因为网络上没有设备支持指定的版本),但愿意使用较低的版本进行操作,则它可能会重复搜索指定较低的版本。

1.2.3 Discovery: Search: Response

为了找到这些设备,设备必须为每一个从该多播地址收到的请求返回一个响应。 如果发起 MSEARCH 设备的 ST 头为 “ssdp:all” 或者 "upnp:rootdevices" 或者 "uuid:device-UUID"(device-UUID 和本设备一致), 设备则必须响应这些消息,

设备在收到 DMC 发出的请求之后应该随机的响应 0 - MX 指定的时间然后才响应设备的请求。 这么做是为了防止环境中的设备集中的响应 DMC 的请求,造成短暂的网络拥塞, 如果一个搜索请求有多个响应,那么这些响应也应该随机的分布在 0 - MX 时间段内。 如果搜索请求不包含 MX 标头,则设备必须静默丢弃并忽略搜索请求。 如果 MX 标头指定的值大于120,则设备应假定其包含的值等于或小于 120。 设备在发送响应之前等待随机延迟的同时,不应停止响应其他请求。

在 M-SEARCH 的响应消息中,LOCATION 字段指定的 URL 必须是可以访问的。

设备对 M-SEARCH 的响应是有意相似于 ssdp:alive 的广播消息的,比如说响应的头几乎与 ‘ssdp:alive’ 消息的头一致(除了 NT 头换成了 ST 头)。 响应必须以以下格式发送。 斜体值是实际值的占位符

( M-SEARCH 请求没有负载(body 字段),但是结尾的空行必须存在,即数据包应该以 \r\n\r\n 结束。 )

IP 包的 TTL 应该设置成 4, 但是该字段 UPnP 可以根据实际情况修改。

下面将会根据上叙协议中各个字段的详细解释。除非特殊说明,上叙头中的字段都是大小写敏感的。

  • request line
    • HTTP/1.1 HTTP 的版本
    • 200 HTTP 状态码,200 代表成功响应
    • OK 响应成功的描述字段
  • headers
    • CACHE-CONTROL 必须具备 | 指定广播的有效秒数的 max-age 指令。 超过此时间之后,控制点应假定设备(或服务)不再可用。 | 该字段应大于或等于 1800 秒(30分钟)。 但可由 UPnP 供应商配置指定。 | 数据类型为整数。
    • DATE 建议携带此字段 | 响应的时候生成, | 格式 follow “rfc1123-date”, 在 RFC 2616 中有定义。
    • EXT 该字段由 ( HTTP Extension Framework) 引入 | 主要用于确定理解请求中 “MAN” 头 | 该头不需要携带任何值
    • LOCATION 需要包含 | 包含指向根设备的 UPnP 描述的 URL。 | 通常,在非托管网络中,主机部分包含的是 IP 地址字符串,而不是域名。 | 由 UPnP 供应商指定。单一网址。
    • SERVER 需要支持。 | 包含操作系统名称,操作系统版本,UPnP / 1.0,产品名称和产品版本的串联。 这些信息由 UPnP 供应商自行指定。 | 数据类型:字符串。 | 该头字段必须准确反映设备支持的 UPnP 设备体系结构的版本号。 控制点必须准备好接受比控制点本身实现的更高的次要版本号。 | 例如,实现 UDA 版本 1.0 的控制点将能够与实现 UDA 版本 1.1 的设备进行互操作。
    • ST 如果请求中指定搜索此头,那么在这里就要包含此头。 | 具体请参考 请求部分的说明
    • USN 需要支持。这里指定唯一服务名称。 | 具体请参考 "ssdp:alive" NOTIFY 部分 的说明

如果搜索请求出现错误(例如 MAN 标头中的值无效,MX 标头丢失或其他格式错误的内容), 则设备应静默丢弃并忽略搜索请求。 不建议发送错误响应,因为如果许多设备向同一请求发送错误响应,则可能会出现数据包风暴。

1.3 Discovery references
2. Description

Description 是 UPnP™ 网络中的步骤 2,在寻址(步骤0)和设备发现(步骤1)之后。

在控制点发现设备之后,控制点仍然对设备知之甚少 - 仅仅能获取发现消息中涉及到的信息,即设备(或服务)的 UPnP 类型, 设备的通用标识符以及设备 UPnP 描述的 URL。 为了控制点了解有关设备及其功能的更多信息,或与设备交互, 控制点必须从发现消息中由设备提供的 URL 检索设备及其功能的详细描述。

设备的 UPnP 描述分为两个逻辑部分: 一个设备描述(名词)描述(动词)物理和逻辑容器,以及描述设备公开的功能的服务描述。 UPnP 设备描述包括特定于供应商的制造商信息, 例如型号名称和编号,序列号,制造商名称,特定的供应商的网站的 URL 等(以下详细信息)。 对于设备中包含的每个服务,设备描述都列出了服务类型,名称,服务描述的 URL, 控制的 URL 和事件的 URL。 设备描述还包括所有嵌入式设备的描述以及用于展示设备状态信息或者控制接口的 URL。 本节说明 UPnP 设备说明,而“控制”,“事件”和“演示”部分分别说明如何使用用于控制,事件和演示的 URL。

注意,单个物理设备可以包括多个逻辑设备。 多个逻辑设备也可以组合成一个嵌入式设备上的根设备(或服务)。 当然也可以组合成多个根设备。 在前一种情况下,根设备只有一个 UPnP 设备描述,并且该设备描述包含所有嵌入式设备的描述。 在后一种情况下,有多个 UPnP 设备描述,每个根设备一个。

UPnP 设备的描述由 UPnP 供应商编写。 该描述采用 XML 格式来表示,通常基于标准的 UPnP 设备模板。 UPnP 论坛工作委员会制作了 UPnP 设备模板; 他们从 UPnP 模板语言(该模板语言是从 XML 的标准构造派生而来)派生模板。 本节说明 UPnP 设备描述,UPnP 设备模板的格式以及 UPnP 模板语言涵盖设备的部分

UPnP 服务描述包括服务响应的命令或动作的列表以及,每个动作的参数或自变量。 服务描述同样也包括变量列表,这些变量决定了设备的运行时状态,同样服务表述还包括各项数据的数据类型,范围和事件特征。 本节说明操作,自变量,状态变量以及这些变量的属性的描述。而事件的特征将会在事件部分说明。

像 UPnP 设备描述一样,UPnP 服务描述由 UPnP 供应商编写。 该描述采用 XML 语法,通常基于标准的 UPnP 服务模板。 UPnP 论坛工作委员会制作了 UPnP 服务模板; 他们从 UPnP 模板语言中获得了模板,并在必要时使用人类语言对其进行了扩充。 UPnP 模板语言源自 XML 的标准构造。 本节将说明 UPnP 服务描述的格式,UPnP 服务模板,典型的人类语言扩展以及 UPnP 模板语言涵盖服务的部分。

UPnP 供应商可以通过扩展服务(包括其他 UPnP 服务)或嵌入其他设备来区分其设备。 当控制点检索特定设备的描述时,这些添加的功能将显示的提供给控制点,以进行设备的控制和事件处理。 从设备和服务描述也严格地记录了设备的实现的功能。

检索 UPnP 设备描述很简单: 控制点使用 HTTP GET 请求去访问设备描述的 URL(在 Discovery 阶段获取),然后设备即会返回设备对应的描述。 检索 UPnP 服务描述的过程与在设备描述中使用 URL 的过程一样。 响应和请求的协议栈,方法,标头和主体在下面详细说明。

只要来自设备的发现广播还没有过期, 控制点就可以假定该设备及其服务可用。 由于设备和服务描述是静态的,因此只要设备及其服务可用,就可以在任何时候检索设备和服务描述。 如果设备取消其广播或广播到期,则控制点应假定该设备及其服务不再可用。 如果设备需要更改这些描述之一,则必须取消其未完成的广播并重新进行广播。 因此,如果设备重新出现在网络上,则控制点不应假定设备和服务描述不变。

像发现一样,描述在使用不同版本的 UPnP 网络的设备和控制点的互操作性中也起着重要作用。 如发现部分所述,UPnP 设备体系结构既有主要版本也有次要版本。 主版本和次版本是单独的整数; 即使它们可能出现在打印中,也不应将其解释为一个带小数点的数值。 次要版本的提升必须是同一主版本的较早次要版本的兼容超集。 主要版本中的进步不一定是早期版本的超集,也不保证其向后兼容。 版本信息在描述消息中传递,作为后备,此信息也存在在设备的发现消息中。 本节将会说明描述消息中版本信息的格式。

设备和服务的标准由 UPnP 论坛工作委员会标准化或由供应商创建。 设备或服务的每个更高版本都必须是先前版本的完全向后兼容的超集, 即,与该设备的早期版本相比,当前版本必须包括前一版本的所有嵌入式设备和服务。 在设备的所有版本中,UPnP 设备或服务类型均相同,而对于更高版本,设备或服务版本必须更大。 在工作委员会的开发过程中,设备和服务模板的版本可能具有非整数版本(例如“ 0.9”), 但是标准化后必须成为整数。 设备和服务的版本号可能大于其设计所针对的体系结构的主要版本号 (例如,“ Power:2”可能被设计为在 UDA 版本1.0上工作); 设备或服务模板的版本与设计用来工作的体系结构的版本之间没有直接关联。 如果定义了设备或服务的非向后兼容版本,则该设备或服务必须具有不同的设备或服务名称, 以指示其不向后兼容(新类型的版本号应从1重新开始)。

UPnP 设备和服务类型可以以各种组合形式组装成 “building blocks”。 标准设备类型和供应商定义的设备类型都可以嵌入标准设备类型中。 标准和供应商定义的设备类型都可以嵌入在供应商定义的设备类型中。 同样,标准和供应商定义的服务类型都可以嵌入在标准和供应商定义的设备类型中。 能够与特定设备或服务类型一起工作的控制点,即使将其嵌入在无法识别的另一种设备类型(标准或供应商定义)中, 也应识别该设备或服务类型。 例如,如果定义了标准服务类型“ Print:1”,并且定义了包含“ Print:1”服务的标准设备类型“ Printer:1”, 控制点希望找到并使用“ Print:1” 服务应,无论服务是嵌入在 “urn:schemas-upnp-org:device:Printer:1” 设备中, 还是嵌入在供应商定义的 “urn:acme-com:device:Printer:1” 中, 或者 “urn:acme-com:device:AcmeMultifunctionPrinter:1”

本节的其余部分首先说明如何描述设备,并详细说明特定于供应商的信息, 嵌入式设备以及用于控制,事件和显示的URL。 其次,它说明了 UPnP 设备模板。 第三,它解释了如何描述服务,解释了动作,参数,状态变量以及这些变量的属性的详细信息。 然后说明 UPnP 服务模板和 UPnP 模板语言。 最后,本节详细说明控制点如何从设备检索设备和服务描述。

2.1 Description: Device description

设备的 UPnP 描述包含几条特定于供应商的信息,所有嵌入式设备的定义,用于设备显示的 URL,所有服务的列表以及用于控制和事件的URL。 除了定义非标准设备(供应商定义的设备和标准嵌入式设备和服务)之外, UPnP供应商还可以提供这些信息,以下是实际元素和值的占位符列表(斜体)。 其中一些占位符将由 UPnP 论坛工作委员会(红色)或 UPnP 供应商(紫色)指定。 对于非标准设备,所有这些占位符将由 UPnP 供应商指定。 (由UPnP设备体系结构定义的元素显示为绿色,以备后用。)紧随清单之后的是元素,属性和值的详细说明。

下面列出的是上面列表中出现的每个元素,属性和值的详细信息。 所有元素和属性都区分大小写; HTTP 指定 URL 也区分大小写; 除非另有说明,否则其他值不区分大小写。 元素的顺序无关紧要。 除非另有说明:必需元素必须只出现一次(没有重复), 推荐或可选元素最多也只能出现一次。 请注意,某些实现可能会严格执行以下各个元素的长度限制,因此建议工作委员会注意所有指定的限制。

  • xml Required | 此字段区分大小写。
  • root Required | 此元素必须设置 xmlns 属性,而且值必须是 urn:schemas-upnp-org:device-1-0, 这引用了 UPnP 模板语言(如下所述)。 该元素下的子元素一起描述了根设备,区分大小写。其子设备类型如下:
    • specVersion Required | 包含下列元素
      • major Required | UDA 的主版本号,必须为 1
      • minor Required | UDA 的子版本号. 对于实现 UDA 1.0 版本的设备此元素的值 必须为 0
      这两个子元素必须准确的反应所实现的 UDA 的版本。而对于控制端,则必须准备接受设备端的版本高于自身的情况。
    • URLBase Optional | 定义 Base URL。 用于构造标准 URL。根据 RFC 2396 中的规则,描述中其他位置出现的 URL 将是 Base URL 的相对路径, 在使用其他 URL 的时候需要先与 Base URL 拼接成一个完整的 URL。 如果 URLBase 为空或未给出,则可以直接访问其他位置出现的 URL(这是现在的首选实现,不再建议使用 URLBase)。 由UPnP供应商指定。| Single URI。
    • device Required. 包含下面的元素:
      • deviceType REQUIRED | UPnP 设备类型. Single URI.
        • 对于由 UPnP 论坛工作委员会定义的标准设备,必须以 "urn:schemasupnp-org:device""开头, 然后是标准化设备类型后缀,冒号和整数设备版本,即 urn: schemas-upnp-org:divice deviceType:ver。 必须指定设备类型的最高支持版本。
        • 对于UPnP供应商指定的非标准设备,必须以 "urn:" + 供应商域名 + ”:device:” + ”deviceType“ + 冒号 + 整数版本, 即“ urn:域名:device:deviceType:ver”。 供应商域名中的句点字符必须根据 RFC 2141 替换为连字符。 必须指定设备类型的最高支持版本。
        设备类型的后缀由 UPNP 委员会定义,或者由设备供应商定义,长度必须小于 64 字节(版本号和冒号不在计数范围)。
      • friendlyName Required | 一个对终端用户友好的简短描述,应支持本地化语言(参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该元素值必须小雨 64 字节 | String 类型。
      • manufacturer Required | 制造商名字. 应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该元素值必须小雨 64 字节 | String 类型。
      • manufacturerURL Optional | 制造商的主页,应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该元素值必须小雨 64 字节 | String 类型。
      • modelDescription Recommended | 关于设备模块的详细描述, 应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该元素值必须小雨 128 字节 | String 类型。
      • modelName Required | 设备模块名字, 应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该元素值必须小雨 32 字节 | String 类型。
      • modelNumber Recommended | 设备模块数量, 应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该元素值必须小雨 32 字节 | String 类型。
      • modelURL Optional | 模块的介绍网址, 应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该字段有可能是资源相对与 BaseURL 的相对位置 | String 类型。
      • serialNumber Recommended | 设备序列号, 应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). 由设备供应商指定。该元素值必须小雨 64 字节 | String 类型。
      • UDN Required | 唯一的设备名称,设备的通用唯一标识符(与根标识符还有嵌入式标识符也保持唯一性)。 在设备的生命周期内,该元素的值应该保持不变(即重启后任然有效,恢复出厂设置之后可以变更), 必须与设备发现消息中 NT 标头的值匹配。 在所有发现消息中,必须与 USN 标头的前缀匹配。 (关于发现的部分说明了 NT 和 USN 标头)。 必须为以下格式( ”uuid:“ + PnP 供应商指定的UUID后缀) | 单个URI。
      • UPC Optional | 通用产品代码(Universal Product Code),由 12 个数字组成, 标识消费者包装的代码。 由统一代码委员会管理(the Uniform Code Council)。 由 UPnP 供应商指定 | Single UPC。
      • iconList 如果产品有自己的 ICON, 那么此字段就需要存在。 有供应商自己指定,包含以下子元素:
        • icon Recommended | 用于在控制点 UI 中描绘设备的图标。 应该支持本地化 (参见. ACCEPT-LANGUAGE 和 CONTENTLANGUAGE 头). ICON 的大小是取决于供应商设计。包含以下子元素:
          • mimetype Required | Icon 的 MIME type, (参考 RFC 2045, 2046, and 2387) | Single MIME image type | 至少应该包含 image/png (Portable Network Graphics, see IETF RFC 2083) 类型
          • width Required | icon 的水品大小(宽) | 单位为像素 | Integer
          • height Required | icon 的垂直大小(高) | 单位为像素 | Integer
          • depth Required | icon 每个像素的(色彩深度) | Integer
          • url Required | img 的 URL( XML 不支持直接嵌入图片在描述文档中. 参看下面的注释) | 该字段有可能是资源相对与 BaseURL 的相对位置 | 由 UPNP 供应商指定 | Single URL.
      • serviceList Optional | 包含以下子元素:
        • service Optional | 对于 UPNP 委员会定义的每个服务都要有描述, 对于供应商额外自定义的用来区分设备的服务也需要在这里有描述。 该服务元素有一下属性。
          • serviceType Required | 服务类型 | 必须包含一个 23 位 UTF-8 编码的散列值 | Single URI.
            • 对于由 UPnP 委员会定义的标准服务类型, 必须遵守以下格式( “urn:schemas-upnp-org:service" + 标准服务类型后缀 + : + 整数服务版本), 即 urn: schemas-upnp-org:device:serviceType:ver 。 必须指定服务的最高支持版本。
            • 对于非标服务,则必须遵守以下格式 ( "urn:" + 供应商的域名 + ”:service:“ + 服务类型后缀 + 服务版本号-整数 ),供应商域名中的句点字符必须根据RFC 2141替换为连字符。必须指定服务类型的最高支持版本。
            这些服务的类型有 UPnP 委员会或者供应商定义,长度必须小于 64 字节 ( 类型后缀和冒号分隔符不包括在内 )。
          • serviceId REQUIRED | 服务的唯一标识 | 在整个设备描述中必须是唯一的 | Single URI.
            • 对于 UPNP 委员会定义的标准服务,这里必须是服从以下格式 ( urn:upnp-org:serviceId: + 服务的 ID 后缀 )(如 urn:upnporg:serviceId:serviceID )。 如果这个实例在上面指定的服务类型(如: <serviceType>)属于上面指定的设备类型(如: <deviceType>)之一, 则服务 ID 后缀的值必须为设备 ID 为该服务实例定义的服务 ID。 否则,服务 ID 后缀的值是供应商定义的。 (请注意,在这种情况下,使用 upnp-org 代替了 schemas-upnp-org,因为没有为每个服务ID定义XML模式。)
            • 对于非标设备,必须服从以下格式(”urn:“ + 供应商域名 + ":serverId:" + 服务 ID 后缀), 例如: “urn:domain-name:serviceId:serviceID”。 如果这个实例在上面指定的服务类型(如: <serviceType>)属于上面指定的设备类型(如: <deviceType>)之一, 则服务 ID 后缀的值必须为设备 ID 为该服务实例定义的服务 ID。 否则,服务 ID 后缀的值是供应商定义的。 (请注意,在这种情况下,使用 upnp-org 代替了 schemas-upnp-org,因为没有为每个服务ID定义XML模式。) 域名中的句点字符必须根据 RFC 2141 替换为连字符。
            这里的设备 ID 后缀由 UPNP 委员会或者供应商定义,必须小于 64 字节。
          • SCPDURL Required | 详细描述此服务的 URL, (nee 服务控制协议定义 URL). (参考以下服务描述章节的说明 ) 又 UPNP 供应商指定 | Single URL.
          • controlURL Required | 用于控制的 URL (参考以下控制章节的说明). 此字段有可能是 BaseURL 的相对路径 | 有供应商指定 | Single URL.
          • eventSubURL Required | 事件的 URL(请参阅事件部分)。 可能是 BaseURL 的相对路径。 设备内必须唯一; 没有两个服务可以具有用于事件的相同 URL。 如果服务没有事件变量,则不应包含事件(请参见事件部分); 如果服务没有事件,则此元素必须存在但应为空,即<eventSubURL></ eventSubURL>。 | 由UPnP供应商指定 | Single URL.
      • deviceList 当此根设备拥有嵌入设备的时候,此元素存在。该元素拥有以下子元素:
        • device Required | 有供应商决定 | 没有嵌入设备都必须拥有一个描述, 如果供应商这里的子元素的定义和上面根设备包含的子元素一致。
      • presentationURL Recommended | 展示设备状态和控制界面的 URL (参考展示章节). 此字段有可能是 BaseURL 的相对路径 | 有供应商指定 | Single URL.

控制点在识别和与设备交互时使用的 serviceID 应该要与设备定义的值不同。 如果存在一个服务有多个实例,则控制点应默认(除非用户操作另有指示)应使用与设备类型定义的 serviceId 值关联的服务实例。 如果服务的所有实例都不具有由设备类型定义的 serviceId 值,则控制点可以使用任何服务实例。 当仅存在一个服务实例时,即使 serviceId 值与设备类型定义的值不匹配,控制点也应使用该实例。

为看可扩展性,当像上面的清单那样处理 XML 时,设备和控制点必须忽略: (a)任何未知元素及其子元素或内容 以及 (b)任何未知属性及其值。

控制点和设备应忽略它们不理解的 UPnP 设备和服务描述中嵌入的任何 XML 注释或 XML 处理指令。

UPnP设备描述应使用UTF-8编码。

当任何文本元素或属性的值包含一个或多个保留标记的字符时(例如 ampersand(&)或 less than(“ <”)), 必须按照XML规范第2.4节的规定对文本进行转义,并将每个此类字符替换为等效的数字表示形式或字符串(例如“&”或“<”)。 出现在 URL 中的此类字符也可以根据 RFC 2396 第 2.4 节中指定的 URL 转义规则进行转义。

XML 不支持直接将二进制数据(例如,图标)嵌入 UPnP 设备描述中。 如果一定要嵌入,可以使用 bin.base64(二进制数据的 MIME-style 的 base 64 编码)或 bin.hex(十六进制数字代表八位字节)的 XML 数据类型将二进制数据转换为文本。 或者,可以通过在 XML 中嵌入 URL 并响应单独的 HTTP 请求来传输数据来间接传递数据。 UPnP 设备描述中的图标以后一种方式传输。

如果有包括任何图标,则至少应该采用 RFC 2083 中���义的可移植网络图形(PNG)格式, 由 MIME 类型“ image/png” 指示,而且不应该采用 progressive encoding 编码方式。 由于各种控制点偏爱的种类繁多,因此不建议使用特定的图标大小。 鼓励控制点供应商发布实施指南。

请注意,在 UPnP 设备体系结构的 1.0 版中, serviceList 元素是必需的,并且它至少包含一个服务元素。 但随后的版本取消了这些要求,以适应网关和基本设备类型。 如果设备没有服务,则 serviceList 元素可以完全省略,也可以存在但不包含服务元素。

2.2 Description: UPnP Device Template

上面的清单说明了 UPnP 设备描述和 UPnP 设备模板之间的关系。 如上所述,UPnP 设备描述是由 UPnP 供应商按照 UPnP 设备模板以 XML 编写的。 UPnP 委员会制作了 UPnP 设备模板,作为标准化设备的一种方法。

通过选择适当的占位符,上面列出的内容可以是 UPnP 设备模板或 UPnP 设备描述。 其中,某些占位符由 UPnP 论坛工作委员会(红色)定义(如:UPnP 设备类型标识符 / UPnP 服务 / UPnP 嵌入式设备(如果有的话))。 如果这些被编写到代码中,则生成的 XML 文档即是 UPNP 设备模板。 UPnP 设备模板是 UPnP 论坛工作委员会的主要成果之一。

更进一步,由 UPnP 供应商(紫色)指定上面列表中的其余占位符, 即特定于供应商的信息。 如果在代码中指定了这些占位符(以及其他占位符), 则列表将是 UPnP 设备描述,适合传递给控制点以用来后续的控制 / 事件 / 演示。

换句话说,UPnP 设备模板定义了设备的整体类型, 每个 UPnP 设备描述都使用供应商特定的信息实例化该模板。 前者 UPnP 论坛工作委员会定义, 而后者由 UPnP 供应商定义。

2.3 Description: Service description

PnP 描述定义了服务的可执行动作, 参数,状态变量及其数据类型,范围和事件特征。

每个服务可能有零个或多个动作。 每个动作可以具有零个或多个参数。 这些参数可以是输入参数或也可以是输出参数。 如果一个动作具有一个或多个输出自变量, 则这些自变量中的一个可被标记为返回值。 每个参数应对应一个状态变量。 这给直接操纵的编程模型增强了简单性。

每一个服务必须有一个或者多个状态变量。

为了定义非标服务,UPnP 供应商可能会增加自己定义的 action 或者 service 给标准设备, 而且也可能嵌入非标准设备到标准设备。

为了说明这些观点,下面列出了包含实际元素的占位符(以斜体显示)和值。 对于标准的 UPnP 服务,其中一些占位符将由 UPnP 论坛工作委员会定义(红色)或由UPnP供应商指定(紫色)。 对于非标准服务,所有这些占位符将由 UPnP 供应商指定。 (由UPnP设备体系结构定义的元素显示为绿色,以备后用。)紧随清单之后的是元素,属性和值的详细说明。

下面列出的是上面列表中出现的每个元素,属性和值的详细信息。 所有元素和属性(包括动作,参数和状态变量名称)均区分大小写, 顺序也无关紧要,必需元素必须仅出现一次(没有重复),推荐或可选元素最多只能出现一次, 除非另有说明。

  • xml所有的 XML 文档都大小写敏感
  • scpd Required | Must | 属性 xmlns 的值必须是 urn:schemas-upnp-org:service-1-0; 这引用了UPnP模板语言(如下所述)。下面详细介绍该元素的子元素:
    • specVersion Required | 包含以下子元素:
      • major Required | UDA 的主版本号,必须为 1
      • minor Required | UDA 的子版本号. 对于实现 UDA 1.0 版本的设备此元素的值 必须为 0
      这两个子元素必须准确的反应所实现的 UDA 的版本。而对于控制端,则必须准备接受设备端的版本高于自身的情况。
    • actionList Required ( 如果服务拥有 action ) (每一个服务必须包含 >= 0 个 action), action 的子元素有如下几种:
      • action Required | 对 UPnP 委员会定义的每个操作都要实现一次。 如果 UPnP 供应商需要通过添加其他 action 来区分服务, 则也要为添加的 action 实现在此处。一个 action 包含以下子元素:
        • name Required | action 的名字, 不得包含连字符(-,UTF-8 中为 0x2D )或 哈希字符(#,UTF-8 中为 0x23)。 | 区分大小写 | 第一个字符必须是 USASCII 字母(A-Z,a-z)/ USASCII 数字(0-9) / 下划线(“ _”)/ 大于 U+007F 的非实验性 Unicode 字母或数字。
          支持的字符必须在以下范围 USASCII 字母(A-Z,a-z) / USASCII 数字(0-9) / 下划线(“ _”) / 句点(“.”) / Unicode组合字符 / 扩展程序或非实验性 Unicode 大于 U+007F 的字母或数字。 在任何情况下,前三个字母不得为“ XML”。
          • 对于标准的 action 必须以 X_ 或者 A_ 开头。
          • 对于非标 action, 但是是添加到标准设备中的 action, 则必须以 X_ 开头。
          String | 必须小于 32 个字符。
        • argumentList Required (当且仅当 action 定义了要操作的参数时)( 每一个 action 可能会有 >= 0 个参数),参数有以下的子元素
          • argument Required | 每一个参数都拥有这样的一个元素,该元素又如下子元素:
            • name Required | 参数的名字 | 不得包含连字符(UTF-8中为 \'-\',0x2D)。 第一个字符必须是 USASCII 字母(A-Z,a-z), USASCII 数字(0-9), 下划线(“ _”)或大于 U+007F 的非实验性 Unicode 字母或数字。 中间包含的字符必须在以下范围内: USASCII 字母(A-Z,az) / USASCII数字(0-9) / 下划线(“ _”),句点(“.”),Unicode 组合字符,扩展程序或非实验 Unicode 大于 U+007F的字母或数字。 任何组合中,前三个字母不得为“ XML”。 应小于32个字符。
            • direction Required | 参数是输入参数还是输出参数。 值必须是 in or out。
            • retval Optional | 最多将一个 out 参数指定为返回值。 如果在一个 action 中指定了一个返回值, 则该参数在文档顺序中一定要是在 out 类型的参数列表中排第一。
            • relatedStateVariable Required | 该参数关联的状态变量名称,
    • serviceStateTable Required | 每一个服务包含大于 0 个状态变量,该状态变量的元素对象包含如下元素:
      • stateVariable Required | 对 UPnP 委员会定义的每个状态变量都要在此实现。 如果 UPnP 供应商有通过添加其他状态变量来区分服务, 则每个其他变量也需要在此实现。 sendEvents 属性定义当此状态变量的值更改时是否生成事件消息; 非事件状态变量具有 sendEvents = “no”; 默认值为 sendEvents = yes。 该状态变量的元素对象包含如下元素:
        • name Required | 状态变量的名字, 不得包含连字符(-,UTF-8 中为 0x2D )或 哈希字符(#,UTF-8 中为 0x23)。 | 区分大小写 | 第一个字符必须是 USASCII 字母(A-Z,a-z)/ USASCII 数字(0-9) / 下划线(“ _”)/ 大于 U+007F 的非实验性 Unicode 字母或数字。
          支持的字符必须在以下范围 USASCII 字母(A-Z,a-z) / USASCII 数字(0-9) / 下划线(“ _”) / 句点(“.”) / Unicode组合字符 / 扩展程序或非实验性 Unicode 大于 U+007F 的字母或数字。 在任何情况下,前三个字母不得为“ XML”。
        • dataType Required | 与 XML Schema 第 2 部分《数据类型定义》的数据类型相同。 由 UPnP 委员会为标准状态变量定义, 或者由UPnP供应商指定用于扩展。 必须为以下值之一:
          • ui1 1 Byte 无符号整形。 数值范围 [0, 255] 。
          • ui2 2 Byte 无符号整形。 数值范围 [0, 65535]。
          • ui4 4 Byte 无符号整形。 数值范围 [0, 4294967295] 。
          • i1 1 Byte 有符号整形。 数值范围 [-128 , 127 ]。
          • i2 2 Byte 有符号整形。 数值范围 [-32768 ,32767 ]。
          • i4 4 Byte 有符号整形。 数值范围 [-2147483648, 2147483647] 。
          • int Fixed point, integer number. May have leading sign. May have leading zeros. (No currency symbol.) (No grouping of digits to the left of the decimal, e.g., no commas.)
          • r4 4 字节浮点型,格式和 float 一样, 数值范围在 [ 3.40282347E+38 , 1.17549435E-38 ] 之间。
          • r8 8 字节浮点型,格式和 float 一样, 复数数值范围在 [ -1.79769313486232E308, -4.94065645841247E-324 ] 之间。正数在 [4.94065645841247E-324 , 1.79769313486232E308 ] 之间。
          • number Same as r8.
          • fixed.14.4 Same as r8, 但是不会超过 14 个数字在小数点左边,不会超过 4 个数字在小数点右边。
          • float Floating point number. Mantissa (left of the decimal) and/or exponent may have a leading sign. Mantissa and/or exponent may have leading zeros. Decimal character in mantissa is a period, i.e., whole digits in mantissa separated from fractional digits by period. Mantissa separated from exponent by E. (No currency symbol.) (No grouping of digits in the mantissa, e.g., no commas.)
          • char Unicode string. 一个字节。
          • string Unicode string.不限长度。
          • date 格式遵从 ISO 8601 标准,但不包括 time data 类型.
          • dateTime 格式遵从 ISO 8601 标准,但不包括 timezone.
          • dateTime.tz 格式遵从 ISO 8601 标准,time 和 timezone 都是可选的。
          • boolean 值包含两个值 0 / 1, 0 代表 假,1 代表真,”true“ / ”false“, ”yes“ / ”no“ 也可以使用,但是不推荐。
          • bin.base64 MIME 样式的 Base64 编码的二进制 BLOB。 每 3 个字节分成 4 个 par,每一 Par 占 6 个 bit。 然后将 6 个 bit 映射到 8 个 bit 中。大小不受限制。
          • bin.hex 将一个字节拆分成两个十六进制字符表示,每 4 个 bit 对应一个 字符。 大小不受限制。
          • bin.uri uri
          • bin.uuid UUID( niversally Unique ID )。 代表八位位组的十六进制数字。 可选的嵌入式连字符将被忽略。
        • defaultValue Recommended | 初始值, 由 UPnP 委员会定义或者 UPnP 供应商定义. 必须符合以上的数值类型,必须在以下提到的 allowedValueList 或者 allowedValueRange 内。
        • allowedValueList Recommended | 枚举合法的字符串值。 禁止使用非字符串数据类型。 该字段与allowedValueRange 互斥。 子元素是有序的(参见NEXT_STRING_BOUNDED)。 包含以下子元素:
          • allowedValue Required | 一个合法的字符串值,对于标准的状态变量,这里的值由 UPnP 委员会定义,对于 UPnP 扩展的则又供应商自己决定。 该字段的值必须小于 32 个字符。
        • allowedValueRange Recommended. 定义一个合法的数值范围,该字段与 allowedValueList 互斥。 包含以下子元素:
          • minimum Required | 可选范围的下边界, 由 UPnP 委员会定义,对于 UPnP 扩展的则又供应商自己决定。 此处为单个数值。
          • maxiumum Required | 可选范围的上边界, 由 UPnP 委员会定义,对于 UPnP 扩展的则又供应商自己决定。 此处为单个数值。
          • step Recommended | 每一次 increment 或者 decrement 是的步进。例如在操作 v = v + s 中 s 的值。 由UPnP论坛工作委员会定义或委托给UPnP供应商。 单个数值。

元素 relatedStateVariable 的值必须是定义在同一服务里状态变量的名称。 relatedStateVariable 定义了参数的类型; argument 和 relatedStateVariable 之间不一定存在任何语义关系。 relatedStateVariable 必须在 serviceStateTable 中指定 stateVariable 的名称,该名称具有的 dataType, allowedValueList 和 allowedValueRange。 如果不存在带有适当定义的 stateVariable, 则工作委员会(或供应商)必须为此目的定义其他状态变量; 用于描述参数类型而定义的状态变量的名称的前缀应该是 “ A_ARG_TYPE_”。

allowedValueList 和 allowedValueRange 元素可用于指示设备可选的功能。 UPnP 委员会可能要求列表或范围中的所有值都应由所有供应商支持(无选项),或带有最小可选值的最小子集,或允许供应商完全决定。 如果委员会允许,供应商可以将供应商定义的 allowedValues 添加到标准 allowedValueList 中(名称前缀必须为 \'X_\')。 但是,应注意,定制的可选功能应该尽可能的减少了控制点可能依赖的值的数量,从而防止影响到交互操作时的兼容性。 如果设备功能会在设备运行期间发生变化,则委员会应定义单独的 action 来检测设备功能, 而不是将区分功能的信息嵌入到服务描述中。 而每次服务描述文档更改时,设备都应该取消之前的广播并重新发布新的功能的广播。 如果服务描述用于传达功能信息,则设备必须从服务描述中省略未实现的可选元素(动作,allowedValues等)。

为了将来的可扩展性,当像上面的清单那样处理 XML 时,设备和控制点必须忽略: (a)任何未知元素及其子元素或内容,以及(b)任何未知属性及其值。

控制点和设备应忽略嵌入在 XML 描述文档中的注释,和控制点本身不支持或者不懂的那些描述。

UPnP 服务描述应使用 UTF-8 编码。

任何属性或者元素的值都包含一个或者多个保留字(即 \'&\' 符号或者是 \'<\'), 如一定要使用此类字符则必须按照 XML 规范的 2.4 节的规定对文本进行转义, 并用等效的数字表示形式或字符串替换每个此类字符(例如 \'& amp;” 或 “& lt;”)。 出现在 URL 中的此类字符也可以根据 RFC 2396 第 2.4 节中指定的 URL 转义规则进行转义。 注意,服务在逻辑上可能没有任何动作,但具有状态变量和事件; 尽管这种服务不太可能,但是它将成为自治信息源。 但是注意,禁止使用没有状态变量的服务。

与设备描述不同,服务描述和关联的值不应使用 locale-specific(本地化) 的值。 应用程序应将信息从标准格式转换(格式化)为语言环境的正确语言(或格式)。 例如,日期以与语言环境无关的格式(ISO 8601)表示,而整数则以没有语言环境特定的格式表示(例如,没有货币符号,没有数字分组)。 字符串值应以标准的 “语言环境” 或与语言环境无关的方式表示。 具有 allowedValueList 的变量应使用 UPnP 标准语言的标记值。

但是,在某些情况下,动作的行为取决于语言环境。 在这种情况下,应该定义一个参数来指示语言环境, 比如可以使用 ACCEPT-LANGUAGE 和 CONTENT-LANGUAGE 标头(RFC 1766)。 如果存在多个依赖于语言环境的 action,则该服务可以包括一个操作, 该操作用于设置状态变量以指示语言环境,以消除了将语言环境标识符分别传递给每个操作的需要。

UPnP 委员会标准化的服务具有整数版本。 服务的每个更高版本都必须是先前版本的超集, 即,它必须完全包含服务的早期版本所定义的所有操作和状态变量。 UPnP 服务类型在服务的所有版本中都相同,而对于更高版本,该服务版本必须更大。 在工作委员会的开发过程中,设备和服务模板的版本可能具有非整数版本(例如“ 0.9”),但是标准化后必须成为整数。 设备和服务的版本号可能大于其设计所针对体系结构的主要版本号,但是设备服务也要应该响应低版本控制端的指令 (例如,“ Power:2” 可能被设计为在 UDA 版本 1.0 上工作)。

2.4 Description: UPnP Service Template

上面的清单还说明了 UPnP 服务描述和 UPnP 服务模板之间的关系。 如上所述,服务的 UPnP 描述是由 UPnP 供应商按照 UPnP 服务模板以 XML 编写的。 UPnP 委员会制作了 UPnP 服务模板,作为标准化设备的一种方法。

通过适当的占位符规范, 上面的清单可以是 UPnP 服务模板也可以是 UPnP 服务描述。 回想一下,某些占位符将由 UPnP 论坛工作委员会(红色)定义,即 action 及其 arguments,status 及其 dataType, range 和 event characteristics 。 如果将上面指定的这些实例化为代码,那么这就是一份 UPnP 服务模板, UPnP 服务模板是 UPnP 论坛工作委员会的主要交付成果之一。

更进一步,如果 UPnP 供应商(紫色)指定了上面列表中的其余占位符, 即由供应商指定的其他 action 和 status variables。 如果指定了这些占位符(以及其他占位符), 则列表将是 UPnP 服务描述,适用于对设备内服务的有效控制。

换句话说,UPnP 服务模板定义了服务的整体类型, 而 UPnP 服务描述则在模板的基础上包含了供应商的添加特定的特性。 第一个由 UPnP 委员会创建; 后者由 UPnP 供应商提供。

2.5 Description: Non-standard vendor extensions

如上所述,UPnP 供应商可以通过添加其他服务和嵌入式设备来区分其设备并扩展标准设备。 同样,UPnP 供应商可以通过添加其他操作或状态变量来扩展标准服务。 下表中列出了每种方法的命名约定,并在上面进行了详细说明。

如上表的最后两行所示, UPnP 供应商还可以将非标准 XML 添加到设备或服务描述中。 每个添加项都必须由供应商的 XML namespace 限制。 供应商添加的 XML 元素必须包含在以 X_ 开头的元素中, 并且该元素必须是标准元素的子元素。 可以将非标准属性添加到标准元素中, 前提是这些属性的元素名称是 X_ 开头的。

为了说明这一点, 下面列举了实际元素和值的占位符(斜体)。 以及一些 UPnP 供应商指定(紫色)字段替换后的样例。 和一些则由 UPnP 设备体系结构定义的样例(绿色)。

  • RootStandardElement Required | 一个标准的根元素. xmlns 属性定义了 namespaces。 在这个案例中,描述了一个标准 UPnP namespace 和一个以 \'n\' 为前缀的非标 namespace。
    • 注意对于设备的描述,这个描述必须存在 root 元素中。
    • 注意对于设备的描述,这个描述必须存在 scpd 元素中。
    AnyStandardElement
    Required | 任何标准元素,无论是根元素还是其他元素,文本的内容或者元素的内容。 必须作为标准设备或标准服务服务的一部分。 X_VendorAttribute 必须以 X_ 开头,后可以接任意字符串。 (前缀 A_ 是保留的。)
  • EltOnlyStandardElement Required | Element with content of element only. Must already be included as part of the standard device or service description. 仅包含元素内容的元素。 这些元素必须包含在标准设备或标准服务描述中。
    • 对于设备的描述,必须是其中之一: root, specVersion, device, iconList, icon, serviceList, service, and/or deviceList.
    • 对于服务的描述,必须是其中之一: scpd, actionList, action, argumentList, argument, serviceStateTable, stateVariable, allowedValueList, and/or allowedValueRange.
    X_VendorElement
    Required | 必须是以 \'X_\' 开头( 前缀 ‘A_’ 为保留字)。必须有 xmlns 属性. 可能还包括其他任意的 XML 内容。

如果控制点不理解自定义的一些标签,那么控制点应该忽略之。

2.6 Description: UPnP Template Language for devices

上面的段落解释了 UPnP 设备描述,并说明了如何从 UPnP 设备模板中实例化该描述。 如前所述,UPnP 设备模板是由 UPnP 论坛工作委员会制作的,这些模板是从 UPnP 模板语言衍生而来的。 该模板语言定义了设备和服务的有效模板。以下是与设备有关的该语言的清单和说明。

UPnP 模板语言是用 XML 语法编写的,并且派生自 XML Schema(第1部分:结构,第2部分:数据类型)。 XML 模式提供了一组 XML 的结构,这些结构表达(展示/阐述)了描述语言概念, 例如必需元素与可选元素,元素嵌套和值的数据类型(以及此处不感兴趣的其他属性)。 UPnP 模板语言使用这些 XML 架构构造来定义元素,例如以上详细列出的 specVersion,URLBase,deviceType 等。 因为 UPnP 模板语言是使用另一种精确的语言构造的,所以它是明确的。 并且由于 UPnP 模板语言,UPnP 设备模板和 UPnP 设备描述都是机器可读的, 因此自动化工具可以自动检查以确保后两个具有所有必需的元素,正确地嵌套并且具有正确的数据类型的值。

下面是由 UDA 定义的设备的 UPnP 模板。 这些元素在 UPnP 设备模板中有介绍(这里和上面的解释都用绿色表示); Below is where these elements are defined; above is where they are used.

紧随其后的是对使用的 XML Schema 元素,属性和值的简要说明。 本节末尾的 XML Schema 参考有更多详细信息。

UPnP Template Language for devices
  • ElementType 用新的派生语言定义元素。 name 属性定义元素名称。 dt:type 属性使用新的派生语言为 element 的值定义数据类型。
  • element 使用嵌套的方式来引用元素。 minOccurs 属性定义元素必须出现的最小次数。 默认值为 minOccurs = 1; 可选元素的 minOccurs =0。maxOccurs属性定义元素必须出现的最大次数。 默认值为maxOccurs = 1; 可以出现一次或多次的元素具有 maxOccurs = *。 Content =“textOnly” 表示元素不包含子元素; content =“eltOnly”表示元素包含子元素。
2.7 Description: UPnP Template Language for services

本章节介绍服务描述是如何从 UPnP 服务模板中实例化到的。像设备模板一样,UPnP 的服务模板也是有 UPnP 委员会定义的每个操作都要实现一次。 服务模板和设备模板也都是由 UPnP 模板语言衍生而来,像上面介绍的一样,UPnP 模板文档服从 XML 的语法格式(由 XML Schema 衍生而来), (这里主要产靠 Part1: struct 和 Part 2: DataTypes)。 下面是一个 service 描述模板的例子,这里绿色代表涉及的内容对应上面提到的内容。 下面定义的元素也揭示了他们的用法。

紧随其后的是对使用的 XML Schema 元素,属性和值的简要说明。本节末尾的 XML Schema 参考有更多详细信息。

UPnP Template Language for services
  • attribute 引用属性以声明该可能出现在哪些元素中。 像任何 XML 元素一样,AttributeType 元素可以具有自己的属性。 在此元素内使用 required 属性指示该属性是否必须存在; 可选属性 required=no。
  • AttributeType 定义一个新的 AttributeType,像任何XML元素一样,AttributeType元素可以具有自己的属性。 在此元素中使用 name 属性定义属性名称,因为它将在派生语言中使用。
  • element 使用嵌套的方式来引用元素。 minOccurs 属性定义元素必须出现的最小次数。 默认值为 minOccurs = 1; 可选元素的 minOccurs =0。maxOccurs属性定义元素必须出现的最大次数。 默认值为maxOccurs = 1; 可以出现一次或多次的元素具有 maxOccurs = *。 Content =“textOnly” 表示元素不包含子元素; content =“eltOnly”表示元素包含子元素。
  • ElementType 定义一个新的 element。 name 属性定义元素名称。dt:type 属性使用新的 element 的值定义数据类型。 model 定义此处是否有一个新的 elements, 属性指示新的派生语言中的元素是否可以包含此处未明确指定的元素; 如果仅可以使用以前的特定元素,则 model=closed。 content 属性指示可能包含的内容; 仅包含其他元素的元素具有 content = eltOnly; 仅包含字符串的元素的content = textOnly。
    • group 将内容组织为一组以指定序列。 minOccurs 属性定义组必须出现的最小次数。 maxOccurs 属性定义组必须出现的最大次数。 顺序属性限制元素的顺序; 当最多允许一个元素时,order = one。
2.8 Description: Retrieving a description

如上所述,控制点发现设备后,仍然对设备知之甚少。 要了解有关设备及其功能的更多信息,控制点必须使用设备在发现消息中提供的 URL 检索设备的 UPnP description(描述)。 然后,控制点必须使用设备描述中提供的 URL 检索一个或多个服务描述。 这是一个简单的基于 HTTP 的过程,并使用了整个 UPnP 协议堆栈的以下子集。 (本文档开头列出了整个UPnP协议栈。)

在协议的最上层,描述消息包含特定于厂商的信息,例如,设备类型,服务类型和所需的服务。 从协议栈向下移动,供应商内容将由 UPnP 论坛工作委员会提供的信息进行补充,例如型号名称,型号编号和特定的 URL。 来自以上各层的消息以本文档中定义的特定于 UPnP 的协议托管。 反过来,上述消息通过基于 IP/TCP 上的 HTTP 传递。 作为参考,上面[方括号]中的颜色表示在下面列出的描述消息中哪个协议定义了特定的标头和主体元素。

使用此协议栈,检索UPnP设备描述非常简单: 控制点向发现消息中的 URL 发出 HTTP GET 请求,并且设备在 HTTP 响应的正文中返回其描述。 类似地,为了检索 UPnP 服务描述,控制点向设备描述中的 URL 发出 HTTP GET 请求,然后设备在 HTTP 响应的正文中返回该描述。 响应和请求的标头和正文将在下面详细说明。

首先,控制点必须使用以下格式的GET方法发送请求。 斜体部分需要填上实际的值

获取 description 的请求没有负载字段, 但在请求头之后一定要有空行(即以 \r\n\r\n 结束)。

下面列出的是请求行的详细信息以及出现在上面列表中的标题。 除非另有说明,否则所有标头值均区分大小写。

  • Request line
    • GET HTTP GET Method
    • path to description 获取 device 或者 service 的 description 的资源路径。device description 的 URL 在 discovery 响应的 LOCATION 字段中。 service description 的 URL 在 device description 的 SCPDURL 字段中。
    • HTTP/1.1 HTTP 版本
  • headers
    • HOST Required | 服务的域名或者 IP 地址(端口号可省略), device description 的 URL 在 discovery 响应的 LOCATION 字段中。 service description 的 URL 在 device description 的 SCPDURL 字段中。 如果端口号没有给出,则默认为 80 端口。
    • ACCEPT-LANGUAGE 在获取设备描述的时候推荐添加此字段,该字段表示有限选择的语言, 如果这个字段没有指定可用的语言,则返回的描述采用用默认语言。 请参考( RFC 1766 )。

在 Control Point 发出获取描述的请求之后,设备需要马上响应 ControlPoint 请求,将 description 文档发给 ControlPoint。 设备必须在合适的时间(一定要小于 30s)响应该请求。 如果在这个时间内响应失败了,那么 ControlPoint 必须马上重新发送一个请求。 设备必须以以下的格式返回响应信息。(斜线部分应该替换成具体的值)

这个响应的负载部即是设备或者服务的描述部分。在上面的几个小节中阐述了。

下面将会介绍请求头字段部分,所有的值都是大小写敏感的。

  • headers
    • CONTENT-LANGUAGE 如果请求的头字段中有 ACCEPT-LANGUAGE 字段则必须存在该字段。否则应该遵从 RFC 1766 中对语言格式的要求。
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 该处的值必须为 text/xml
    • DATE Recommended | 该处记录响应生成是的时间,格式遵从 RFC 2616 中定义的 “rfc1123-date” 格式
    • SERVER 关于请求 description 的响应,不需要此头字段。(我不知道为什么要存在此说明,莫名其妙)

请注意,由于 HTTP 1.1 允许使用分块编码,因此,如果 GET 请求指定 HTTP 1.1, 则某些设备可能会使用分块编码发送描述。 因此,建议所有在 GET 请求中包含 HTTP 1.1的实现都支持接收分块编码。

2.9 Description references>
  • ISO 8601 ISO (International Organization for Standardization). Representations of dates and times, 1988-06-15. 传送门
  • RFC 822 Standard for the format of ARPA Internet text messages. 传送门
  • RFC 1123 Includes format for dates, for, e.g., HTTP DATE header 传送门
  • RFC 1766 Format for language tag for, e.g., HTTP ACCEPT-LANGUAGE header. 传送门 See also for language codes 传送门.
  • RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies. 传送门
  • RFC 2046 Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types. 传送门
  • RFC 2083 PNG (Portable Network Graphics) Specification Version 1.0 传送门传送门
  • RFC 2387 Format for representing content type, e.g., mimetype element for an icon. 传送门
  • RFC 2396 Uniform Resource Identifiers: Generic Syntax. 传送门
  • RFC 2616 HTTP: Hypertext Transfer Protocol 1.1. 传送门
  • UPC Universal Product Code. 12-digit, all-numeric code that identifies the consumer package. Managed by the Uniform Code Council. 传送门
  • XML Extensible Markup Language. 传送门
  • XML Schema (Part 1: Structures, Part 2: Datatypes) Grammar defining UPnP Template Language. 传送门1, 传送门2
3. Control

UPnP™ networking 的第三步就是 Control。该步骤在 addressing, discovery, description 之后,但是该步骤与第四步(eventing)是独立的。 通过控制,控制点通过调用设备描述中的 action 并附带参数来控制。 Control 和 Eventing 是 presentation 的延伸。

给定一个设备和该设备服务的信息,控制点可以向该设备发起请求调用服务的 action,并接受服务执行动作的响应。 调用的方式是通过 RPC 的方式实现的,控制点向服务点发送请求控制数据,服务点接收控制命令,并且返回成功或者失败的结果给控制点

如何控制设备?在设备的 description 中有个 controlURL 元素,该元素的值即为服务器地址, 控制点向这个地址发送合适的请求,即可实现对设备的控制。 控制点的动作会最终影响设备的运行时状态。 当这些状态改变,设备应该将这些状态发送给感兴趣(之前订阅过)的控制点。 这个章节将会介绍发起控制的消息格式,响应格式。 Eventing 章节将会介绍 event 相关的信息。

委员会和供应商会定义一些 actions 的状态, 以允许控制点确定设备的当前值的动作。 与 action 的调用类似,控制点需要发送一个定义好的请求。服务收到该请求之后, 服务需要将请求的状态变量,以及变量的值返回给控制点。 每个服务需要负责保持其状态表的一致性,以便控制点在轮询时查到的值是有意义的。 事件部分介绍了变量值的自动通知。

只要设备 discovery 时广播的消息没有过期,那么控制点即可认为广播的服务是可用的。 如果设备取消了广播,控制点必须认定之前广播的设备和服务不可用了。

UPnP 的控制消息和响应的负载应该是 UTF-8 编码的。

尽管 UDA 确实定义了一套方法来调用动作并轮询值, 但 UDA 并未指定或约束针对在控制点上运行的应用程序的 API 设计; 操作系统供应商可以创建适合其客户需求的API。

如果有很大的一部分数据需要随 action 一起传输(特别是事先不知道数据的大小), 则不建议将数据作为 SOAP 参数的一部分或者作为 MIME 附件发送, 相反的应该采用 out-of-band 的方式传输。 例如,你可以在 argument 中包含一个 URL。 控制点可以通过该 URL 去GET / PUT /POST 获取目标数据。 如果事先不知道数据量,则可以使用 HTTP chunked encoding。

在接下来的章节将对 Control Msg 的格式进行详细解释。

3.1 Control: Protocols

为了执行动作或者获取状态,控制点需要用到下面的这个 UPNP 协议栈。 ( UPnP 的完整协议栈在本文档开头有说明)

在协议栈最上层,控制信息包含了供应商(vendor)的一些信息(比如:参数值)。 接下来供应商的 Content 由 UPnP 委员会补充(如:action 的名字,argument 的名字,variable 的名字)。 以上各层的消息都由 UPnP-specific protocols 承载,协议定义在本文档中。 因此,上述消息使用简单对象访问协议(SOAP)标头和主体元素进行格式化, 并且这些消息是通过基于 IP/TCP 上的 HTTP 传递。 作为参考,上面[方括号]中的颜色表示在下面列出的订阅消息中哪个协议定义了特定的标头元素。

3.2 Control: Action

控制点调用服务 action, 服务返回操作结果或者错误,都采用 SOAP 封包的形式进行通信(传输协议是 HTTP )。

3.2.1 Control: Action: Invoke

简单对象访问协议(SOAP)定义了利用 XML 和 HTTP 实现远程过程调用的一组规范。 UDA 使用 SOAP 将控制消息传递到设备,并将结果或错误返回给控制点。

SOAP 定义了其他 HTTP 标头,并且为了确保它们不会与其他 HTTP 扩展名混淆, SOAP 遵循 HTTP 扩展框架(RFC 2774),并在 MAN 标头中指定 SOAP 唯一 URI,并以 M- 作为 HTTP 方法的前缀。 在这里,方法为 M-POST。 使用 M-POST 要求 HTTP 服务器查找和理解 SOAP 唯一 URI 和特定于 SOAP 的标头。

为了向防火墙和代理提供更大的管理灵活性,SOAP 指定必须首先尝试不使用 MAN 头或 M- 前缀的请求。 如果请求的响应为 “405不允许的方法” 而被拒绝,则必须使用 MAN 头和 M 前缀发送第二个请求。 如果该请求以 “未实现501” 或 “未扩展510” 的响应被拒绝, 则该请求将失败。 (其他HTTP响应应根据HTTP规范进行处理。)

下面是使用 POST 方法发送的控制消息的列表(不带 MAN 头),后面是头和主体的说明。 紧随其后的是使用 M-POST 方法和 MAN 标头发送的控制消息列表。

要调用设备服务上的操作,控制点必须使用以下格式的POST方法发送请求。 斜体值是实际值的占位符。

下面列出的是上面列表中出现的请求行,标题和正文元素的详细说明。 所有标头值和元素名称均区分大小写,值不区分大小写,除非另有说明。 元素的顺序无关紧要,除非另有说明。 必需元素必须仅出现一次(没有重复),推荐或可选元素最多只能出现一次,除非另有说明。

  • Request line
    • POST HTTP method.
    • path control url 组件 URL 路径,用于控制此服务(设备描述的 service 元素的 controlURL 子元素)。| single URL
    • HTTP/1.1 HTTP/1.1 版本。
  • headers
    • HOST Required | URL 的域名或 IP 地址以及用于控制此服务的 URL 的可选端口组件(设备描述的 service 元素的 controlURL 子元素)。 如果端口为空或未指定,则假定端口为 80。
    • ACCEPT-LANGUAGE (该控制消息无 ACCEPT-LANGUAGE 头)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • MAN (POST 请求无此字段, M-POST 请求则需要附带此字段)
    • SOAPACTION Required | SOAP 定义的此标头 | 必须是服务类型 | Hash mark | 所有的名称都应该用双引号引起来。 如果是使用 M-POST 方法, HTTP 头中必须有 MAN 头。 | Single URI | 必须指定控制服务的版本,这里方便服务识别当前控制点可支持的版本。 其值可以是定义了指定操作的服务类型的任何版本。
  • body
    • Envelope Required | 由 SOAP 定义的元素 | xmlns 属性的值必须为 http://schemas.xmlsoap.org/soap/envelope/. | encodingStyle 属性的值必须为 http://schemas.xmlsoap.org/soap/encoding/ | 该元素包含以下子元素:
      • Body Required | 由 SOAP 定义 | 应该符合 SOAP 的命名空间,该元素包含以下子元素:
        • actionName Required | 元素的名字为被调用 action 的名字, xmlns 的属性值必须为服务类型,用双引号包含起来, 这里指定的版本号必须与 SOAPACTION 中的版本号一致。 该元素的值大小写铭感。 该元素标签必须为 Body 的第一个标签,该元素标签包含如下子元素。
          • argumentName 如果 action 拥有参数,那么此元素必须存在, 每一个参数(argument) 需要实现一次(元素的名称不受命名空间的限制,元素名称符合上下文即可), | 区分大小写 | 该元素的值必须是符合 UPnP 服务描述中定义的数据类型和数据范围 | 顺序也必须按照 SCPD 中指定的顺序(该处的顺序服从 description spec 中 in 的排列顺序),

如果 CONTENT-TYPE 是不支持的值(不是\'text/xml\'), 那么设备必须返回一下 HTTP 错误状态码(“415 Unsupported Media Type”)。

为了将来的可扩展性,当像上面的清单那样处理 XML 时,设备控制点必须忽略它不认识的元素,子元素,内容,属性,值。

设备和控制点应该忽略任何有可能受到的但是不认识的 XML 注释或者 XML 的处理说明。

XML 命名空间前缀不必是上面给出的特定示例(例如,“ s”或“ u”); 它们可以是任何遵循常规 XML 命名空间机制规则的值; 设备必须接受使用其他合法 XML 命名空间前缀的请求。

如果一个 action 没有 \'in\' 参数,那么你可以用 combine the opening and closing XML tags 表示。 (比如: 可以用“<actionname/>” 代替 “<actionname></actionname>”).

当一个参数的值中包含保留符号时(如 ‘&’ / ’<‘),必须按照 XML 规范第 2.4 节的规定对文本进行转义, 并用等效的数字表示形式或字符串(例如 ‘&amp;’ 或 ‘&lt;\')替换每个此类字符。 出现在 URL 中的此类字符也可以根据 RFC 2396 的 2.4 节中指定的 URL 转义规则进行转义。

请注意,由于 HTTP 1.1 允许使用分块编码,因此,如果 POST 方法标头指定 HTTP 1.1, 则某些控制点可能会使用分块编码发送操作请求。 不支持使用分块编码接收操作请求的设备实现应返回 505 HTTP Version Not Supported 错误。

如果带有 POST 请求的响应为 405 Method Not Allowed 被拒绝, 则控制点必须以以下格式发送带有方法 M-POST 和 MAN 的第二个请求。 斜体值是实际值的占位符。

(Message body for request with method M-POST is the same as body for request with method POST. See above.)

  • Request Line
    • M-POST HTTP 可扩展框架定义的方法 (RFC 2774)
    • path control url 组件 URL 路径,用于控制此服务(设备描述的 service 元素的 controlURL 子元素)。| single URL
    • HTTP/1.1 HTTP/1.1 版本。
  • Headers
    • HOST Required | URL 的域名或 IP 地址以及用于控制此服务的 URL 的可选端口组件(设备描述的 service 元素的 controlURL 子元素)。 如果端口为空或未指定,则假定端口为 80。
    • ACCEPT-LANGUAGE (该控制消息无 ACCEPT-LANGUAGE 头)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • MAN Required | 此处的值必须是 "http://schemas.xmlsoap.org/soap/envelope/"。 ns 则定义了为其他字段(如:SOAPACTION)定义了命名空间( namespace)(如 01)
    • SOAPACTION Required | SOAP 定义的此标头 | 必须是服务类型 | Hash mark | 所有的名称都应该用双引号引起来。 如果是使用 M-POST 方法, HTTP 头中必须有 MAN 头。 | Single URI | 必须指定控制服务的版本,这里方便服务识别当前控制点可支持的版本。 其值可以是定义了指定操作的服务类型的任何版本。
3.2.2 Control: Action: Response

服务必须在 30s 内完成请求的操作,并做出响应,这里的 30s 包括预期的传输时间(从传输操作消息的时间到接收到相关响应的时间测得的时间)。 应该定义的完成操作时间的时间比 30s 要短,以便尽早返回响应。 如果服务在此时间内无法响应,则控制点应执行的操作取决于应用程序交互设计。 服务必须以以下格式发送响应。 斜体值是实际值的占位符。

下面将对 response line / headers / body elements 作详细解释。 所有头( header )的值和元素( element )的名称均区分大小写; 除非特殊申明,所有值都不区分大小写; 除非特殊申明,所有的元素的顺序都是无关紧要的。 除非另有说明,否则必需元素必须仅出现一次(没有重复),推荐或可选元素当且仅当出现一次。

  • Response line
    • HTTP/1.1 HTTP version
    • 200 OK HTTP success code
  • Headers
    • CONTENT-LANGUAGE (控制消息中无此头)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • DATE Recommended | 该处的时间为响应生成时的时间,时间格式为 “rfc1123-date”, 此格式定义于 RFC 2616.
    • EXT Required. 确认 MAN 中指定的请求类型是可被识别的 | 此处只有头,无值
    • SERVER Required | 此处并列如下参数,操作系统名称和操作系统版本,此两个值用 / 充当分割符, UPnP的版本,以及产品的名称和产品的版本。其中操作系统信息,UPnP 信息,和产品信息之间用空格充当分割符。| String | 必须准确反映设备支持的 UPnP 设备体系结构的版本号。 | 控制点必须准备好接受比控制点本身实现的更高的次要版本号(但主版本号相同)。 例如,实现 UDA 版本 1.0 的控制点应当能够能够与实现 UDA 版本 1.1 的设备进行互操作。
  • Body
    • Envelope Required | 由 SOAP 定义的元素 | xmlns 属性的值必须为 http://schemas.xmlsoap.org/soap/envelope/. | encodingStyle 属性的值必须为 http://schemas.xmlsoap.org/soap/encoding/ | 该元素包含以下子元素:
      • Body Required | 由 SOAP 定义 | 应该符合 SOAP 的命名空间,该元素包含以下子元素:
        • 指明 Required | 该元素的名称对应请求操作的 action 的名称, xmlns 属性的值必须使用双引号引起 | 而这里的 Version 也必须与请求头中 SOAPACTION 指定的 version 相同 | 大小写敏感 | 而且必须是 Body 元素中的第一个子元素 | 此元素包含一下子元素:
          • argumentName Required | 当且仅当 action 拥有 out 参数的时候才拥有此字段, 这里的值即填写 action 执行之后的。 | 每一个 out 参数只允许出现一次 | 如果 action 有一个标记为 retval 的参数,则此参数必须是第一个元素。 (元素名称不受名称空间限制,符合上下文语义即可) | 区分大小写。 UPnP服务描述所定义的单一数据类型 | 参数出现的顺序必须与设备可用的服务描述(SCPD)中指定的顺序相同

为了将来的可扩展性,当像上面的清单那样处理,设备和控制点必须忽略自己不认识的元素或者子元素,和任何认识的属性和该属性的值。

所有的控制点和设备应该忽略 XML 的注释或者他们看不懂的 XML 的流程说明。

XML 命名空间的前缀不必是上面给出的特定示例(例如,“ s”或“ u”), 它们可以是任何遵循常规 XML 命名空间规则的值, 控制点必须接受使用其他合法 XML 命名空间前缀的响应。

如果一个 action 没有一个 out 参数,那么该参数使用单闭合标签是有效的。 (如: 可以用 “<actionnameResponse/>” 替换 “<actionnameResponse></actionnameResponse>”)

当任何参数的值包含一个或多个保留为标记的字符(例如,“&”或小于(“<”))时, 必须根据 XML 规范 2.4 节的规定对文本进行转义。 并用等效的数字表示形式或字符串替换每个此类字符(例如 “&amp;”或“&lt;”)。 出现在 URL 中的此类字符也可以根据 RFC 2396 第 2.4 节中指定的URL转义规则进行转义。

如果控制点使用 HTTP/1.0 承载 SOAP 协议,但是没有携带 KeepAlive 头, 设备必须在响应结束之后关闭通信 socket。 如果控制点使用 HTTP/1.1 承载 SOAP 协议,而且设置 Connection: CLOSE, 那么设备必须在������之后关闭通信 socket。

请注意,由于 HTTP 1.1 允许使用分块编码, 因此,如果 POST 请求指定 HTTP 1.1, 则某些设备可能会使用分块编码来发送操作响应。 因此,建议在 POST 请求中包括 HTTP 1.1 的所有实现都支持接收分块编码。

如果服务在调用控制点发送的操作时遇到错误, 则该服务必须在 30 秒内发送响应(包括预期的传输时间)。 注意 out 参数不能用于传达错误信息, out 参数只能用于返回数据。 错误响应必须以以下格式发送。 斜体值是实际值的占位符。

下面列出的是上面列表中出现的 response line / Headers 和 Body 的详细信息。 所有头( header )的值和元素( element )的名称均区分大小写; 除非特殊申明,所有值都不区分大小写; 除非特殊申明,所有的元素的顺序都是无关紧要的。 除非另有说明,否则必需元素必须仅出现一次(没有重复),推荐或可选元素当且仅当出现一次。

  • Response line
    • HTTP/1.1 HTTP version
    • 500 Internal Server Error HTTP error code
  • Headers
    • CONTENT-LANGUAGE (无此头字段)(PS: 不清楚卫生么要这样提示,我理解应该是如果请求中包含 ACCEPT-LANGRAGE, 而且当前控制参数的确包含本地化的一些值,则这里会出现该字段)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • DATE Recommended | 该处的时间为响应生成时的时间,时间格式为 “rfc1123-date”, 此格式定义于 RFC 2616.
    • EXT Required. 确认 MAN 中指定的请求类型是可被识别的 | 此处只有头,无值
    • SERVER Required | 此处并列如下参数,操作系统名称和操作系统版本,此两个值用 / 充当分割符, UPnP的版本,以及产品的名称和产品的版本。其中操作系统信息,UPnP 信息,和产品信息之间用空格充当分割符。| String | 必须准确反映设备支持的 UPnP 设备体系结构的版本号。 | 控制点必须准备好接受比控制点本身实现的更高的次要版本号(但主版本号相同)。 例如,实现 UDA 版本 1.0 的控制点应当能够能够与实现 UDA 版本 1.1 的设备进行互操作。
  • Body
    • Envelope Required | 由 SOAP 定义的元素 | xmlns 属性的值必须为 http://schemas.xmlsoap.org/soap/envelope/. | encodingStyle 属性的值必须为 http://schemas.xmlsoap.org/soap/encoding/ | 该元素包含以下子元素:
      • Body Required | 由 SOAP 定义 | 应该符合 SOAP 的命名空间,该元素包含以下子元素:
      • Fault Required | 由 SOAP 定义 | 当调用 action 失败的时候存在此字段 | 错误的值应该符合 SOAP 定义的命名规则 | 包含以下子元素:
        • faultCode Required | 由 SOAP 定义 | 值必须为 ‘Client’ (该处的值应该服从 SOAP 的命名规范)
        • faultstring Required | 由 SOAP 定义 | 值必须为 \'UPnPError\'
        • detail Required | 由 SOAP 定义 | 包含以下子元素:
          • UPnPError Required | 由 UDA 定义 | 包含以下子元素:
            • errorCode Required | 由 UDA 定义 | 这里定义遇到的是哪一种错误 | 请参考下面的表格中的值,以及对应的错误类型 | 整形
            • errorDescription UDA 定义的推荐字段,用于简短描述错误类型 | 具体 Code 对应的推荐值参考下表 | 供应商也可以自定义值,但最好设计成是人类可读的 | 此处描述字段值的长度应该 < 256 个字符

下表总结了已定义的错误类型以及 errorCode 和 errorDescription 元素的相应值。

为了将来的可扩展性,当像上面的清单那样处理,设备和控制点必须忽略自己不认识的元素或者子元素,和任何认识的属性和该属性的值。

XML 命名空间前缀不必是上面给出的特定示例(例如,“ s”或“ u”); 它们可以是任何遵循常规 XML 名称空间机制规则的值; 控制点必须接受使用其他合法 XML 名称空间前缀的响应。

3.3 Control: Query for variable

UPnP 论坛已将 QueryStateVariable action 作废, 除非在有限的测试场景测试出现非期望结果,否则控制点不应该再使用此方法, 委员会和供应商应明确定义查询状态变量所需的功能。 如果需要,这样的显式查询动作可以包括多个状态变量。 UPnP 设备体系结构中保留了 QueryStateVariable 的以下定义,以供参考并与早期实现向后兼容。

除了调用设备服务上的操作外,控制点还可以通过发送查询消息来轮询服务以获取状态变量的值。 查询消息一次只能查询一个状态变量;如果要查询多个,则必须发送多条查询消息。

此查询消息与服务的事件(如果有)是相互独立的。 如果想要对 variable 的值进行检查,查询的次数会远远多余事件发送的次数, 关于事件的部分描述了事件管理。

3.3.1 Control: Query: Invoke

要查询状态变量的值,控制点必须以以下格式发送请求。 斜体值是实际值的占位符。

下面列出的是上面列表中出现的 request line / Headers 和 Body 的详细信息。 所有头( header )的值和元素( element )的名称均区分大小写; 除非特殊申明,所有值都不区分大小写; 除非特殊申明,所有的元素的顺序都是无关紧要的。 除非另有说明,否则必需元素必须仅出现一次(没有重复),推荐或可选元素当且仅当出现一次。

  • Request line
    • POST HTTP 的请求方法
    • path of control URL 服务器用于接受控制的服务资源路径 (该路径在设备描述文档的 service 元素的 controlURL 元素下)。 | Single relative URL.
    • HTTP/1.1 HTTP 版本.
  • Headers
    • HOST Required | 用于发送控制指令的服务器的服务地址(可能是域名也可能是 IP 地址) (该地址存在与设备描述文档的 service 元素下), 该地址可能会携带端口号,如果没有携带,则默认为是 80 端口。
    • ACCEPT-LANGUAGE (Control 消息不需要此头)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • MAN (POST 请求无此字段, M-POST 请求则需要附带此字段)
    • SOAPACTION Required | SOAP 定义的此标头 | 之必须是 urn:schemas-upnp-org:control-1-0#QueryStateVariable | 如果 HTTP method 使用的是 M-POST, 则头名必须使用 MAN 头中使用的 namespace 来限定 | Single URI
  • Body
    • Envelope Required | 由 SOAP 定义的元素 | xmlns 属性的值必须为 http://schemas.xmlsoap.org/soap/envelope/. | encodingStyle 属性的值必须为 http://schemas.xmlsoap.org/soap/encoding/ | 该元素包含以下子元素:
      • Body Required | 由 SOAP 定义 | 应该符合 SOAP 的命名空间,该元素包含以下子元素:
        • QueryStateVariable Required | 由 UPnP 定义,这里填写的即使 Action 的名字, 其中 xmlns 属性的值必须为 urn:schemas-upnp-org:control-1-0, 而且此元素必须是 Body 元素的第一个子元素, | 包含一下子元素。
          • varName Required | 由 UPnP 定义,是 state 变量的名字,该状态必须从属于 QueryStateVariable 的命名空间 | String

为了将来的可扩展性,当按照 Flexible XML Processing Profile(FXPP)的规定处理上述清单中的 XML 时, 设备和控制点必须忽略:(a)任何未知元素及其子元素或内容,以及(b)任何未知元素 属性及其值。

如果带有 POST 的请求被拒绝并返回响应 405 Method Not Allowed, 则控制点必须使用上述方法 M-POST 和 MAN 发送第二个请求。

3.3.2 Control: Query: Response

要回答有关状态变量值的查询,服务必须在 30 秒内响应(此 30 秒包含传输时间), 如果服务在此时间内无法响应,则控制点应执行的操作取决于应用程序。 服务必须以以下格式发送响应。 斜体值是实际值的占位符。

下面列出的是上面列表中出现的 response line / Headers 和 Body 的详细信息。 所有头( header )的值和元素( element )的名称均区分大小写; 除非特殊申明,所有值都不区分大小写; 除非特殊申明,所有的元素的顺序都是无关紧要的。 除非另有说明,否则必需元素必须仅出现一次(没有重复),推荐或可选元素当且仅当出现一次。

  • Response line
    • HTTP/1.1 HTTP version.
    • 200 OK HTTP success code.
  • Headers
    • CONTENT-LANGUAGE (无此头字段)(PS: 不清楚卫生么要这样提示,我理解应该是如果请求中包含 ACCEPT-LANGRAGE, 而且当前控制参数的确包含本地化的一些值,则这里会出现该字段)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • DATE Recommended | 该处的时间为响应生成时的时间,时间格式为 “rfc1123-date”, 此格式定义于 RFC 2616.
    • EXT Required. 确认 MAN 中指定的请求类型是可被识别的 | 此处只有头,无值
    • SERVER Required | 此处并列如下参数,操作系统名称和操作系统版本,此两个值用 / 充当分割符, UPnP的版本,以及产品的名称和产品的版本。其中操作系统信息,UPnP 信息,和产品信息之间用空格充当分割符。| String | 必须准确反映设备支持的 UPnP 设备体系结构的版本号。 | 控制点必须准备好接受比控制点本身实现的更高的次要版本号(但主版本号相同)。 例如,实现 UDA 版本 1.0 的控制点应当能够能够与实现 UDA 版本 1.1 的设备进行互操作。
  • Body
    • Envelope Required | 由 SOAP 定义的元素 | xmlns 属性的值必须为 http://schemas.xmlsoap.org/soap/envelope/. | encodingStyle 属性的值必须为 http://schemas.xmlsoap.org/soap/encoding/ | 该元素包含以下子元素:
      • Body Required | 由 SOAP 定义 | 应该符合 SOAP 的命名空间,该元素包含以下子元素:
        • QueryStateVariableResponse Required | 由 SOAP 定义 | xmlns 属性的值必须为 urn:schemasupnp-org:control-1-0 | 必须是 Body 元素的第一个元素 | 包含如下子元素:
          • return Required | 由 UPnP 定义 | 元素名字 element defined by UPnP. (元素名称不受名称空间限制;语言符合下文即可。) 此处的值是在请求中的 varName 元素中指定的状态变量的当前值。

为了将来的可扩展性,当按照 Flexible XML Processing Profile(FXPP)的规定处理上述清单中的 XML 时, 设备和控制点必须忽略:(a)任何未知元素及其子元素或内容,以及(b)任何未知元素 属性及其值。

当任何参数的值包含一个或多个保留为标记的字符(例如,“&”或小于(“<”))时, 必须根据 XML 规范 2.4 节的规定对文本进行转义。 并用等效的数字表示形式或字符串替换每个此类字符(例如 “&amp;”或“&lt;”)。 出现在 URL 中的此类字符也可以根据 RFC 2396 第 2.4 节中指定的URL转义规则进行转义。

如果服务无法为变量提供值,则服务必须在 30 秒内发送响应(该 30s 包含预期的传输时间)。 响应必须以以下格式发送。 斜体值是实际值的占位符。

下面列出的是上面列表中出现的 response line / Headers 和 Body 的详细信息。 所有头( header )的值和元素( element )的名称均区分大小写; 除非特殊申明,所有值都不区分大小写; 除非特殊申明,所有的元素的顺序都是无关紧要的。 除非另有说明,否则必需元素必须仅出现一次(没有重复),推荐或可选元素当且仅当出现一次。

  • Response line
    • HTTP/1.1 HTTP version.
    • 500 Internal Server Error HTTP error code.
  • Headers
    • CONTENT-LANGUAGE (无此头字段)(PS: 不清楚卫生么要这样提示,我理解应该是如果请求中包含 ACCEPT-LANGRAGE, 而且当前控制参数的确包含本地化的一些值,则这里会出现该字段)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • DATE Recommended | 该处的时间为响应生成时的时间,时间格式为 “rfc1123-date”, 此格式定义于 RFC 2616.
    • EXT Required. 确认 MAN 中指定的请求类型是可被识别的 | 此处只有头,无值
    • SERVER Required | 此处并列如下参数,操作系统名称和操作系统版本,此两个值用 / 充当分割符, UPnP的版本,以及产品的名称和产品的版本。其中操作系统信息,UPnP 信息,和产品信息之间用空格充当分割符。| String | 必须准确反映设备支持的 UPnP 设备体系结构的版本号。 | 控制点必须准备好接受比控制点本身实现的更高的次要版本号(但主版本号相同)。 例如,实现 UDA 版本 1.0 的控制点应当能够能够与实现 UDA 版本 1.1 的设备进行互操作。
  • Body
    • Envelope Required | 由 SOAP 定义的元素 | xmlns 属性的值必须为 http://schemas.xmlsoap.org/soap/envelope/. | encodingStyle 属性的值必须为 http://schemas.xmlsoap.org/soap/encoding/ | 该元素包含以下子元素:
      • Body Required | 由 SOAP 定义 | 应该符合 SOAP 的命名空间,该元素包含以下字段:
        • Fault Required | 由 SOAP 定义. 此标签包含为什么服务没有返回请求变量值的原因。 格式需要服从 SOAP 的命名规范 | 包含以下子元素:
          • faultcode Required | 由 SOAP 定义 | 值必须为 ‘Client’ (该处的值应该服从 SOAP 的命名规范)
          • faultstring Required | 由 SOAP 定义 | 通用描述 UPnP ErrorCode 的字符串 | 请查看以下表格中的说明
          • detail Required | 由 SOAP 定义 | 包含以下子元素:
            • UPnPError Required | 由 UPnP 定义 | 包含以下子元素:
              • errorCode Required | 由 UPnP 定义 | 定义遭遇何种 Error 的 Code | 具体的值请查看下表定义 | Integer
              • errorDescription Required | 由 UPnP 定义 | 一个解释该错误类型的简短说明 | 请查看以下表格中的说明 | 建议小于 256 个字符 | String

下表总结了 UPnP 定义的常用的 errorCode 和 errorDescription。

如果设备没有实现 QueryStateVariable 指定的 Action, 设备服务应该返回 401 Invalid Action

为了将来的可扩展性,当像上面的清单那样处理,设备和控制点必须忽略自己不认识的元素或者子元素,和任何认识的属性和该属性的值。

3.4 Control references
  • RFC 1123 Includes format for dates, for, e.g., HTTP DATE header. 传送门
  • RFC 2396 Uniform Resource Identifiers: Generic Syntax 传送门
  • RFC 2616 HTTP: Hypertext Transfer Protocol 1.1 传送门
  • RFC 2774 HTTP Extension Framework. 传送门
  • SOAP Simple Object Access Protocol. 传送门
  • XML Extensible Markup Language. 传送门
4. Eventing

Eventing 是 UPnP™ networking 的第四步,通过 Eventing 控制点监听设备的变化。 Control 和 Eventing 作为设备的控制和展示借口,相当于 presntation (第五步)的外延。

在控制点 discovery 和获取设备 description 之后,控制点知道了设备包含的设备和服务信息。 正如 Description 那节描述的那样,UPnP 服务描述包括服务的 action 列表和运行时服务状态变量列表。 如果这些状态变量中的一个或多个发生了事件,则服务在这些变量更改时发布更新, 并且控制点可以订阅以接收此信息。 在本节中, Publisher 指事件的源(通常是设备的服务), 而订户指事件的目的地(通常是控制点)。

为了订阅事件,订阅者发送订阅消息。 如果订阅被接受,则设备需要返回一个有效期给订阅者。 为了使订阅保持活动状态,订阅者必须在订阅到期之前对其订阅进行续订。 当订阅者不再需要来自 Publisher 的事件时,订阅者应取消其订阅。 本节在下面详细说明了订阅,续订和取消消息。

Publisher 通过发送事件消息来通知状态变量的更改。 事件消息包含一个或多个状态变量的名称以及这些变量的当前值,以 XML 表示。 当订户第一次订阅时,会发送一个特殊的初始事件消息。 该事件消息包含所有事件变量的名称和值,并允许订户初始化其服务状态模型。 为了支持具有多个控制点的方案,可以使用事件来使感兴趣的控制点及时的知晓设备(服务)的状态变更(即便这个变更是由其他控制点影响的)。 当设备(服务)的状态变更,应该向所有订阅者发送所有事件消息,所有的订阅者也应该接收所有的 event, 并且无论状态变量为何更改(不管是因为响应 action 的请求导致的或因为服务因自身原因导致的)都发送事件消息更改。 本节将在下面详细说明事件消息的格式。

一些状态变量可能变化的速度很快,以至于事件并不能及时的将这些变化发出, 一个可选的做法是,如果来不及报告,那么就过滤或者平滑的处理掉中间变化的状态变量, 只报告最后的状态即可。 有些状态可能包含的数值太大或者 size 太大,而以至于订阅者无法识别(使用),针对这种情况, 由于这个原因或其他原因,服务可以将一个或多个状态变量指定为 non-event, 并且不将该事件消息发送给订户即可。 为了确定此类设置为 non-event 变量的当前值,控制点必须显式轮询,当然前提是假定设备(服务)提供了接口来获取状态变量的值。 本节说明如何在服务描述中描述变量事件。

为了发送或者接受事件的消息,控制点和服务必须 following 完整 UPnP 协议栈的子集。 (完整的 UPnP 协议栈在本文最开始已经说明了)。

在协议最上层,订阅和事件消息包含供应商特有的消息,例如订阅的 URL / 订阅的持续时间 / 特定的变量值。 在协议栈中向下移动,供应商内容将由 UPnP 委员会提供的信息进行补充,例如服务标识符或变量名。 来自以上各层的消息用本文档中定义的 UPnP 的协议承载。 而 UPnP 消息由通过 HTTP 或者扩展了 HTTP 头的 HTTP 协议承载。 作为参考,图片中[方括号]中的颜色表示在下面列出的订阅消息中哪个协议定义了特定的标头元素。

在本章剩下的内容中,首先会解释说明订阅相关的内容, 包括详细的订阅消息,刷新消息,取消订阅等操作。 其次,还会介绍 eventing 消息的格式,和如何发送给控制点。 最后,将会介绍 eventing 的 the UPnP Template Language。

4.1 Eventing: Subscription

当且仅当它拥有一个或一个以上的状态变量在服务描述文档中被标记为可被 event 时,一个服务拥才有 event 事件,

如果一个服务拥有 event 时间, 它将会发送该时间给感兴趣的订阅者。 Publisher 拥有一个 Subscriber 列表,而且保存这每一个 Subscriber 的如下信息:

  • unique subscription identifier Required | 在订阅的整个生命周期内此 ID 必须是唯一的,但是对长度并没有做要求。 由 Publisher 生成并在响应订阅请求时发送给 Subscriber 。 推荐使用 universally-unique identifiers ( 参考 RFC4122 ) 来保证唯一性 | Single URI
  • delivery URL for event messages Required | 由 Subscriber 在请求订阅的消息中提供 | Single URL
  • event key Required | 订阅初始的时候值为 0。 Key 必须是序列增长的,即每发送一个 event, Key 应该自增 1。 Subscriber 接收到排序好的 key 之后即可知道自己丢失了哪些 event 消息。 当 Key 增长到 4294967295 之后在增加的 event, key 应该变成 1 (32-bit 无符号整形最大值). 有些实现可能包括了 0, 遇到 0 的 key 应该将之忽略。
  • subscription duration Required | 订阅到期之前的时间或持续时间。 单个整数或关键字“ infinite”。

Publisher 应接受尽可能多的管理 / 维护 / 交付订阅。

Publisher 可能希望在电源故障时保留订阅。 虽然控制点可以从完全的网络故障中恢复,但是如果问题很简单并且已定位到设备, 则重新使用存储的订阅可能会加快恢复速度。

订户列表的更新是和订阅动作(subscription, renewal, and cancellation)相关的,

为了订阅服务的事件,订阅者发送订阅消息,该消息包含 Publisher 的 URL, Publisher 的服务标识符以及 Subscriber 接收事件消息的 URL。 订阅消息还可以包括所请求的订阅持续时间。 Publisher 的 URL 和服务标识符来自描述消息。 如 “描述” 部分中所述,描述消息包含设备描述。 设备描述包含(除其他事项外)每个服务的事件 URL(在 eventSubURL 元素中)和服务标识符(在 serviceId 元素中); 它们分别对应于 Publisher 的 URL 和服务标识符。 Publisher 的网址对于该设备中的特定服务必须是唯一的。

订阅请求发出,即代表订阅该 URL 下的所有事件,没有提供任何机制来可变地订阅事件消息。 向订户发送该服务的所有事件消息。 这是设计服务时要考虑的一个因素。

如果订阅请求被接受,则 Publisher 应该立即做出响应,响应的内容包括该订阅的 UUID 和该订阅的有效期。 有效期的选择应该考虑到控制点从该网络中移除的频率,如果控制点每隔几分钟就会从网络中离开, 那么过期时间就应该设置得比较短,以保证 Publiser 能更快的处理过期的 Subscriber; 如果控制点几乎是永久性的存在网络中,则订阅时间应该设置得非常长, 以减小因为经常的刷新订阅信息而造成网络拥塞。

订阅被接受后,Publisher 首先应该发送第一个 event, 该 event 包含当前服务的基础状态,好让 Subscriber 对当前设备有一个基本的了解。 该消息包括当前可被 event 状态变量的名字,以及搜索状态变量的值(状态变量的值和范围之前已经在 description 文档中有说明)。 即使控制点在传递之前取消订阅,该消息也始终会发送出去。 设备必须在发送初始事件消息之前确保控制点已收到对订阅请求的响应,以确保控制点已接收到 SID(订阅ID),从而可以将事件消息与订阅相关联。

为了保持订阅的在线状态,Subscriber 必须在订阅过期前 发送 renewal 消息执行 renew 操作, renewal 消息需要发送到和订阅消息相同的 URL, 但是 renewal 消息并不需要包括 Subscriber 接受 event 的 URL; 相反的 renewal 消息应该包含之前订阅的时响应的 UUID。 Publisher 返回给 renewal 消息的响应和订阅时的响应一样。

如果订阅过期了,这个订阅的 UUID 以及对应的信息都将变得无效, Publisher 应该停止向该 Subscriber 发送 event,并且可以将该 Subscriber 的信息从定户列表中清除。 如果定于试图发送除订阅消息的其他消息给服务,那么 Publisher 应该拒绝该 Subscriber 的所有请求。

当 Subscriber 不再需要来自特定服务的事件时,订户应取消其订阅。 取消订阅通常会减少服务,控制点和网络负载。 如果将订户突然从网络中删除,则可能无法发送取消消息。 作为后备,除非最终续订,否则订阅最终将自行失效。

Subscriber 应监视来自 Publisher 的发现消息。 如果 Publisher 取消其设备和服务的广播, 则订阅者应假定其订阅已被有效取消。

以下是对订阅,续订和取消消息的请求,响应和错误的特定格式的说明。

4.1.1 Eventing: Subscribing: SUBSCRIBE with NT and CALLBACK

对于设备中的每个服务,描述消息均包含事件 URL(设备描述中 service 元素的 eventSubURL 子元素)和 UPnP 服务标识符(设备描述中 service 元素中的 serviceId 子元素)。 为了订阅特定服务的事件,将订阅消息发送到该服务的事件 URL。 (请注意,事件 URL 可能是相对于 Base URL 的)。 消息包含该服务的标识符以及事件消息的传递 URL。 订阅消息还可以包括请求的订阅过期时间(时间段)。

要订阅服务事件,订阅者必须使用以下格式发送带有方法 SUBSCRIBE 以及 NT 和 CALLBACK 标头的请求。 斜体值是实际值的占位符。

SUBSCRIBER 方法的请求没有负载(body)部分,但是在头部后了空行一定不能少。(结尾是两个 \r\n)

下面的列表详细描述了上图中出现的请求的 request line / headers 字段. 除非特殊申明,所有的头的值都是大小写敏感的。

  • Request line
    • SUBSCRIBE 订阅和刷新订阅的 HTTP Method.
    • publisher path 订阅事件 URL 中的资源路径,(该 URL 在设备描述文档中 eventSubURL 子元素中提供 ) | Single, relative URL.
    • HTTP/1.1 HTTP version
  • Headers
    • HOST Required | 订阅事件的域名或者 IP 地址,端口信息是可选标注的 ( 该 URL 在 device 描述文档中的 eventSubURL 元素有提及)。 如果端口号未指定则默认为是 80 端口。
    • CALLBACK Required | 接受订阅事件的服务器 location ( URL ), 如果 CALLBACK 中包含了多个 URL,那么在发送事件的时候,Publisher 会按照顺序一个一个尝试,直到有一个成功。 每个 URL 用 \'<\' 和 ‘>’ 包围。 每一个 URL 应该指定的是 HTTP/TCP 的服务器(URL 前缀为 \'http://\')。 设备不得以任何方式截断此 URL; 如果没有足够的内存来存储整个 CALLBACK URL,则设备必须拒绝订阅。
    • NT Required | Notification 类型, 值必须是 upnp:event。
    • SID (此订阅请求,无 SID 标头)
    • TIMEOUT Recommended | 此订阅消息失效的时间间隔,值微微 infinite 或者数妙。 UPnP 委员会的建议。 由 UPnP 供应商定义。 格式由关键字 “Second-” 组成,后跟(无中间空格)是整数或关键字 “infinite”。

如果有足够的资源支持新的定户,Publisher 就应该接受新定户的请求。 为了接受新的请求, Publisher 应该为该请求生成一个唯一码,分配一个订阅的有效时间。 并发送初始事件消息(本节稍后将详细说明)。 要接受订阅请求,发布者必须在 30 秒内(包括传输时间)以以下格式发送响应。 斜体值是实际值的占位符。

SUBSCRIBER 方法的请求没有负载(body)部分,但是在头部后了空行一定不能少。(结尾是两个 \r\n)

如果设备发送的响应基于 HTTP/1.0 但是有 KeepAlive 头, 或者是基于 HTTP/1.1 但是没有 Connection: CLose 头, 那么设备必须保证在发送初始化事件之前先发送 TCP FIN Flag。 在其他的情况下(除非响应使用了 HTTP Chunk 功能),必须在发送初始事件之前指定 Content-Length(并将其设置为0)。

下面的列表详细描述了上图中出现的请求的 request line / headers 字段. 除非特殊申明,所有的头的值都是大小写敏感的。

  • Headers
    • DATE Recommended | 该时间为响应生成时的时间,服从 RFC 2616 定义的 “rfc1123-date” 格式。
    • SERVER Required | 此处并列如下参数,操作系统名称和操作系统版本,此两个值用 / 充当分割符, UPnP的版本,以及产品的名称和产品的版本。其中操作系统信息,UPnP 信息,和产品信息之间用空格充当分割符。| String | 必须准确反映设备支持的 UPnP 设备体系结构的版本号。 | 控制点必须准备好接受比控制点本身实现的更高的次要版本号(但主版本号相同)。 例如,实现 UDA 版本 1.0 的控制点应当能够能够与实现 UDA 版本 1.1 的设备进行互操作。
    • SID Required | 为该订阅生成的 UUID. 必须是全局唯一的, 而且必须以 uuid: 作为前缀。 由 UPnP 供应商定义。Single URI.
    • TIMEOUT Recommended | 此订阅消息失效的时间间隔,值微微 infinite 或者数妙。 UPnP 委员会的建议。 由 UPnP 供应商定义,该值应该大于或者等于 1800 秒。 格式由关键字 “Second-” 组成,后跟(无中间空格)是整数或关键字 “infinite”。

如果 Publisher 不能接受订阅,或者在订阅请求消息中检查到了错误,Publisher 必须发送一个响应同志定户,错误信息如下。 响应消息必须在 30s 内发送出去(该时间包含消息传输时间)。

  • Errors
    • Incompatible headers 400 Bad Request | 如果存在 SID 标头以及 NT 或 CALLBACK 标头之一, 则发布者必须发送 400 Bad Request 错误消息。
    • Missing or invalid CALLBACK 412 Precondition Failed | 如果 CALLBACK 标头丢失或不包含有效的 HTTP URL,则发布者必须以 HTTP 错误 412 Precondition Failed 进行响应。
    • Invalid NT 412 Precondition Failed | 如果 NT 标头不等于 upnp:event , 则发布者必须响应 412 Precondition Failed 错误消息。
    • Unable to accept subscription 5xx | 如果发布者无法接受订阅(例如由于资源不足),则它必须以 HTTP 500 系列错误代码响应。

UPnP 协议底层协议栈中的层可能会返回其他错误。 有关详细信息,请查阅这些协议的文档。

4.1.2 Eventing: Renewing a subscription: SUBSCRIBE with SID

为了刷新订阅的状态,Subscriber 应当发送 renew 消息到订阅事件的 URL。 与初始订阅消息不同,续订消息既不包含服务的标识符也不包含事件消息的传递 URL。 而是,该消息包含由发布者分配的订阅标识符,从而提供对要更新的订阅的明确引用。 像订阅消息一样,续订消息也可以包括请求的订阅持续时间。

续订消息的请求方法和订阅消息的请求方法一致,只是两个消息包含的头集合不一样。 续订消息使用 SID,而订阅消息使用 NT 和 CALLBACK。 一个既包含 SID,又包含 NT 和 CALLBACK 的消息是错误的。

为了续订服务的事件, Subscriber 应该发送以下格式请求到订阅服务的 URL, 斜体值是实际值的占位符。

SUBSCRIBER 方法的请求没有负载(body)部分,但是在头部后了空行一定不能少。(结尾是两个 \r\n)

下面的列表详细描述了上图中出现的请求的 request line / headers 字段. 除非特殊申明,所有的头的值都是大小写敏感的。

  • Request line
    • SUBSCRIBE 订阅和刷新订阅的 HTTP Method.
    • publisher path 订阅事件 URL 中的资源路径,(该 URL 在设备描述文档中 eventSubURL 子元素中提供 ) | Single, relative URL.
    • HTTP/1.1 HTTP version
  • Headers
    • HOST Required | 订阅事件的域名或者 IP 地址,端口信息是可选标注的 ( 该 URL 在 device 描述文档中的 eventSubURL 元素有提及)。 如果端口号未指定则默认为是 80 端口。
    • CALLBACK (续订消息中无此头)
    • NT (续订消息中无此头)
    • SID Required | 由 Publisher 之前分配个定户的 SID, 必须是全局唯一的,而且值必须以 uuid: 开头,由 UPnP 供应商定义 | Single URI
    • TIMEOUT Recommended | 此订阅消息失效的时间间隔,值微微 infinite 或者数妙。 UPnP 委员会的建议。 由 UPnP 供应商定义。 格式由关键字 “Second-” 组成,后跟(无中间空格)是整数或关键字 “infinite”。

要接受续订,发布者会重新分配订阅期限,并且必须以与新订阅请求相同的格式和条件发送响应,但不需要再次发送初始事件消息。

如果发布者不能接受续订,或者续订请求有错误,则发布者必须发送带有以下错误之一的响应。 响应必须在 30s 内发送(包括预期的传输时间)。

  • Errors
    • Incompatible headers 400 Bad Request | 如果存在 SID 标头以及 NT 或 CALLBACK 标头之一, 则发布者必须发送 400 Bad Request 错误消息。
    • Invalid SID 412 Precondition Failed | 如果 SID 其值未可知,或者该订阅已过期,则 Publisher 必须响应 412 Precondition Failed 错误信息。
    • Missing SID 412 Precondition Failed | 如果 SID 缺失,或者为空,则 Publisher 必须响应 412 Precondition Failed 错误信息。
    • Unable to accept renewal 5xx | 如果发布者无法接受续订(例如由于资源不足),则它必须以 HTTP 500 系列错误代码响应。

UPnP 协议底层协议栈中的层可能会返回其他错误。 有关详细信息,请查阅这些协议的文档。

4.1.3 Eventing: Canceling a subscription: UNSUBSCRIBE

当不再需要订阅服务的事件时,应将取消订阅的消息发送到该服务的事件 URL(请注意,事件 URL 可能是基于 Base URL 的), 该消息中包含由 Publisher 分配的 SID, 取消订阅通常会减少服务,控制点和网络负载。 如果将控制点突然从网络中删除,则可能无法发送取消订阅消息。 作为后备,除非最终续订,否则订阅最终将自行失效。

若要取消对服务事件的订阅,订阅者应使用以下格式的 UNSUBSCRIBE 方法发送请求。 斜体值是实际值的占位符。

UNSUBSCRIBE 方法的请求没有负载(body)部分,但是在头部后了空行一定不能少。(结尾是两个 \r\n)

下面的列表详细描述了上图中出现的请求的 request line / headers 字段. 除非特殊申明,所有的头的值都是大小写敏感的。

  • Request line
    • UNSUBSCRIBE 取消订阅的 HTTP Method.
    • publisher path 订阅事件 URL 中的资源路径,(该 URL 在设备描述文档中 eventSubURL 子元素中提供 ) | Single, relative URL.
    • HTTP/1.1 HTTP version
  • Headers
    • HOST Required | 订阅事件的域名或者 IP 地址,端口信息是可选标注的 ( 该 URL 在 device 描述文档中的 eventSubURL 元素有提及)。 如果端口号未指定则默认为是 80 端口。
    • CALLBACK ( UNSUBSCRIBE 方法没有 CALLBACK 头 )
    • NT ( UNSUBSCRIBE 方法没有 NT 头 )
    • SID Required | 由 Publisher 之前分配个定户的 SID, 必须是全局唯一的,而且值必须以 uuid: 开头,由 UPnP 供应商定义 | Single URI
    • TIMEOUT ( UNSUBSCRIBE 方法没有 TIMEOUT 头 )

为了取消订阅,Publisher 必须在 30s 内发送如下的响应消息(改 30s 包括传输时间),

                HTTP/1.1 200 OK

如果在取消订阅的时候发生了错误,Publisher 必须发送如下的错误信息给定户。 该消息必须在 30s 内发送如下的响应消息(改 30s 包括传输时间),

  • Errors
    • Incompatible headers 400 Bad Request | 如果存在 SID 标头以及 NT 或 CALLBACK 标头之一, 则发布者必须发送 400 Bad Request 错误消息。
    • Invalid SID 412 Precondition Failed | 如果 SID 其值未可知,或者该订阅已过期,则 Publisher 必须响应 412 Precondition Failed 错误信息。
    • Missing SID 412 Precondition Failed | 如果 SID 缺失,或者为空,则 Publisher 必须响应 412 Precondition Failed 错误信息。

UPnP 协议底层协议栈中的层可能会返回其他错误。 有关详细信息,请查阅这些协议的文档。

4.2 Eventing: Event messages

服务通过发送事件消息来发布对其状态变量的更改。 这些消息包含一个或多个状态变量的名称以及这些变量的当前值。 事件消息应尽快发送给订户,以使定户能及时的获取有关该服务的精准信息,并使订户能及时的响应到用户界面。 如果同时更改多个变量的值,则发布者应将这些更改捆绑到单个事件消息中,以减少处理和网络流量。

如上所述,初始事件消息是在订户首次订阅时发送的; 该事件消息包含所有事件变量的名称和值,并允许订户初始化其服务状态模型。 发布者接受订阅后,应尽快发送此消息。 即使控制点在传递消息之前取消订阅,也应始终发送此消息。

事件消息用 event key 标记。 发布者必须为每个订阅维护一个单独的事件密钥,以便于错误检测(如下所述)。 当发布者发送初始事件消息时,订阅的 event key 被初始化为 0。 对于每个后续事件消息,发布者都会递增订阅的 event key,并将更新后的密钥包括在事件消息中。 事件密钥的任何实现都应处理溢出,并将事件密钥从 4294967295 包装回 1(而不是0)。 当下一个事件键不是上一个键的增量时,订阅服务器还必须处理这种特殊情况。 应实现为4字节(32位)无符号整数。

如果定户没有对任何事件消息做出响应,则发布者应继续尝试向订阅者发送后续事件消息,直到订阅到期。

为了修复事件订阅,例如,如果订户错过了一个或多个事件消息, 则订户必须退订并重新订阅。 这样,订户将获得新的订阅标识符,新的初始事件消息和新的事件密钥。

所有 UPnP 的事件消息应该使用 UTF-8 编码。

4.2.1 Eventing: Event messages: NOTIFY

为了发送事件消息,Publisher 必须发送一个如下格式的请求,该请求的请求方法为 NOTIFY。 斜体值是实际值的占位符。

下面列出的是上图中出现的 response line / Headers 和 Body 的详细信息。 所有头( header )的值和元素( element )的名称均区分大小写; 除非特殊申明,所有值都不区分大小写; 除非特殊申明,所有的元素的顺序都是无关紧要的。 除非另有说明,否则必需元素必须仅出现一次(没有重复),推荐或可选元素当且仅当出现一次。 特别是,单个 propertyset 元素不得包含多个指定同一 variableName 元素的 property 元素; 每一个同名的 variableName 状态必须使用单独的事件通知消息。

  • Request line
    • NOTIFY 用于 Notify event 的 HTTP 请求方法。
    • delivery path Notify 消息服务的 URL 的资源路径(该值存在与定户发送订阅消息的 CALLBACK 头中)。 必须是 CALLBACK 标头中包含的 URL 之一,不能截断或修改。| Single, relative URL
    • HTTP/1.1 HTTP version.
  • Headers
    • HOST Required | 接受订阅事件的域名或者 IP 地址,端口信息是可选标注的 ( 该 URL 在 Subscriber 发送的订阅请求的 CALLBACK 头有提及)。 如果端口号未指定则默认为是 80 端口。
    • ACCEPT-LANGUAGE (事件消息无此头)
    • CONTENT-LENGTH Required | Body 的大小,单位字节 | Integer
    • CONTENT-TYPE Required | 必须为 text/xml, 指明 Body 采用的字符编码,这里必须为 “uft-8”。
    • NT Required | Notification 类型, 值必须是 upnp:event。
    • NTS Required | 事件推送的子类型 | 值必须为 upnp:propchange
    • SID Required | 由 Publisher 之前分配个定户的 SID, 必须是全局唯一的,而且值必须以 uuid: 开头,由 UPnP 供应商定义 | Single URI URI.
    • SEQ Required | Event key | initial event 的值必须为 0, 没向定户发送一次事件消息,该定户的该值就相应的增加 1, 为防止溢出,当值为 4294967295 再发送的下一个事件的改值将变为 1。 该值为 32 位无符号的十进制,不带前导零(某些实现可能包含前导零,接收者应将其忽略)。
  • Body
    • properyset Required | xmlns 属性必须为 urn:schemas-upnp-org:event-1-0 包含如下子元素:
      • property Required | 每一个状态变量名在每一个时间中只重复一次,而且必须属于 propertyset 命名空间前缀的响应。 包含如下元素:
        • variableName Required | 有变动的状态变量的名称(服务描述中 stateVariable 元素的 name 子元素)。 不得使用任何命名空间限定。 值是此状态变量的新值。 区分大小写。 UPnP 服务描述所指定的单一数据类型。

为了将来的可扩展性,当像上面的清单那样处理 XML 时,设备和控制点必须忽略: (a)任何未知元素及其子元素或内容,以及(b)任何未知属性及其值。 请注意,当使用比控制点支持的版本更高的服务订阅事件时,该服务可能会将原本定户不能识别的状态变量发送给控制点。 此时,控制点应丢弃并忽略事件通知消息中的此类无法识别的状态变量。

所有的控制点和设备应该忽略 XML 的注释或者他们看不懂的 XML 的流程说明。

请注意,由于 HTTP 1.1 允许使用分块编码, 因此,如果 SUBSCRIBE 请求指定 HTTP 1.1, 则某些设备可能会使用分块编码来发送操作响应。 因此,建议在 SUBSCRIBE 请求中包括 HTTP 1.1 的所有实现都支持接收分块编码。

为了确认收到此事件消息,订户必须在 30s 内做出响应,包括预期的传输时间。 如果订阅者在 30s 内没有响应,或者发布者无法连接到订阅 URL, 则发布者应放弃向订阅者发送此消息,但应保持订阅处于活动状态,并向订阅者发送将来的事件消息, 直到订阅过期或被取消。 订户必须以以下格式发送响应。

            HTTP/1.1 200 OK

NOTIFY 方法的请求没有负载(body)部分,但是在头部后了空行一定不能少。(结尾是两个 \r\n)

如果控制点使用 HTTP/1.0 承载 SOAP 协议,但是没有携带 KeepAlive 头, 设备必须在响应结束之后关闭通信 socket。 如果控制点使用 HTTP/1.1 承载 SOAP 协议,而且设置 Connection: CLOSE, 那么设备必须在响应之后关闭通信 socket。

如果事件消息有错误,则订户必须以以下错误之一进行响应。 响应必须在 30 秒内发送,包括预期的传输时间。

  • Errors
    • MISS SID 412 Precondition Failed | 如果 SID 缺失,或者为空,则 Publisher 必须响应 412 Precondition Failed 错误信息。
    • Invalid SID 412 Precondition Failed | 如果 SID 其值未可知,或者该订阅已过期,则 Publisher 必须响应 412 Precondition Failed 错误信息。 (收到此错误响应时必须终止此 SID。)
    • Missing NT or NTS header 400 Bad Request | 如果 NT 或者 NTS 头缺失,则发布者必须响应 400 Bad Request 错误消息。
    • Invalid NT header 412 Precondition Failed | 如果 NT 标头不等于 upnp:event , 则发布者必须响应 412 Precondition Failed 错误消息。
    • Invalid NTS header 412 Precondition Failed | 如果 NTS 头不等于 upnp:propchange ,则发布者必须响应 412 Precondition Failed 错误消息。

UPnP 协议底层协议栈中的层可能会返回其他错误。 有关详细信息,请查阅这些协议的文档。

4.3 Eventing: UPnP Template Language for eventing

UPnP 模板语言为设备和服务定义了格式正确的模板。 在较小程度上,它还提供了事件消息主体的模板。 “描述” 部分说明了与设备和服务有关的 UPnP 模板语言。 如该部分所述,UPnP 模板语言是用 XML 语法编写的,并且是从XML架构派生的(第1部分:结构,第2部分:数据类型)。 以下是与事件有关的该语言列表。它定义的元素用于事件消息中。 它们在这里是绿色,在上面的清单中是绿色。 以下是定义这些元素的位置(尽管这是最低限度的定义); 以及使用它们的地方。

紧随其后的是对使用的 XML Schema 元素,属性和值的简要说明。 本节末尾的 XML Schema 参考文献有更多详细信息。

UPnP Template Language for eventing

  • element 引用元素以声明嵌套。 maxOccurs 属性定义元素必须出现的最大次数。 默认值为 maxOccurs = 1 ; 可以出现一次或多次的元素具有 maxOccurs = *
  • ElementType 用新的派生语言定义元素。 name 属性定义元素名称。 model 属性定义元素是否包含子元素。 如果有元素中的子元素包含的内容不确定的时候 model 等于 open, 如果只有元素中的子元素才拥有内容的时候 model 等于 eltOnly, name 属性定义元素名称。 model 性指示新的派生语言中的元素是否可以包含此处未明确指定的元素, 仅在未指定的子元素可能会包括在内的时候,model = open。 content 属性指示可能包含的内容, 仅包含其他元素的元素具有 content = eltOnly。

如 “描述” 部分中所述,服务的 UPnP 模板语言还为状态变量指定 sendEvents 属性。 此属性的默认值为 yes。 为了表示状态变量可 event,在服务描述中此属性的值为 yes(或省略该属性)。 如果为 non-event, 则其值为 no。 请注意,如果所有服务的状态变量均为 non-event,则该服务没有任何要发布的内容,并且控制点无法订阅,也不会从该服务接收事件消息。

4.4 Eventing: Augmenting the UPnP Template Language

在 UPnP 模板语言中,对设备和服务做 annotations 是很有用的。 在较小程度上,这些批注中的值可以平滑和温和的捕获事件。

如上所述,某些状态变量可能更改值的速度太快,以至于无法使用。 以下是 UPnP 论坛工作委员会或 UPnP 供应商推荐的词汇表,用于记录由于变量值更改而导致发送的事件消息数量的度。

  • maximumRate = n Optional | 在发送事件之后,状态变量的值产生了变化,但是你设置了该值, 那么即便在 ns 秒内产生了事件。下次发送事件也会在 ns 以后。 但是如果 v 在 ns 内停止了连续的更新,那么此时应该及时的吧该变量的最新状态发送出去。 | 建议给频繁,持续变化的状态变量设置该值(比如说播放进度) | 整数。
  • minimumDelta = n Optional | 在发送事件之后,状态变量的值产生了变化,但是你设置了该值, 那么即便值的变化在 +-n 范围内,此时并不会产生事件, 但是如果 v 此时值超出了 allowedValueRange 的范围,那么此时应该及时的吧该变量的最新状态发送出去。 | 建议需要进行滤波,或者计数类型的状态变量设置该值 | 整数。

事件发生后,发布者可以发送任何已更改的调节变量。 发布者也应尽最大努力满足上述审核规则, 但发布者在发出事件时可以过滤掉最近的更改。

请注意,查询状态变量,仅仅会影响事件,而不会导致状态变量的更新。 具体来说,返回状态变量值的控制动作可能会返回比通过事件发布的状态值更接近于真是值。 换句话说,调节导致的状态表更改并非意味都会导致事件的发生。

关于那个状态变量是否可被 event 或者调节,这由 UPnP 委员会或者 UPnP 供应商决定(非标服务)。

4.5 Eventing references
  • RFC 2396 Uniform Resource Identifiers: Generic Syntax 传送门
  • RFC 2616 HTTP: Hypertext Transfer Protocol 1.1. 传送门
  • XML Extensible Markup Language. 传送门
  • XML Schema (Part 1: Structures, Part 2: Datatypes) Grammar defining UPnP Template Language. 传送门 / 传送门
  • A Universally Unique IDentifier (UUID) URN Namespace 传送门
5. Presentation

演示是 UPnP™ 网络中的第 5 步,是对 3 3步和第 4 步的补充。

在控制点(1)发现设备并(2)检索到该设备的描述之后,控制点就可以打开演示功能。 如果设备具有用于演示的 URL,则控制点可以从该 URL 检索页面,将页面加载到浏览器中,并根据页面的功能,允许用户控制设备和/或查看设备状态。 这些功能可以实现的程度取决于演示页面和设备的特定功能。

用于演示的 URL 包含在设备描述中的 presentationURL 元素内。 设备描述是通过描述消息传递的。 “描述” 部分详细说明了设备描述和描述消息。

为了发送或者接受事件的消息,控制点和服务必须 following 完整 UPnP 协议栈的子集。 (完整的 UPnP 协议栈在本文最开始已经说明了)。

协议最上层,演示页面由 UPnP 供应商指定。 在协议第二层,UDA 指定该页面用 HTML 编写。 该页面通过基于 TCP/IP 上的 HTTP 传递。 作为参考,为了与本文档中的其他部分保持一致,在[方括号]中添加��颜色��

为了检索演示页面,控制点向演示 URL 发出 HTTP GET 请求,然后设备返回一个演示页面。

与 UPnP 设备和服务模板以及标准设备和服务类型不同,演示页面的功能完全由 UPnP 供应商指定。 演示页面不在 UPnP 委员会的主持下。 该页面必须是 HTML 页面; 它应该是 HTML 3.0 或更高版本。 但是,其他设计方面留给供应商指定。 这包括但不限于控制点浏览器的所有功能,使用的脚本语言或浏览器插件以及与设备进行交互的方式。 为了实现演示页面,UPnP 供应商可能希望利用 UPnP 机制进行控制和/或事件化,以利用设备的现有功能,但并不限于此。

演示页面应使用 HTML 提供的本地化机制(例如,具有 charset 属性的 META 标签)。 控制点应使用 HTTP 的 ACCEPT-LANGUAGE 和 CONTENT-LANGUAGE 功能来尝试检索本地化的演示文稿页面。 具体地说,控制点可以在对演示页面的请求中包括 HTTP ACCEPT-LANGUAGE 标头。 如果请求中存在 ACCEPT-LANGUAGE 标头,则响应必须包含 CONTENT-LANGUAGE 标头以标识页面的语言。

5.1 Presentation references
UPnP device protection

DeviceProtection:1 服务旨在提供一个大致等价于 UPnP Security 1.0 的功能. 引入这项新服务的目的是解决阻碍行业中较早设计部署的各种问题。 下文将解释为什么。

DeviceProtection 访问控制模型基于访问控制列表(ACL), 该访问控制列表将特定设备和特定服务的角色分配给控制点和用户。 每个设备都维护自己的 ACL,设备可以支持在多个设备间自动同步共享 ACL, 也可不支持。 每一个角色对应于执行一组特定 SOAP 操作的权限。 执行一个 Action 所需的角色可以取决于传递给该动作的参数值。 UPnP 服务规范推荐了 Action 和角色对应关系的定义,但是设备可以忽略这些建议。 因此,DeviceProtection 还提供了查询设备以发现执行特定操作所需的角色的功能。 DeviceProtection 使用自己的服务来防止未经授权的 ACL 修改。

名词解释
  • SW-CP Security-aware Control Points, 支持 UPnP 安全协议的控制点(非官方简称)
动机

UDA 1.0 在 2003 年 11 月发布,但自从那时开始,产品的开发和发布都面临极大的限制。 一个极大的影响因素如下:

  1. 设置过程的用户体验过于复杂和繁琐。(有人认为)
  2. 某些高级 UPnP 安全功能取决于诸如 SPKI 之类的安全标准,而这些标准在业界并未得到广泛支持。
  3. UPnP Security 1.0 基于 “3box” 模型,但该模型要求存在带有丰富用户界面的安全控制台。 这是产品开发部署的一个很大的障碍。
  4. UPnP 论坛内部对于 UPnP Security 部署缺乏共识,这增加了风险并降低了产品实施的收益。

此外,在 1.0 发布以后,下列功能在新的产品中也发展成熟了起来。

  1. Wi-Fi 网络已在家庭中普及,并且已经建立了安全设置 Wi-Fi 的新标准。 这项称为 Wi-Fi 保护设置[WPS]的新标准基于比 UPnP Security 1.0 更简单的用户体验。 WPS 的迅速采用表明制造商相信 WPS 的用户体验可以为广大市场所接受。从 2020 年的中国普通用户来看 wps 功能看起来很鸡肋,虽然各大厂商都支持,但是普通用户几乎不会使用到该功能,该功能看起来似乎像研发经费太多开发出来的
  2. DLNA 已经确定了几种需要安全性的新方案,这些新方案增加了为 UPnP 开发可部署的安全性框架的紧迫性。
  3. 恶意网站通过家用 PC 上的浏览器扩展发起的主动攻击,针对家用设备的新威胁已经出现。
  4. 基于 VPN 技术的用于 UPnP 网络远程访问的 UPnP 服务正在标准化。 此技术提供了另一种方法来支持对 UPnP 设备的安全远程访问。
该协议所提供的能力
  • SWCP 可以根据 SSDP 的扩展头提取到设备服务的安全描述信息。
  • Initial Introduction – 提供最基本的信息,安全的将自己介绍给目标设备(或者 CP),以建立其最基本权限的访问通道。
  • 设备与控制点安全通信链路建立(设备和 CP 的信任关系建立)(设备和 CP 认证)(Device and Control Point Authentication) – 设备授权给 CP 之前先需要完成 TLS 的双向证书(x.509)认证,从而建立安全的通讯链路。 其中用于身份验证过程中的证书信任链并不需要由权威的证书颁发机构颁发。 相反的,更推荐 Device 和 CP 去生成自己的证书(可以使用自签名证书,这样可以更好的管控风险), DeviceProtection 安全模型信任关系的建立取决于本地点对点的配置,而并非基于初始预置的根证书取得信任关系。 设备的实现者可能会需要设计一个更加复杂的模型去将用户引入信任构建流程中。 但该方法的好处也是显而易见的,它并不需要提供一个机制去保证所有的设备(或者控制设备)签发的证书都基于同一个受信的 CA。 因此,基于受信任的CA根和更长的证书链(这将需要单独的TLS握手)的访问控制策略不在DeviceProtection的范围内。
  • 用户鉴权(User Authentication) – 在已经建立了 TLS 通讯通道的基础上,用户可以建立属于他自己的 username/password 鉴权机制。
  • SOAP 服务和 Presentation Pages 的私密性和完整性 ( Privacy and integrity protection for SOAP services and Presentation Pages ) – 私密性和完整性由 基于标准的 TLS 的 HTTPS 保证。 (目前推荐至少应该上 TLS 1.2 而且在鉴权阶段应该协商好 Token, 在此处的通讯应该将该 Token 带上)
  • 访问控制的策略与审计(Examination and manipulation of access control policy) - – 经过身份验证和授权的控制点可以读取和配置设备针对控制点和/或用户身份的访问控制策略。
  • 设备支持的角色的枚举( Enumeration of Roles supported by Devices ) – 角色是与一组访问权限关联的名称。 当一个角色或一组角色分配给控制点身份或用户身份时,该身份将被授予与角色相关联的访问权限。( 这里可以参考 AWS 的 IAM 服务机制 )
该协议不提供的能力

DeviceProtection service 明确不包括的目标有:

  • UPnP 设备的平台安全(Platform security for UPnP devices) – DeviceProtection 不能解决内部存在信任完整性问题设备的安全问题。
  • 数字权限管理(Digital rights management) – DeviceProtection 不能解决关于数字媒体或者数字内容的拷贝的问题。
  • 代码级别的安全问题(Code base trust) – 和平台安全性类似,DeviceProtection 不能保证 downloading, hosting, verifying the integrity 实现代码的安全性问题。
  • Vendor 自定义安全机制(Application-specific security needs)- DeviceProtection 提供了一组机制,但是每个设备或应用程序负责决定如何应用这些机制来解决特定于域的问题。 例如,其他 UPnP DCP 可以定义其自己的使用 DeviceProtection 的特定要求。 对于 Vendor 自定义部分产生的安全问题,DeviceProtection 无法保证安全性。
  • 安全策略执行的策略/验证过程中产生的安全问题
注意
  • 《UPnP gw DeviceProtection ..》提供了一个 UUID 的计算方法,请参考该文档的 2.6.8.2 章节,但也不需要完全遵守该方法
  • DeviceProtection 定义了三个角色,拥有三种等级的权限,可以参考一下:Public, Basic, Admin
  • 为兼容传统设备 DeviceProtection 也允许暴露一些基础的功能到环境中,但这些功能必须通过 HTTPS 进行通信,而且在双方都支持的前提下,应该选用最接近最新版本的 TLS 来进行消息通讯。
  • DeviceProtection 并不能保证 SSDP 阶段的攻击,因此黑客有可能监听此阶段的信息并加以利用,但是 DeviceProtection 能保证 SSDP 的后续操作安全性。
  • DeviceProtection 所增加的 SSDP 扩展头为: SECURELOCATION.UPNP.ORG, 而且 LOCATION 字段也应该存在。 两个 URI 应该对应不同的端口,但应该指到同一个 XML 描述文档。
    如果根设备不包含 DeviceProtection 那么跟设备的 SSDP 中不需要包含 SECURELOCATION.UPNP.ORG 字段,但是如果该设备的嵌入设备有任何一个包含 DeviceProtection 服务,那么所有的设备 SSDP 都必须携带 SECURELOCATION.UPNP.ORG 头
    SECURELOCATION.UPNP.ORG 头指定的 URI 协议必须是 https
    安全铭感的 CP, 所有的请求都必须通过 SECURELOCATION.UPNP.ORG 指定的服务来访问
  • 针对同一请求 Device 可能会更具访问用户的权限不同而返回不同的响应,而权限的指派是由最初拥有该设备的 CP 来指定分配的。
  • 如果一个根设备或者嵌入设备中包含 DeviceProtection,那么该设备下的所有服务的 ACL 都由 DeviceProtection 来维护。但如果设备中位包含 DeviceProtection, 那么各个服务的 ACL 都存在与各个服务当中。
DLNA 实战前热身(DLNA 抓包)
说明

DLNA 的基础就是 UPnP, 目前 UPnP 已经更新到 2.0 了, DLNA 也已经更新到 2016 了,但是相对的 DLNA 组织也解散了。 我觉得这也是最好的使命吧,这种标准由这么多个商业利益不同的组织一起维护本来就是一件很容易出问题的事情。

虽然我还没看 2.0 部分的 spec, 但是就 1.0 来说,一个是文章太罗嗦,很简单的东西讲这么久。 第二个路由器厂商对组播其实支持不是特别好,远远没有 TCP/IP 支持得那样好,这样造成用户体验很差,而且作为控制端, 服务端的开发者我们对此毫无办法。 第三个是虽然协议是在局域网传输信息,但是一方面这也就限制在了局域网,第二方面,在协议层面几乎没有考虑安全这方面的需求, 通信协议几乎是在网络中裸奔,这个放在消费者安全意识日益增强的现在,算是一个非常严重的劣势。 最后一个我觉得用 XML 一个是解析速度方面是个问题,另外一个方面单位 Byte 承载的信息量太小了。 虽然局域网通信更加有保障,也不太需要注意这种 size 的封包消耗,但如果将这个服务放在智能家居,小平台上,这个协议就几乎无勇武之地,如果用 ProtoBuf 会不会更好? 另外对于接受端和发送短要维护版本兼容,在通信协议里面其实可以加入更多东西,这样,升级也更方便。

这个协议本身就是为了打通家里的设备而实现的(使家庭设备能互相发现,互相调用,互相分享信息),而现在越来越多的家电也开始连入了,家庭的网络。 而从这方面来看,不管是 UPnP 也好,DLNA 也好,完全失败了,几乎被 MQTT 摁在地上来回摩擦。 但 MQTT 其实刚好缺少设备发现,设备宣告,设备描述那方面的功能,而 UPnP 没有跟上时代,这不能说不是一个遗憾。

我个人觉得统一标准这中事情最好是由开源组织,或者无组织团队,个人来维护,可能协议有很多个版本,也有很多个分支, 但是大家总会看见好的分支版本被引用,被传诵。只有协议本身不断的去旧添新,不断的适应环境,才能够留下更加适应当前的环境。

接下来,先结合 UPnP 的后 5 个步骤,我从乐播官网下载了乐播的服务端 APK,然后用网易云 DLNA 播放音乐,抓出通信过程中产生的数据包。 把数据包记录到本章。

Discovery
  • DMC 发送的 M-Search 请求
        Internet Protocol Version 6, Src: fe80::e9c7:bf5e:e75c:18cb, Dst: ff02::c
        User Datagram Protocol, Src Port: 63040, Dst Port: 1900
    
        M-SEARCH * HTTP/1.1
        Host: [FF02::C]:1900
        ST: urn:Microsoft Windows Peer Name Resolution Protocol: V4:IPV6:LinkLocal
        Man: "ssdp:discover"
        MX: 3
    
  • Device 发送的组播信息: rootdevice
        NOTIFY * HTTP/1.1
        HOST: 239.255.255.250:1900
        CACHE-CONTROL: max-age=66
        LOCATION: http://192.168.3.65:49152/description.xml
        NT: upnp:rootdevice
        NTS: ssdp:alive
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        USN: uuid:F7CA5454-3F48-4390-8009-5892d163053a::upnp:rootdevice
    
  • Device 发送的组播信息: 子设备
        NOTIFY * HTTP/1.1
        HOST: 239.255.255.250:1900
        CACHE-CONTROL: max-age=66
        LOCATION: http://192.168.3.65:49152/description.xml
        NT: uuid:F7CA5454-3F48-4390-8009-5892d163053a
        NTS: ssdp:alive
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        USN: uuid:F7CA5454-3F48-4390-8009-5892d163053a
    
  • Device 发送的组播信息: MediaRenderer 信息
        NOTIFY * HTTP/1.1
        HOST: 239.255.255.250:1900
        CACHE-CONTROL: max-age=66
        LOCATION: http://192.168.3.65:49152/description.xml
        NT: urn:schemas-upnp-org:device:MediaRenderer:1
        NTS: ssdp:alive
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        USN: uuid:F7CA5454-3F48-4390-8009-5892d163053a::urn:schemas-upnp-org:device:MediaRenderer:1
    
  • Device 发送的组播信息: AVTransport service
        NOTIFY * HTTP/1.1
        HOST: 239.255.255.250:1900
        CACHE-CONTROL: max-age=66
        LOCATION: http://192.168.3.65:49152/description.xml
        NT: urn:schemas-upnp-org:service:AVTransport:1
        NTS: ssdp:alive
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        USN: uuid:F7CA5454-3F48-4390-8009-5892d163053a::urn:schemas-upnp-org:service:AVTransport:1
    
  • Device 发送的组播信息: ConnectionManager service
        NOTIFY * HTTP/1.1
        HOST: 239.255.255.250:1900
        CACHE-CONTROL: max-age=66
        LOCATION: http://192.168.3.65:49152/description.xml
        NT: urn:schemas-upnp-org:service:ConnectionManager:1
        NTS: ssdp:alive
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        USN: uuid:F7CA5454-3F48-4390-8009-5892d163053a::urn:schemas-upnp-org:service:ConnectionManager:1
    
  • Device 发送的组播信息: RenderingControl service
        NOTIFY * HTTP/1.1
        HOST: 239.255.255.250:1900
        CACHE-CONTROL: max-age=66
        LOCATION: http://192.168.3.65:49152/description.xml
        NT: urn:schemas-upnp-org:service:RenderingControl:1
        NTS: ssdp:alive
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        USN: uuid:F7CA5454-3F48-4390-8009-5892d163053a::urn:schemas-upnp-org:service:RenderingControl:1
    
Description
  • 控制点发送 GET 请求获取设备描述文件
        GET /description.xml HTTP/1.1
        Content-Length: 0
        HOST: 192.168.3.65
        User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.1; OS105 Build/NGI77B)
        Connection: Keep-Alive
        Accept-Encoding: gzip
    
    
  • 设备响应控制点设备描述文件
        HTTP/1.1 200 OK
        CONTENT-LENGTH: 2695
        CONTENT-TYPE: text/xml
        DATE: Mon, 03 Aug 2020 01:12:24 GMT
        LAST-MODIFIED: Mon, 03 Aug 2020 01:09:24 GMT
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        X-User-Agent: redsonic
        CONNECTION: close
    
        <?xml version="1.0"?>
        <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>TV(192.168.3.65ï¼</friendlyName>
                <manufacturer>LEBO</manufacturer>
                <manufacturerURL>http://www.hpplay.com.cn</manufacturerURL>
                <modelDescription>Lebo Media Render</modelDescription>
                <modelName>HappyCast</modelName>
                <modelURL>http://www.hpplay.com.cn</modelURL>
                <dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMR-1.50</dlna:X_DLNADOC>
                <UDN>uuid:F7CA5454-3F48-4390-8009-5892d163053a</UDN>
                <UID>-1482191554774566800</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>
                        <LELINKFT>{ &quot;deviceip&quot;: &quot;192.168.3.65&quot;,192.168.3.65}</LELINKFT>
                    </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://192.168.3.65: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://192.168.3.65:49152</URLBase>
        </root>
    

    可以看到上叙的设备描述文档中有一个根设备,三个服务,还有一个 X_RController_DeviceInfo。其中,一个设备和三个服务属于 DLNA 中规范的, 而 <av:X_RController_DeviceInfo xmlns:av="urn:mi-com:av"> 则是小米自己定义的一套规范,这个我在网上也没找到相关的资料。

  • 控制点发送请求获取 AVTransport 服务 SCPD
        GET /dlna/Render/AVTransport_scpd.xml HTTP/1.1
        Content-Length: 0
        HOST: 192.168.3.65
        User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.1; OS105 Build/NGI77B)
        Connection: Keep-Alive
        Accept-Encoding: gzip
    
    
  • 设备响应控制点请求,返回 AVTransport 服务 SCPD
        HTTP/1.1 200 OK
        CONTENT-LENGTH: 14868
        CONTENT-TYPE: text/xml
        DATE: Mon, 03 Aug 2020 01:12:41 GMT
        LAST-MODIFIED: Thu, 01 Jan 1970 00:00:00 GMT
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        X-User-Agent: redsonic
        CONNECTION: close
        <?xml version="1.0" encoding="utf-8"?>
    
        <scpd xmlns="urn:schemas-upnp-org:service-1-0">
        <specVersion>
            <major>1</major>
            <minor>0</minor>
        </specVersion>
        <actionList>
            <action>
            <name>GetCurrentTransportActions</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>Actions</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentTransportActions</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>GetDeviceCapabilities</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>PlayMedia</name>
                <direction>out</direction>
                <relatedStateVariable>PossiblePlaybackStorageMedia</relatedStateVariable>
                </argument>
                <argument>
                <name>RecMedia</name>
                <direction>out</direction>
                <relatedStateVariable>PossibleRecordStorageMedia</relatedStateVariable>
                </argument>
                <argument>
                <name>RecQualityModes</name>
                <direction>out</direction>
                <relatedStateVariable>PossibleRecordQualityModes</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>GetMediaInfo</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>NrTracks</name>
                <direction>out</direction>
                <relatedStateVariable>NumberOfTracks</relatedStateVariable>
                </argument>
                <argument>
                <name>MediaDuration</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentMediaDuration</relatedStateVariable>
                </argument>
                <argument>
                <name>CurrentURI</name>
                <direction>out</direction>
                <relatedStateVariable>AVTransportURI</relatedStateVariable>
                </argument>
                <argument>
                <name>CurrentURIMetaData</name>
                <direction>out</direction>
                <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
                </argument>
                <argument>
                <name>NextURI</name>
                <direction>out</direction>
                <relatedStateVariable>NextAVTransportURI</relatedStateVariable>
                </argument>
                <argument>
                <name>NextURIMetaData</name>
                <direction>out</direction>
                <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
                </argument>
                <argument>
                <name>PlayMedium</name>
                <direction>out</direction>
                <relatedStateVariable>PlaybackStorageMedium</relatedStateVariable>
                </argument>
                <argument>
                <name>RecordMedium</name>
                <direction>out</direction>
                <relatedStateVariable>RecordStorageMedium</relatedStateVariable>
                </argument>
                <argument>
                <name>WriteStatus</name>
                <direction>out</direction>
                <relatedStateVariable>RecordMediumWriteStatus</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>GetPositionInfo</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>Track</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentTrack</relatedStateVariable>
                </argument>
                <argument>
                <name>TrackDuration</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentTrackDuration</relatedStateVariable>
                </argument>
                <argument>
                <name>TrackMetaData</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentTrackMetaData</relatedStateVariable>
                </argument>
                <argument>
                <name>TrackURI</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentTrackURI</relatedStateVariable>
                </argument>
                <argument>
                <name>RelTime</name>
                <direction>out</direction>
                <relatedStateVariable>RelativeTimePosition</relatedStateVariable>
                </argument>
                <argument>
                <name>AbsTime</name>
                <direction>out</direction>
                <relatedStateVariable>AbsoluteTimePosition</relatedStateVariable>
                </argument>
                <argument>
                <name>RelCount</name>
                <direction>out</direction>
                <relatedStateVariable>RelativeCounterPosition</relatedStateVariable>
                </argument>
                <argument>
                <name>AbsCount</name>
                <direction>out</direction>
                <relatedStateVariable>AbsoluteCounterPosition</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>GetTransportInfo</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>CurrentTransportState</name>
                <direction>out</direction>
                <relatedStateVariable>TransportState</relatedStateVariable>
                </argument>
                <argument>
                <name>CurrentTransportStatus</name>
                <direction>out</direction>
                <relatedStateVariable>TransportStatus</relatedStateVariable>
                </argument>
                <argument>
                <name>CurrentSpeed</name>
                <direction>out</direction>
                <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>GetTransportSettings</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>PlayMode</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
                </argument>
                <argument>
                <name>RecQualityMode</name>
                <direction>out</direction>
                <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>Next</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>Pause</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>Play</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>Speed</name>
                <direction>in</direction>
                <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>Previous</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>Seek</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>Unit</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable>
                </argument>
                <argument>
                <name>Target</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>SetAVTransportURI</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>CurrentURI</name>
                <direction>in</direction>
                <relatedStateVariable>AVTransportURI</relatedStateVariable>
                </argument>
                <argument>
                <name>CurrentURIMetaData</name>
                <direction>in</direction>
                <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>SetPlayMode</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                <name>NewPlayMode</name>
                <direction>in</direction>
                <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
            <action>
            <name>Stop</name>
            <argumentList>
                <argument>
                <name>InstanceID</name>
                <direction>in</direction>
                <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
            </argumentList>
            </action>
        </actionList>
        <serviceStateTable>
            <stateVariable sendEvents="no">
            <name>TransportStatus</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>OK</allowedValue>
                <allowedValue>ERROR_OCCURRED</allowedValue>
                <allowedValue>vendor-defined</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>NextAVTransportURI</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>NextAVTransportURIMetaData</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentTrackMetaData</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>RelativeCounterPosition</name>
            <dataType>i4</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>A_ARG_TYPE_InstanceID</name>
            <dataType>ui4</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>A_ARG_TYPE_SeekTarget</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>PlaybackStorageMedium</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>UNKNOWN</allowedValue>
                <allowedValue>DV</allowedValue>
                <allowedValue>MINI-DV</allowedValue>
                <allowedValue>VHS</allowedValue>
                <allowedValue>W-VHS</allowedValue>
                <allowedValue>S-VHS</allowedValue>
                <allowedValue>D-VHS</allowedValue>
                <allowedValue>VHSC</allowedValue>
                <allowedValue>VIDEO8</allowedValue>
                <allowedValue>HI8</allowedValue>
                <allowedValue>CD-ROM</allowedValue>
                <allowedValue>CD-DA</allowedValue>
                <allowedValue>CD-R</allowedValue>
                <allowedValue>CD-RW</allowedValue>
                <allowedValue>VIDEO-CD</allowedValue>
                <allowedValue>SACD</allowedValue>
                <allowedValue>MD-AUDIO</allowedValue>
                <allowedValue>MD-PICTURE</allowedValue>
                <allowedValue>DVD-ROM</allowedValue>
                <allowedValue>DVD-VIDEO</allowedValue>
                <allowedValue>DVD-R</allowedValue>
                <allowedValue>DVD+RW</allowedValue>
                <allowedValue>DVD-RW</allowedValue>
                <allowedValue>DVD-RAM</allowedValue>
                <allowedValue>DVD-AUDIO</allowedValue>
                <allowedValue>DAT</allowedValue>
                <allowedValue>LD</allowedValue>
                <allowedValue>HDD</allowedValue>
                <allowedValue>MICRO-MV</allowedValue>
                <allowedValue>NETWORK</allowedValue>
                <allowedValue>NONE</allowedValue>
                <allowedValue>NOT_IMPLEMENTED</allowedValue>
                <allowedValue>vendor-defined</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>RelativeTimePosition</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>PossibleRecordStorageMedia</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentPlayMode</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>NORMAL</allowedValue>
                <allowedValue>REPEAT_ALL</allowedValue>
                <allowedValue>INTRO</allowedValue>
            </allowedValueList>
            <defaultValue>NORMAL</defaultValue>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>TransportPlaySpeed</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>1</allowedValue>
                <allowedValue>vendor-defined</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>PossiblePlaybackStorageMedia</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>AbsoluteTimePosition</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentTrack</name>
            <dataType>ui4</dataType>
            <allowedValueRange>
                <minimum>0</minimum>
                <maximum>4294967295</maximum>
                <step>1</step>
            </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentTrackURI</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentTransportActions</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>NumberOfTracks</name>
            <dataType>ui4</dataType>
            <allowedValueRange>
                <minimum>0</minimum>
                <maximum>4294967295</maximum>
            </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>AVTransportURI</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>AbsoluteCounterPosition</name>
            <dataType>i4</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentRecordQualityMode</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>0:EP</allowedValue>
                <allowedValue>1:LP</allowedValue>
                <allowedValue>2:SP</allowedValue>
                <allowedValue>0:BASIC</allowedValue>
                <allowedValue>1:MEDIUM</allowedValue>
                <allowedValue>2:HIGH</allowedValue>
                <allowedValue>NOT_IMPLEMENTED</allowedValue>
                <allowedValue>vendor-defined</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentMediaDuration</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>A_ARG_TYPE_SeekMode</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>ABS_TIME</allowedValue>
                <allowedValue>REL_TIME</allowedValue>
                <allowedValue>ABS_COUNT</allowedValue>
                <allowedValue>REL_COUNT</allowedValue>
                <allowedValue>TRACK_NR</allowedValue>
                <allowedValue>CHANNEL_FREQ</allowedValue>
                <allowedValue>TAPE-INDEX</allowedValue>
                <allowedValue>FRAME</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>AVTransportURIMetaData</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>RecordStorageMedium</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>UNKNOWN</allowedValue>
                <allowedValue>DV</allowedValue>
                <allowedValue>MINI-DV</allowedValue>
                <allowedValue>VHS</allowedValue>
                <allowedValue>W-VHS</allowedValue>
                <allowedValue>S-VHS</allowedValue>
                <allowedValue>D-VHS</allowedValue>
                <allowedValue>VHSC</allowedValue>
                <allowedValue>VIDEO8</allowedValue>
                <allowedValue>HI8</allowedValue>
                <allowedValue>CD-ROM</allowedValue>
                <allowedValue>CD-DA</allowedValue>
                <allowedValue>CD-R</allowedValue>
                <allowedValue>CD-RW</allowedValue>
                <allowedValue>VIDEO-CD</allowedValue>
                <allowedValue>SACD</allowedValue>
                <allowedValue>MD-AUDIO</allowedValue>
                <allowedValue>MD-PICTURE</allowedValue>
                <allowedValue>DVD-ROM</allowedValue>
                <allowedValue>DVD-VIDEO</allowedValue>
                <allowedValue>DVD-R</allowedValue>
                <allowedValue>DVD+RW</allowedValue>
                <allowedValue>DVD-RW</allowedValue>
                <allowedValue>DVD-RAM</allowedValue>
                <allowedValue>DVD-AUDIO</allowedValue>
                <allowedValue>DAT</allowedValue>
                <allowedValue>LD</allowedValue>
                <allowedValue>HDD</allowedValue>
                <allowedValue>MICRO-MV</allowedValue>
                <allowedValue>NETWORK</allowedValue>
                <allowedValue>NONE</allowedValue>
                <allowedValue>NOT_IMPLEMENTED</allowedValue>
                <allowedValue>vendor-defined</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>RecordMediumWriteStatus</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>WRITABLE</allowedValue>
                <allowedValue>PROTECTED</allowedValue>
                <allowedValue>NOT_WRITABLE</allowedValue>
                <allowedValue>UNKNOWN</allowedValue>
                <allowedValue>NOT_IMPLEMENTED</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="yes">
            <name>LastChange</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>CurrentTrackDuration</name>
            <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>TransportState</name>
            <dataType>string</dataType>
            <allowedValueList>
                <allowedValue>STOPPED</allowedValue>
                <allowedValue>PAUSED_PLAYBACK</allowedValue>
                <allowedValue>PAUSED_RECORDING</allowedValue>
                <allowedValue>PLAYING</allowedValue>
                <allowedValue>RECORDING</allowedValue>
                <allowedValue>TRANSITIONING</allowedValue>
                <allowedValue>NO_MEDIA_PRESENT</allowedValue>
            </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
            <name>PossibleRecordQualityModes</name>
            <dataType>string</dataType>
            </stateVariable>
        </serviceStateTable>
        </scpd>
    
  • 控制点发送请求获取 RenderingControl 服务 SCPD
        GET /dlna/Render/RenderingControl_scpd.xml HTTP/1.1
        Content-Length: 0
        HOST: 192.168.3.65
        User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.1; OS105 Build/NGI77B)
        Connection: Keep-Alive
        Accept-Encoding: gzip
    
    
  • 设备响应控制点请求,返回 RenderingControl 服务 SCPD
        HTTP/1.1 200 OK
        CONTENT-LENGTH: 17287
        CONTENT-TYPE: text/xml
        DATE: Mon, 03 Aug 2020 01:12:41 GMT
        LAST-MODIFIED: Thu, 01 Jan 1970 00:00:00 GMT
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        X-User-Agent: redsonic
        CONNECTION: close
    
        <?xml version="1.0" encoding="utf-8"?>
    
        <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>
              </argumentList>
            </action>
            <action>
              <name>GetBlueVideoGain</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentBlueVideoGain</name>
                  <direction>out</direction>
                  <relatedStateVariable>BlueVideoGain</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetBrightness</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentBrightness</name>
                  <direction>out</direction>
                  <relatedStateVariable>Brightness</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetColorTemperature</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentColorTemperature</name>
                  <direction>out</direction>
                  <relatedStateVariable>ColorTemperature</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetContrast</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentContrast</name>
                  <direction>out</direction>
                  <relatedStateVariable>Contrast</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetGreenVideoBlackLevel</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentGreenVideoBlackLevel</name>
                  <direction>out</direction>
                  <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetGreenVideoGain</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentGreenVideoGain</name>
                  <direction>out</direction>
                  <relatedStateVariable>GreenVideoGain</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetHorizontalKeystone</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentHorizontalKeystone</name>
                  <direction>out</direction>
                  <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetLoudness</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentLoudness</name>
                  <direction>out</direction>
                  <relatedStateVariable>Loudness</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetMute</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentMute</name>
                  <direction>out</direction>
                  <relatedStateVariable>Mute</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetRedVideoBlackLevel</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentRedVideoBlackLevel</name>
                  <direction>out</direction>
                  <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetRedVideoGain</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentRedVideoGain</name>
                  <direction>out</direction>
                  <relatedStateVariable>RedVideoGain</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetSharpness</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentSharpness</name>
                  <direction>out</direction>
                  <relatedStateVariable>Sharpness</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetVerticalKeystone</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentVerticalKeystone</name>
                  <direction>out</direction>
                  <relatedStateVariable>VerticalKeystone</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetVolume</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentVolume</name>
                  <direction>out</direction>
                  <relatedStateVariable>Volume</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetVolumeDB</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentVolume</name>
                  <direction>out</direction>
                  <relatedStateVariable>VolumeDB</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>GetVolumeDBRange</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>MinValue</name>
                  <direction>out</direction>
                  <relatedStateVariable>VolumeDB</relatedStateVariable>
                </argument>
                <argument>
                  <name>MaxValue</name>
                  <direction>out</direction>
                  <relatedStateVariable>VolumeDB</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>ListPresets</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>CurrentPresetNameList</name>
                  <direction>out</direction>
                  <relatedStateVariable>PresetNameList</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SelectPreset</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>PresetName</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_PresetName</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetBlueVideoBlackLevel</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredBlueVideoBlackLevel</name>
                  <direction>in</direction>
                  <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetBlueVideoGain</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredBlueVideoGain</name>
                  <direction>in</direction>
                  <relatedStateVariable>BlueVideoGain</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetBrightness</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredBrightness</name>
                  <direction>in</direction>
                  <relatedStateVariable>Brightness</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetColorTemperature</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredColorTemperature</name>
                  <direction>in</direction>
                  <relatedStateVariable>ColorTemperature</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetContrast</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredContrast</name>
                  <direction>in</direction>
                  <relatedStateVariable>Contrast</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetGreenVideoBlackLevel</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredGreenVideoBlackLevel</name>
                  <direction>in</direction>
                  <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetGreenVideoGain</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredGreenVideoGain</name>
                  <direction>in</direction>
                  <relatedStateVariable>GreenVideoGain</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetHorizontalKeystone</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredHorizontalKeystone</name>
                  <direction>in</direction>
                  <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetLoudness</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredLoudness</name>
                  <direction>in</direction>
                  <relatedStateVariable>Loudness</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetMute</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredMute</name>
                  <direction>in</direction>
                  <relatedStateVariable>Mute</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetRedVideoBlackLevel</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredRedVideoBlackLevel</name>
                  <direction>in</direction>
                  <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetRedVideoGain</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredRedVideoGain</name>
                  <direction>in</direction>
                  <relatedStateVariable>RedVideoGain</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetSharpness</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredSharpness</name>
                  <direction>in</direction>
                  <relatedStateVariable>Sharpness</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetVerticalKeystone</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredVerticalKeystone</name>
                  <direction>in</direction>
                  <relatedStateVariable>VerticalKeystone</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetVolume</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredVolume</name>
                  <direction>in</direction>
                  <relatedStateVariable>Volume</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
            <action>
              <name>SetVolumeDB</name>
              <argumentList>
                <argument>
                  <name>InstanceID</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
                </argument>
                <argument>
                  <name>Channel</name>
                  <direction>in</direction>
                  <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
                </argument>
                <argument>
                  <name>DesiredVolume</name>
                  <direction>in</direction>
                  <relatedStateVariable>VolumeDB</relatedStateVariable>
                </argument>
              </argumentList>
            </action>
          </actionList>
          <serviceStateTable>
            <stateVariable sendEvents="no">
              <name>GreenVideoGain</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>BlueVideoBlackLevel</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>VerticalKeystone</name>
              <dataType>i2</dataType>
              <allowedValueRange>
                <minimum>-32768</minimum>
                <maximum>32767</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>GreenVideoBlackLevel</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>Volume</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>Loudness</name>
              <dataType>boolean</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>A_ARG_TYPE_InstanceID</name>
              <dataType>ui4</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>RedVideoGain</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>ColorTemperature</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>65535</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>Sharpness</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>A_ARG_TYPE_PresetName</name>
              <dataType>string</dataType>
              <allowedValueList>
                <allowedValue>FactoryDefaults</allowedValue>
                <allowedValue>InstallationDefaults</allowedValue>
                <allowedValue>Vendor defined</allowedValue>
              </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>RedVideoBlackLevel</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>BlueVideoGain</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>Mute</name>
              <dataType>boolean</dataType>
            </stateVariable>
            <stateVariable sendEvents="yes">
              <name>LastChange</name>
              <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>A_ARG_TYPE_Channel</name>
              <dataType>string</dataType>
              <allowedValueList>
                <allowedValue>Master</allowedValue>
                <allowedValue>LF</allowedValue>
                <allowedValue>RF</allowedValue>
              </allowedValueList>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>HorizontalKeystone</name>
              <dataType>i2</dataType>
              <allowedValueRange>
                <minimum>-32768</minimum>
                <maximum>32767</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>VolumeDB</name>
              <dataType>i2</dataType>
              <allowedValueRange>
                <minimum>-32768</minimum>
                <maximum>32767</maximum>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>PresetNameList</name>
              <dataType>string</dataType>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>Contrast</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
            <stateVariable sendEvents="no">
              <name>Brightness</name>
              <dataType>ui2</dataType>
              <allowedValueRange>
                <minimum>0</minimum>
                <maximum>100</maximum>
                <step>1</step>
              </allowedValueRange>
            </stateVariable>
          </serviceStateTable>
        </scpd>
    
Control
  • 控制点发送控制事件,设置播放内容
        POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1
        Content-Type: text/xml; charset="utf-8"
        HOST: 192.168.3.65
        Content-Length: 1808
        SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
        Connection: close
    
        <?xml version="1.0" encoding="utf-8"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
           <s:Body>
              <u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                 <InstanceID>0</InstanceID>
                 <CurrentURI>http://m801.music.126.net/20200803093741/d5af3e28d583af57bac81d5d341208ba/jdyyaac/055f/515d/0e0e/259b90e667f79bfeab219d9524a256d4.m4a</CurrentURI>
                 <CurrentURIMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot; xmlns:netease=&quot;http://music.163.com/dlna/&quot;&gt;&lt;item id=&quot;5276809&quot;&gt;&lt;dc:title&gt;&amp;#20891;&amp;#38431;&amp;#36827;&amp;#34892;&amp;#26354;&lt;/dc:title&gt;&lt;dc:creator&gt;Franz Schubert&lt;/dc:creator&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;res protocolInfo=&quot;http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;&quot; duration=&quot;00:05:05.000&quot;&gt;http://m801.music.126.net/20200803093741/d5af3e28d583af57bac81d5d341208ba/jdyyaac/055f/515d/0e0e/259b90e667f79bfeab219d9524a256d4.m4a&lt;/res&gt;&lt;upnp:artist&gt;Franz Schubert&lt;/upnp:artist&gt;&lt;upnp:album&gt;&amp;#21490;&amp;#19978;&amp;#26368;&amp;#20248;&amp;#32654;&amp;#38050;&amp;#29748;&amp;#23567;&amp;#21697;&amp;#31934;&amp;#21326;&lt;/upnp:album&gt;&lt;upnp:albumArtURI&gt;http://p1.music.126.net/UTrFxVAoq-Qh50dB_M74dw==/51677046522530.jpg&lt;/upnp:albumArtURI&gt;&lt;netease:musicId&gt;5276809&lt;/netease:musicId&gt;&lt;neteasemusicdlna&gt;1&lt;/neteasemusicdlna&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</CurrentURIMetaData>
              </u:SetAVTransportURI>
           </s:Body>
        </s:Envelope>
    
    
  • 设备处理 Action 并响应控制点
        HTTP/1.1 200 OK
        CONTENT-LENGTH: 270
        CONTENT-TYPE: text/xml; charset="utf-8"
        DATE: Mon, 03 Aug 2020 01:12:41 GMT
        EXT:
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        X-User-Agent: redsonic
    
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>
            <u:SetAVTransportURIResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"></u:SetAVTransportURIResponse>
            </s:Body> </s:Envelope>
    
  • 控制点发送控制事件,设置开始播放
        POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1
        Content-Type: text/xml; charset="utf-8"
        HOST: 192.168.3.65
        Content-Length: 349
        SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"
        Connection: close
    
        <?xml version="1.0" encoding="utf-8"?>
        <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>
    
    
  • 设备处理 Action 并响应控制点
        HTTP/1.1 200 OK
        CONTENT-LENGTH: 244
        CONTENT-TYPE: text/xml; charset="utf-8"
        DATE: Mon, 03 Aug 2020 01:12:41 GMT
        EXT:
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        X-User-Agent: redsonic
    
        
            
             
    
Event
  • 订阅 AVTransport 事件
        SUBSCRIBE /_urn:schemas-upnp-org:service:AVTransport_event HTTP/1.1
        Content-Length: 0
        HOST: 192.168.3.65
        CALLBACK: 
        NT: upnp:event
        TIMEOUT: Second-36000
        Connection: close
    
    
  • 响应订阅请求
        HTTP/1.1 200 OK
        DATE: Mon, 03 Aug 2020 01:12:40 GMT
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        CONTENT-LENGTH: 0
        X-User-Agent: redsonic
        SID: uuid:6ce3110c-1dd2-11b2-bab1-d0f9824e2d38
        TIMEOUT: Second-36000
    
    
  • 订阅 AVTransport 事件
        SUBSCRIBE /_urn:schemas-upnp-org:service:RenderingControl_event HTTP/1.1
        Content-Length: 0
        HOST: 192.168.3.65
        CALLBACK: 
        NT: upnp:event
        TIMEOUT: Second-36000
        Connection: close
    
    
  • 响应订阅请求
        HTTP/1.1 200 OK
        DATE: Mon, 03 Aug 2020 01:12:40 GMT
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        CONTENT-LENGTH: 0
        X-User-Agent: redsonic
        SID: uuid:6ce7ec36-1dd2-11b2-bab1-d0f9824e2d38
        TIMEOUT: Second-36000
    
    
  • 控制点查询设备状态:Position
        POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1
        Content-Type: text/xml; charset="utf-8"
        HOST: 192.168.3.65
        Content-Length: 345
        SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"
        Connection: close
    
        <?xml version="1.0" encoding="utf-8"?>
        <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>
              </u:GetPositionInfo>
           </s:Body>
        </s:Envelope>
    
    
  • 设备响应查询
        HTTP/1.1 200 OK
        CONTENT-LENGTH: 1880
        CONTENT-TYPE: text/xml; charset="utf-8"
        DATE: Mon, 03 Aug 2020 01:12:41 GMT
        EXT:
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        X-User-Agent: redsonic
    
        <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:05:05</TrackDuration>
            <TrackMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot; xmlns:netease=&quot;http://music.163.com/dlna/&quot;&gt;&lt;item id=&quot;5276809&quot;&gt;&lt;dc:title&gt;&amp;#20891;&amp;#38431;&amp;#36827;&amp;#34892;&amp;#26354;&lt;/dc:title&gt;&lt;dc:creator&gt;Franz Schubert&lt;/dc:creator&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;res protocolInfo=&quot;http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;&quot; duration=&quot;00:05:05.000&quot;&gt;http://m801.music.126.net/20200803093741/d5af3e28d583af57bac81d5d341208ba/jdyyaac/055f/515d/0e0e/259b90e667f79bfeab219d9524a256d4.m4a&lt;/res&gt;&lt;upnp:artist&gt;Franz Schubert&lt;/upnp:artist&gt;&lt;upnp:album&gt;&amp;#21490;&amp;#19978;&amp;#26368;&amp;#20248;&amp;#32654;&amp;#38050;&amp;#29748;&amp;#23567;&amp;#21697;&amp;#31934;&amp;#21326;&lt;/upnp:album&gt;&lt;upnp:albumArtURI&gt;http://p1.music.126.net/UTrFxVAoq-Qh50dB_M74dw==/51677046522530.jpg&lt;/upnp:albumArtURI&gt;&lt;netease:musicId&gt;5276809&lt;/netease:musicId&gt;&lt;neteasemusicdlna&gt;1&lt;/neteasemusicdlna&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</TrackMetaData>
            <TrackURI>http://m801.music.126.net/20200803093741/d5af3e28d583af57bac81d5d341208ba/jdyyaac/055f/515d/0e0e/259b90e667f79bfeab219d9524a256d4.m4a</TrackURI>
            <RelTime>00:04:32</RelTime>
            <AbsTime>00:04:32</AbsTime>
            <RelCount>2147483647</RelCount>
            <AbsCount>2147483647</AbsCount>
            </u:GetPositionInfoResponse>
            </s:Body> </s:Envelope>
    
  • 控制点查询设备状态:Volume
        POST /_urn:schemas-upnp-org:service:RenderingControl_control HTTP/1.1
        Content-Type: text/xml; charset="utf-8"
        HOST: 192.168.3.65
        Content-Length: 373
        SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#GetVolume"
        Connection: close
    
        <?xml version="1.0" encoding="utf-8"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
           <s:Body>
              <u:GetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
                 <InstanceID>0</InstanceID>
                 <Channel>Master</Channel>
              </u:GetVolume>
           </s:Body>
        </s:Envelope>
    
    
  • 设备响应查询
        HTTP/1.1 200 OK
        CONTENT-LENGTH: 296
        CONTENT-TYPE: text/xml; charset="utf-8"
        DATE: Mon, 03 Aug 2020 01:12:41 GMT
        EXT:
        SERVER: Linux/4.9.118+, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
        X-User-Agent: redsonic
    
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <s:Body>
            <u:GetVolumeResponse xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
            <CurrentVolume>29</CurrentVolume>
            </u:GetVolumeResponse>
        </s:Body>
        </s:Envelope>
    
  • 设备 Notify LastChange 事件
        NOTIFY /evetSub HTTP/1.1
        HOST: 192.168.3.60:8058
        CONTENT-TYPE: text/xml; charset="utf-8"
        CONTENT-LENGTH: 364
        NT: upnp:event
        NTS: upnp:propchange
        SID: uuid:6ce3110c-1dd2-11b2-bab1-d0f9824e2d38
        SEQ: 2
        
        <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
        <e:property>
        <LastChange>&lt;Event xmlns = &quot;urn:schemas-upnp-org:metadata-1-0/AVT/&quot;&gt;&lt;InstanceID val=&quot;0&quot;&gt;&lt;TransportState val=&quot;PLAYING&quot;/&gt;&lt;TransportStatus val=&quot;OK&quot;/&gt;&lt;/InstanceID&gt;&lt;/Event&gt;</LastChange>
        </e:property>
        </e:propertyset>            
    
    
  • 控制点响应设备上报
        HTTP/1.1 200 OK
        Content-Type: text/html; charset="utf-8"
        Server: Linux/4.4.21-perf+ CyberHTTP/1.0
        Content-Length: 0
        Date: Mon, 03 Aug 2020 09:12:49 GMT    
    
    
Presentation

待补充

说明

首先这里的抓包并不全,而且此部分后续也不会在补录了。 但是即便是这样,上面的这些协议包已经足够对 UPnP 协议有个大概了解了。 而如果是了解 HTTP 协议和原理和服务器实现原理的同学,最后一部分 Presentation 也应该是很容易理解的。 但是市面上实现 Presentation 这部分的机器确实比较少见, 甚至可以说几乎没有。 我估摸着即便 DLNA 设备卖得再火,应该也比不上路由器卖得火吧。

还有一部分是关于 DHCP 部分的,这部分下次找时间另开专题解释吧。 至于 Auto-IP 这个我是不太懂,而且也不想懂, 感觉这个协议是上个世纪的,现在基本上没有路由器不支持 DHCP 了吧。 有市场才会促进技术的革新,这种老古董,现在也没有必要再翻出来研究了。 不过商定 IP 的过程,可能以后在家庭设备主服务自举方面可能会用得着。

DLNA 协议
DLNA 交互设计架构

先祭出架构图:

上图所展示的是 DLNA 网络交互协议的组件框架,其中主要分成网络基础设施(网络连接),网络,协议栈,流媒体传输,媒体格式这么几个部分。

网络与连接部分主要由支持 IPv4 的网络基础设施提供,虽然在短距离场景下还会有蓝牙的身影,但是现在市面上蓝牙这方面的产品是越来越少了,其中主要原因是受制于蓝牙的通信速率。 而 IPv4 (后续由扩充 IPv6 部分,但是我还没花时间去研究那部分)作为应用最广泛,拥有非常成熟的技术, 已经渗透到生活工作的方方面面的一个协议来说,作为 DLNA 协议的载体是再好不过了。 (IPv4 在 DLNA 官方的参考指南中是作为 DLNA 实现参考的一部分的)

多媒体应用在 IP 网络中的一个好处是, IP 网络提供了 QoS 的 功能,该功能会决定和优化资源在不同应用之间的分配方式, 换而言之,每个应用在 IP 网络中都能得到响应的传输数据的机会。 像提供视频流这类的应用,对网络的延迟极为敏感,在 QoS 的帮助下,这类的数据包便会在网络传输中享有更优的权限(Qos Priority 会指定为 User Priority (UP) 权限)。 DLNA QoS 模型试图希望利用 UP 的 DLNA 应用程序能够标记自己的数据包。但如果不希望使用 QoS 的应用也必须有途径关闭此标记。 除了互操作性之外,DLNA QoS 模型还促进了所有 DLNA 流量类型之间公平,一致地使用优先级和平衡性能。 从而增强整体用户体验。

被控设备可向网络中宣告自己的存在,CP 可发现环境中的设备,并加以控制,而这刚好是 UPnP 实现的功能, 因此, UDA 是 DLNA 发现控制协议的首选。

媒体管理使设备和应用程序可以在家庭网络设备上识别,管理和分发媒体内容。 UPnP Audio/Video (AV) and UPnP Printer 技术满足了家庭网络的所有这些需求,并且是 DLNA 设备的媒体管理解决方案。 UPnP A/V 体系结构定义了 UPnP AV 设备与关联的控制点应用程序之间的交互模型。 UPnP AV 设备可以以多种形式实例化自己,包括(但不限于)电视,VCR,DVD 播放器,机顶盒,立体声系统,静止图像相机,便携式媒体播放器,手机和PC。 UPnP AV 体系结构允许设备使用任何媒体传输协议以任何格式支持娱乐内容。 UPnP AV 规范定义了家庭网络上的两种类型的UPnP设备:UPnP AV MediaServers 和 UPnP AV MediaRenderer。 该规范还定义了 UPnP AV MediaServer 和 UPnP AV MediaRenderer 托管的四种服务。暗示存在与 UPnP AV 设备和服务交互的 UPnP 控制点。 这些服务隐含了 CP 和设备之间的交互关系。 1) Content Directory Service 向环境中提供可下载/播放/的媒体内容; 2) Connection Manager Service 决定那些内容可以从 UPnP AV MediaServer 传输至 UPnP AV MediaRenderer 设备; 3) AV Transport Service: 管理和控制流的传输; 4) Rendering Control Service: 展示播放媒体内容。

UPnP Printer 架构定义了 UPnP 打印设备和控制点之间的交互模型。 比如一台支持 UPnP 的照片打印设备,该设备支持了 UPnP PrintEnhanced:1 服务, 该服务又将 XHTML-Print / CSS Print / CSS Print增强版面扩展指定为打印页面描述语言。

媒体格式描述了,如何对内容进行编码和格式化以在家庭网络上进行传输和呈现。 DLNA 媒体格式模型旨在实现网络互操作性的 baseline,同时鼓励媒体编解码器技术的不断创新。

DLNA 定义了三类设备,图像/音频和视频设备,每一类设备都定义了强制支持的格式和可选支持的格式。 媒体格式的描述文档是由一系列的属性/参数/系统/编码级别所决定的, 为了提供不同的设备类别之间的良好的交互性能,媒体交互模型最好支持最基本的不同媒体格式之间的转换。 另外,DLNA 媒体格式模型指定了有关在可选格式和强制格式之间进行转换的规则,以确保可以在所有设备上展示内容。 DLNA 的交互性设计文档中有对此的详细说明,我有时间也会将相关信息在接下来的文档中说明。

媒体传输模块定义了媒体内容是如何在家庭网络中传输的。 DLNA 设备通过 HTTP 协议从媒体源获取媒体内容,而支持 HTTP 方式传输是媒体内容,则是传输中最基础的部分, 其次某些媒体内容可能是通过 RTP 传输,因此也推荐实现此功能。

DLNA 设备模型

在家庭网络中存在不同的设备类型,如 HND (家庭网络设备)和 MHD(手持设备), 他们在网络连接和媒体格式方面都有不同的需求。 本小节为这些相似的设备提供了统一的术语和用法模型。

为了支持家庭网络设备和移动手持设备之间的互操作性,家庭网络设备有可能满足相应移动手持设备的所有要求。移动手持设备也需要满足相应家庭网络设备的所有要求。 在这些情况下,此类设备既是 HND 的成员,也是 MHD 设备的成员。

但是在大多数情况下可能不可行,因此实现互操作性的另一种方法是通过一组设备,这些设备将能够在这两个设备类别之间提供桥接或内容转换服务。 这些设备属于称为家用信息设备(HID)的设备类别。以下总结了这些设备:

如第4节所述,遵守DLNA家庭网络设备互操作性准则的设备具有六个体系结构层。 总之,它们是用于描述一致内容的媒体格式, 媒体管理,用于描述如何找到和控制内容以实现不同的系统使用情况,用于设备控制的设备发现和控制, 媒体传输用于内容传输,网络堆栈用于IPv4协议要求,网络连接用于支持不同的网络物理层。

关于 DLNA 涉及的四大服务

DLNA 主要涉及的四个服务 Content Directory Service, Connection Manager Service, AV Transport Service, Rendering Control Service 均参考 UPnP 分别针对这四个服务定义。下面我们分别对这四个服务做详细的介绍。

  1. Content Directory Service 在 home network 中可能有许多设备有各种各样的内容想要分享给其他设备展示(如: 音乐,图片,视频)。 为了方便 homeowner 能方便的浏览设备中的内容并投射到相应的播放器中播放,一般存储这些内容的设备都自己存在 UI 界面帮助 homeowner 控制内容的播放, 如果这样的内容比较多,这样的操作是非常繁琐的。Content Directory Service 即是为了解决此场景的用户需求,提出了一种为浏览设备中内容的统一机制, 以供 UI 设备能方便的存储/浏览/获取/查找各个设备中的内容。
    一般该服务的设备类别属于:urn:schemas-upnp-org:service:ContentDirectory:1
  2. Connection Manager Service 该类服务用于对设备之间的 stream 关系进行建模,任何一个拥有一个 ConnectionManager 实例的设备都可以发送(或接收)媒体流。 该服务为控制点(CP)提供如下功能:
    a. 将相应的设备配置为源设备(或者渲染设备)
    b. 发现(查找)当前网络中正在进行传输的信息
    c. 帮助设备之间建立(断开)链接
    ConnectionManager 是一个正对流传输协议的通用抽象,使用此服务可以使控制点不必关注设备之间采用何种流传输协议。
    一般该服务的设备类别属于:urn:schemas-upnp-org:service:ConnectionManager:1
  3. AV Transport Service UPnP AV Architecture 中提到的 AV 架构定义了通用的 UPnP 控制点和 AV 设备之间的交互逻辑。该架构独立于任何设备类型,媒体格式和传输协议。 他支持广泛的设备类型,比如说 TV, VCR, CD/DVD, MP3, PC ... AV Architecture 还允许传输不同类格式的媒体内容(比如: MPEG2, JPEG, MP4, WMA, BMP, NTSC, PAL, ATSC)。 还支持多种类型的传输协议(如:IEC-61883/IEEE-13194, HTTP GET, RTP, HTTP PUT/POST, TCP/IP ...)。
    AV Archtecure 旨在实现一种 AV 架构,它能支持传输任何的媒体合适类型; 不需要控制点过多的干预就能随心所欲的控制媒体内容在设备之间传输;同时使控制点独立与任何的传输协议,媒体格式,并在新传输协议或者格式发明的时候能快速方便的兼容;同时还能以极小的代价轻松的在任何设备运行。
    (上叙目的仅仅出现在 UPnP AV Arch... 第一版本中)
    一般该服务的设备类别属于:urn:schemas-upnp-org:service:AVTransport:1
  4. Rendering Control Service 大部分的渲染设备都具备多项配置参数,当这些参数设置不同的时候,渲染的内容也会随之改变(比如 TV 在播放内容的时候可以改变屏幕的亮度,对比度,清晰度之类的参数)。 而 RenderingControl 服务则是提供一组控制接口用于控制渲染设备的各项参数。
    一般该服务的设备类别属于:urn:schemas-upnp-org:service:RenderingControl:1
DLNA AVTransport 服务状态转移流程

以图祭天,以下是 V1 版本的:

以图祭天,以下是 V2 版本的:

DLNA AVTransport 事件模型

自从 AVTransport 支持多实例,传统的 UPnP 的事件模型机制便已无法区分拥有同样状态的多个实例。 因此 AVTransport 服务设计了一套独特的机制 ( LastChange )来描述各个实例独自的状态变化。 在这个模型中 LastChange 是唯一可 Event 的事件。其他的状态变量都需要通过 LastChange 来间接的向定户发送状态变化 ( Position 信息除外,A_ARG_TYPE_ 信息除外, A_ARG_TYPE_ 禁止直接和间接的 eventing )。

为了使感兴趣的接受端能及时准确的获取到媒体内容的 Position 信息,接受端主动查询此类信息是更明智的选择, 因此 RelativeTimePosition, AbsoluteTimePosition, RelativeCounterPosition, AbsoluteCounterPosition, 不会通过 LastChange 机制来 Eventing, 而是由感兴趣的终端来主动获取。 对于终端,它可以在 AVTransport 实例处于 PLAYING, RECORDING, TRANSITIONING 状态时的任意时刻, 以其自定义的速率调用 GetPositionInfo 来获取上叙信息,

自从采取了 LastChange 这种方式之后,多个状态会聚集到 LastChange 中,此时可能并不会马上触发 LastChange 的发送, 而是会等到 moderation 过期时间的到来。当这个发生的时候,LastChange 会 Follow 标准 UPnP 的机制将事件发送出去。 所有对此感兴趣的 DMC 都会收到此消息。 (但据我目前测试的情况所知,很多 DMC 因为处理自己的订阅不当,会误认为其他 DMC 控制的内容是自己的, 从而影响到自己的状态,从而影响到整个 DLNA 的投屏体验)

在 LastChange 将状态变量发送出去之后,它应该将自己缓存中的状态都清理干净,以准备存储下一次的状态变化。 (注意:清除状态的时候不需要再产生额外的事件,以免产生不必要的壅塞)

因为 LastChange State 是带有 filtter 属性的,因此在有些状态可能在一个 Moderation Priod Expires 里面产生多次变化。 这时, LastChange 仅会为这个变量保存一个用户表示设备当前状态的单一的值。 标准的 UPnP 协议规定了 CP 何时去订阅接收的事件,在订阅之时,当前所有的可 event 时间都应该 return 给 subscriber. 虽然 LastChange 是唯一的直接可发送状态变量,但是使用 LastChange 状态变量的当前值响应事件订阅请求并不是很有意义。 因此,当接收到事件订阅时,设备应使用该服务的所有有效实例中所有(间接事件)状态变量的当前值进行响应。

DLNA AVTransport 状态变量以及其作用和值域

下面我只会对特定的几个常用的状态做说明,其他状态变量的说明还是请参考《UPnP-av-AVTransport-v1-Service.pdf》。

  • TransportState
    不管是播放还是录制,该状态包含下列表列举的值,而该状态构成了 AVTransport 服务的核心状态, 设备也并不需要实现所有的这些状态(比如只支持播放的设备就没有 RECODING 之类的状态),
    注意: PAUSED_RECORDING 和 STOPPED 的意义不尽相同,PAUSED_RECORDING 说明已经开始录制,只是暂停了,恢复的时候能尽快的恢复。
    注意: PAUSED_PLAYBACK 和 PAUSED_RECORDING 也不是只同一意义
    其他字段的意义和字面意义一致
    以下为 TransportState 的值域:(Request 代表必须实现,Option 代表可选实现)
    • STOPPED Request
    • PLAYING Request
    • TRANSITIONING Option
    • PAUSED_PLAYBACK Option
    • PAUSED_RECORDING Option
    • RECORDING Option
    • NO_MEDIA_PRESENT Option
  • TransportStatus
    此状态不同于 state, state 主要是指示 AVTransport 服务的当前播放状态,而 Status 主要是指示当前 AVTransport 服务是否正常, 因为在一些时候可能因为网络的原因出现暂时的卡顿,或者 DMR 有某些异常需要 DMC (或其他)接收。 此时可以通过该状态变量通知定户。
    服务无异常,该状态变量值为 OK
    DLNA 官方只定义了一个模糊的错误值 ERROR_OCCURRED, 但并没有指定该值的详细错误场景,因此如果有特殊需求,供应商可自行设计自己的错误代码
    以下为 TransportStatus 的值域:(Request 代表必须实现,Option 代表可选实现)
    • OK Request
    • ERROR_OCCURRED Request
  • PlaybackStorageMedium
    用于指示当前播放的存储媒介。 该状态是可选( Option )实现的。 具体的值请参考 《 UPnP-av-AVTransport-v1-Service.pdf 》
  • RecordStorageMedium
    用于指示当前录音的存储媒介,和 PlaybackStorageMedium 的值域相同。 该状态是可选( Option )实现的。 具体的值请参考 《 UPnP-av-AVTransport-v1-Service.pdf 》
  • PossiblePlaybackStorageMedia
    使用 CSV 格式的一个字符串列表,指明设备可支持从那些存储介质上播放媒体内容。
  • PossibleRecordStorageMedia
    使用 CSV 格式的一个字符串列表,指明设备可将媒体内容存储到哪些介质上。
  • CurrentPlayMode
    当前的播放模式,拥有一下的可选值:
    • NORMAL Request
    • SHUFFLE Option
    • REPEAT_ONE Option
    • REPEAT_ALL Option
    • RANDOM Option
    • DIRECT_1 Option | 播放当前一首,然后结束播放
    • INTRO Option | 代表每首歌播放一个很简短的 Sample
  • TransportPlaySpeed
    当前播放速度,以有理数/分数字符串表示,比如说当前播放速度为 x1, 那么值就是 "1"。 速度 1 是必须实现的,其他根据供应商自己的应用场景自己设置。
  • RecordMediumWriteStatus
    当前存储介质的读写状态,有一下可选项目:WRITABLEPROTECTEDNOT_WRITEBLEUNKNOWNNOT_IMPLEMENTED
  • CurrentRecordQualityMode
    当前的录制质量等级,有以下可选项目:
  • PossibleRecordQualityModes
  • NumberOfTracks
    描述当前播放的那个音轨,如果设备当前未存在播放或者录制,那么当前的值应当为 0, 但对于没有音轨概念的设备(如磁带播放器),则值恒为 1, 对于 CD 类的设备,该值应该对应 CD 音轨数, 而对于网络播放设备来说,如果当前 AVTransportURI 指向的是播放列表中的内容,那么该值列表的总数,否则值恒为 1。
  • CurrentTrack
    指代当前播放的 Track 在 NumberOfTracks 中的哪一个,如果当前 NumberOfTracks 为 0, 那么此值即为 0。 注意,NumberOfTracks 的步进应当为 1。
  • CurrentTrackDuration
    指示当前 Track 的总播放时常。 时间戳格式应当为 H+:MM:SS[.F+] 或者 H+:MM:SS[.F0/F1]。时间戳前可能携带有 \'-\' 或者 \'+\' 字符。 如:1:01:02 即为一个小时一分钟两秒,该时间戳也可表示为 01:01:02, 01:01:02, 01:01:02.000 ...
  • CurrentMediaDuration
    指示当前 AVTransport 实例所播放媒体的总时长。 值的格式和 CurrentTrackDuration 相同。 CurrentMediaDuration 的时常应该总是大于等于 CurrentTrackDuration 的时长。
  • CurrentTrackMetaData
    指示使用 DIDL-Lite XML 模板框架描述的媒体信息。该信息也可能存在于 AVTransportURIMetaData 字段, 或者音频文件中(如 MP3 的 ID3 标签中)。
  • CurrentTrackURI
  • AVTransportURI
    这里的 URI 可能是单个的 URI, 也可能是一个 URI 的列表。 当 AVTransport 只有一个 Track 时,那么 AVTransportURI 和 CurrentTrackURI 是相同的。
  • AVTransportURIMetaData
  • NextAVTransportURI
  • NextAVTransportURIMetaData
  • RelativeTimePosition
    指示当前播放处在 media track 的时长,从 Track 的开始位置开始计算。值的范围为 "00:00:00" 到 CurrentTrackDuration 状态变量指示的范围。
    对于磁带类虽然有播放列表,但无法定位具体的 Track 起始位置的这类设备,该值即代表从磁带开始播放到现在这个位置的时长。
    如果不支持,那么该字段应该被填写 NOT_IMPLEMENTED
  • AbsoluteTimePosition
    指示当前播放处在整个 PlayList 中的时长。
    如果当前设备不支持检测播放的 Position 但是能够知道何时能够播放结束。那么在播放过程中应该使用 NOT_IMPLEMENTED 而在播放结束使用 END_OF_MEDIA
    如果不支持,那么该字段应该被填写 NOT_IMPLEMENTED
  • RelativeCounterPosition
    意义与 RelativeTimePosition 类似,不过这里采用的数据格式为 i4,单位为 s, (这是我猜的,如果清楚的朋友可以告诉我)
    当不支持该状态变量的时候应该填写 i4 的最大值。
  • AbsoluteTimePosition
    意义与 RelativeTimePosition 类似,不过这里采用的数据格式为 i4,单位为 s, (这是我猜的,如果清楚的朋友可以告诉我)
    当不支持该状态变量的时候应该填写 i4 的最大值。
  • CurrentTransportActions
  • LastChange
    是唯一的一个用于 eventing 的状态变量,它通过 UPnP event 的标准机制通知定户。 所有的其他状态都间接的通过 LastChange 来发送事件。 理论上该变量的一级标签为当前 AVTransport 的实例 ID, 二级标签为各个状态变量,以及其对应的值。
    下面为一个 LastChange 例子:
    <?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_RCS">
                <InstanceID val=”0”>
                <Brightness val=”36”/>
                <Contrast val=”54”/>
                ...
                </InstanceID>
                <InstanceID val=”1”>
                <Mute channel=”Master” val=”0”/>
                <Volume channel=”CF”val=”24”/>
                ...
                </InstanceID>
                </Event>
            </LastChange>
        </e:property>
    </e:propertyset>
    
    
  • A_ARG_TYPE_SeekMode
  • A_ARG_TYPE_SeekTarget
  • A_ARG_TYPE_InstanceID

以下是 2.0 中新增加的

  • CurrentMediaCategory
  • PlaybackStorageMedium
  • DRMState
  • A_ARG_TYPE_DeviceUDN
  • A_ARG_TYPE_ServiceType
  • A_ARG_TYPE_ServiceID
  • A_ARG_TYPE_StateVariableValuePairs
  • A_ARG_TYPE_StateVariableList

其次供应商还可以制定自己的 status。

DLNA AVTransport 定义的 Action
  • SetAVTransportURI
    该方法用于设置 AVTransport 实例所使用的资源。 该方法会携带三个标签信息:InstanceID, CurrentURI, CurrentURIMetaData 建议实例在执行该方法时先检查一下 MIME-type。 CP 可以在制定 CurrentURI 的时候还可以指定该资源的 Metadata. MetaData 使用 DIDL-Lite 格式的字符串表示, 这里可以参考 CurrentURIMetaData。如果不想指定该字段,那么可以给该字段设置为空串。
    在任何 AVTransport 处于任何状态的时候都可以执行该 Action,但要注意做好状态之间的转移和收尾工作。
    当该 URI 指向的是单个媒体文件,那么 NumberOfTracks 的值将变为 1, 如果指向的是一个 PlayList 那么 NumberOfTracks 将变成列表的大小。
    如果播放器无法成功定位到 URI 指向的资源,或者不能成功下载,那么此时 TransportState 应该变为 STOPPED 状态
    如果当前的状态为 PLAYING, 但是在向用户成功展示之前需要花费比较长一段时间来缓冲数据,那么该状态应该为 TRANSITIONING, 成功播放后变为 PLAYING
    如果当前播放结束之后状态处于 NO MEDIA PRESENT, 那么 TransportState 应该变成 STOPPED 状态。
    如果遇到出错的情况,请参考一下列表返回错误状态
  • SetNextAVTransportURI
    该字段与 SetAVTransportURI 类似,传输的内容也类似,主要是用于当前媒体内容将要播放完,需要推送下一条资源,用于提前缓冲,提升用户体验作用。 但是目前几乎很少有 DMC 支持此 Action(好像 QPlay 支持),不多做赘述,详情请翻阅参考文档。
  • GetMediaInfo
    获取当前播放 Media 的信息,响应时需要携带以下信息(如果有些状态不支持,传输的时候指定空串即可)(该 Action 多见于视频内 DMC 实现):
    • InstanceID
    • NrTracks 参考状态变量 NumberOfTracks
    • MediaDuration 参考状态变量 CurrentMediaDuration
    • CurrentURI 参考状态变量 AVTransportURI
    • CurrentURIMetaData 参考状态变量 AVTransportURIMetaData
    • NextURI 参考状态变量 NextAVTransportURI
    • NextURIMetaData 参考状态变量 NextAVTransportURIMetaData
    • PlayMedium 参考状态变量 PlaybackStorageMedium
    • RecordMedium 参考状态变量 RecordStorageMedium
    • WriteStatus 参考状态变量 RecordMediumWriteStatus
  • GetTransportInfo
    获取当前播放 Media 的状态信息,响应时需要携带以下信息(如果有些状态不支持,传输的时候指定空串即可):
    • InstanceID
    • CurrentTransportState 参考状态变量 TransportState
    • CurrentTransportStatus 参考状态变量 TransportStatus
    • CurrentSpeed 参考状态变量 TransportPlaySpeed
  • GetPositionInfo
    获取当前播放 Media 的播放位置信息,响应时需要携带以下信息(如果有些状态不支持,传输的时候指定空串即可):
    • InstanceID
    • Track
    • TrackDuration 参考状态变量 CurrentTrackDuration
    • TrackMetaData 参考状态变量 CurrentTrackMetaData
    • TrackURI 参考状态变量 CurrentTrackURI
    • RelTime 参考状态变量 RelativeTimePosition
    • AbsTime 参考状态变量 AbsoluteTimePosition
    • RelCount 参考状态变量 RelativeCounterPosition
    • AbsCount 参考状态变量 AbsoluteCounterPosition
  • GetDeviceCapabilities
  • GetTransportSettings
  • Stop
    该 Action 用于停止指定 InstanceID 的当前的资源输出。
    在某些设备上,Stop Action 会导致 Position 的变化,CP 可以通过 CurrenTrack 的事件通知得知该变化。当然也可以通过 GetPositionInfo 来主动获取该变化。
  • Play
    该 Action 会携带两个参数:IstanceID, speed
    根据当前设置的播放模式,预先设置的 Position, 预先设置的 URI, 开始播放。
    直到播放结束,或者 TransportState 的状态变成 STOPPED / PAUSED_PLAYBACK
    该 Action 应该可以在 STOPPED, PLAYING 或者 PAUSED_PLAYBACK 状态下执行,在其他状态下,可能会返回 701, 但是更具 vendor 设计的区别。
    如果在执行 Play Action 之后用户还需要等待相当长的一段时间才能开始 PLAY, 那么此时最好加入 TRANSITIONING 状态,等到真正开始播放再变成 PLAYING 状态。(这里之的过长在平时音视频应用中一般是 1s)
  • Pause
    当设备处于 PLAYING 状态的时候,该动作用于停止当前制定 Instance 的播放进度,任何该资源对外展示的信息都处于静态的样子 (针对图像则画面处于静止状态,对于音频则声音处于安静状态)。
    区别于 STOP 状态, PAUSED 状态必须保存当前的播放进度,而且当前播放的资源还保持在前台
    对于处于录制设备,暂停则是让录制处在发生 PAUSE Action 的时间点,当再次收到 Record 的时候从该时间点开始录制。
  • Record
    该 Action 用于启动指定实例(instance)开始录制。 如果 AVTransportURI 有指定资源,那么录制信息应从当前指定资源获取(比如:mic)。 如果没有指定,那么就选择默认的。 在以上两种方案中,是否将录制信息转播到屏幕或者扬声器都取决于设备的交互。 如果录制设备实现了 内容服务器,那么 record 的内容应该以设备自定义的方式转存到设备配置的内容服务器(ContentDirectory)。 具体地来说,UPnP 没有 定义所记录内容在 ContentDirectory 层次结构中的位置。
    该 Action 只能从 STOPPED 或者 PAUSED_RECORDING 状态下启动,如果是其他状态则会返回 701, 但实际的交互还是要结合设备自身定义来实现。
    当成功执行该 Action 之后,状态将变成 RECORDING。
  • Seekmarkdown播放模式,该 Action 有两个参数,InstanceID 和 NewPlayMode, 其中 NewPlayMode 所支持的值域可参考 CurrentPlayMode。

     

    该字段在任何时候都可以执行,当设置该字段之后,接下来的播放动作应该与当前设置的一致。

  • SetRecordQualityMode
  • GetCurrentTransportActions
  • 供应商自定义 Actions
CSV ( Comma Separated Value ) List

属于 DLNA 定义的两种派生数据类型的一种,主要用于承载 list 的值(一维数组)。 CSV 主要用于表达传输控制时的具体数据,该字段通常与 XML 中的 type="xsd:xxx" 属性搭配使用。 xsd:xxx 主要用于指示 CSV 承载的内容的数据类型。 list 中的值可以是统一类型(比如 xsd:string), 也可以是不同的(比如 xsd:string,xsd:integer), 但一般情况下,只会存在一种类型。

参考文档
  1. RFC / UPnP / 蓝牙官方的文档参考链接我就不放在这里了,DLNA 官方尽量完全资料在文首资料包中。
  2. 蓝牙PAN
  3. 蓝牙核心技术概述
DLNA V4.0 RemoteUserInterface HTML5(基于 HTML5 的远程用户界面 )

因为查找到中文规范文档,或者官方的中文解释,标题中的中文标题为我直译而来,并非官方定义。该部分定义出现在 2016 年的 《Part 6-1: Remote User Interface - HTML5》 文档中(资料包中包含该文档)。

RUI-H 定义了用 HTML5 开发远程用户界面的准则。该规范依赖与 HTML5 的通用性,开发商可以利用 HTML5 开发一套 APP, 并可在任何支持 HTML5 标准的浏览器的设备上运行。 利用该技术,公司可以快速迭代自己的产品,并降低了开发/运营成本,为每个设备提供一个统一的 UI。

RUIx-CP 的定义基本涵盖在 29341-12-1,29341-12-2,29341-12-10,29341-12-11 这几个文档中,但是这几个都是要付费购买的,目前没有搜集到这几个文档。

注意: 下文中可能会经常性的提到有无 A/V 功能模块,此处的意思并非指不带音视频播放功能,而是之不携带 DLNA 中定义的 AV 模块。

简介

2012 年 3 月 19 日–数字生活网络联盟®(DLNA®)和 RVU Alliance™ 宣布RVU联盟远程用户界面(RUI)已被纳入 DLNA 互操作性指南。 两家公司之间的关系强调了消费者在整个数字家庭中访问服务提供商内容的重要性。这些新准则允许服务提供商将其功能的外观导出到DLNA认证的设备。

DLNA 了解到,如今的消费者并不局限于客厅看电影。DLNA 主席兼总裁 Nidhish Parikh 表示: “他们希望能够在多种设备*问从服务提供商的订阅中获得的优质内容。” “通过将RVU RUI纳入我们的互操作性指南,我们为消费者提供了新颖的创新方式来连接和使用其数字内容。 想象一下能够在非DVR设备*问DVR功能。RVU RUI使这成为可能,并且现在可以包含在DLNA认证的产品中。”

关于 RVU, RVU 是一种技术协议,它向 UPnP 设备发现和 DLNA 音频/视频内容流规范添加了一个远程用户界面。 RVU RUI 图形命令对于 RVU 协议是唯一的,并且不与任何其他现有协议或标准相对应。 该协议基于客户端-服务器体系结构,其特定目的是利用低成本客户端,并且不增加硬件开销。该服务器通常由内容服务提供商提供, 该内容服务提供商允许向一个或多个消费电子设备分发和管理视频以及一致的用户体验。 RVU 协议白皮书介绍了 RVU 技术及其应用。RVU 协议规范中包含 RVU 技术,并且通过认证程序可确保协议符合性。

RVU 联盟是一个由服务提供商,技术公司和消费电子产品制造商组成的行业联盟。 RVU RUI 是一种远程用户界面技术,当与兼容的 RVU 服务器一起使用时,可以在兼容的客户端设备上准确显示服务提供商的用户界面。 RVU 联盟董事会主席 Henry Derovanessian 表示:“将 RVU RUI 纳 入 DLNA 互操作性指南是 RVU 规范发展的另一个主要里程碑。” “与DLNA的合作进一步将 RVU 应用于互联家庭设备的全球社区,为消费者和服务提供商都提供了帮助。”

RVU 系统已经投放市场,服务提供商 DIRECTV 于 2011 年底推出了 HR34 RVU 服务器。 在 1 月份的 2012 年国际 CES 活动中,三星在其 2011 年连接的三种电视型号中展示了该协议,表明 RVU 支持将在在所有 2012 年连接的电视型号中。

关于支持 RVU 的产品,可在 https://rvuproject.org/products 中查询,其中带有 RVU 认证的设备一般在信源选择处可以找到类似下面的图表:

下面是几个实际生产出来设备中的例子:

下面是实现 RVU 需要用到的技术标准:

  • Device and Service Discovery and Control
    • UPnP SSDP
  • Media Management, Distribution, and Control
    • Digital Living Network Alliance™ (DLNA®)
    • UPnP
  • Content streaming and media format interoperability
    • DLNA AV Transport HTTP
  • Digital content protection
    • DTCP-IP link protection
  • Remote User Interface
    • Low-overhead remote RUI, including remote control commands and status from client devices to the server

下面我们就针对其中涉及到 DLNA 和 UPnP 的技术进行介绍。

名词解释
  • UI User Interface 用于和用户交互的一套界面应用。
  • Remote UI 一个由设备上的服务提供的一台用于用户交互的界面应用(User Interface),该应用可有一个或者多个设备提供。
  • RUI-H HTML5 Reomte User Interface
  • RUI-H Content 实现 UI 所需要的 HTML 文档以及其附属文档(如 image,javascript, CSS, fonts ...),但此处的内容不包括与 HTML5MediaElement 相关的 A/V 资源。
HTML5 RUI 设备功能

RUI-H 交互指南定义了如下的设备功能(原文为 Device Functionis):

  • RUIHS( RUI-H Server ) 顾名思义,该功能提供一个或者多个接口给一个或者多个 UI 用户进行 HTML5 范畴内的操作(如基于 HTML 的 GET/POST 方法)。
  • RUIHS-CP( RUI-H Server Control Point) 该功能是用于为浏览器选择 RUIHS 提供 RUI-H 服务的控制点。
  • RUIHC( RUI-H Client ) 该功能为基于公开的 HTML5 设计标准设计的一个客户端,用于给用户进行 HTML5 应用展示和接受用户操作。
  • RUIHC-CP( RUI-H Client Control Point ) 该功能作为一个控制者,协助 RUIHC 和 RUIHS 建立链接。
  • RUIHTS( RUI-H Transport Server ) RUIHTC( RUI-H Transport Client ) 这个两个功能分别用于担任 RUI-H Content 的分发和接受。
  • RUIHUA( RUI-H User Agent ) 该功能为 RUIHC 的一部分,接受 RUIHTC 接收到的信息进行解码,渲染,展示,并提供输入功能接受用户的操作,对 RUIS 进行检索,修改,控制等操作。
HTML5 RUI 设备能力

RUI-H 交互指南定义了如下设备能力(原文为 Device Capabilities)(下文中所有的设备能力(或者说角色)的简称在 DLNA Guildeline 中都用 + 包围,比如说 RUIHPL 在 GL 中的表示为 +RUIHPL+,但在此文中我将把 + 符号全部去除 ):

  • RUIHPL ( RUI-H Pull Controller ) 该角色可从环境中暴露的 RUIHSRC 发现加载 RUIH Content 并向用户展示和提供交互借口, 该角色包含如下 Functions: RUIHS-CP, RUIHTC, RUIHUA 和可选的用于接收媒体资源的客户端 ( depend vendor )。
  • RUIHSRC ( RUI-H Source Controller ) 该角色拥有想环境中暴露 RUI-H Content 的能力, 该角色包含如下 Functions: RUIHS, RUIHTS 和可选的用于发送媒体资源的服务端。
  • RUIHSINK ( RUI-H Sink Capability ) 该角色用于向环境中展示 RUI-H Content and Exposing HTML5 remote UI functionality(个人感觉有歧义), 该角色包含如下 Functions: RUIHC, RUIHTC, RUIHUA 和可选的用于接收媒体资源的客户端。
  • RUIHCTRL ( RUI-H Controller ) 该角色用与发现 RUIHSRC 和 RUIHSINK,并协助这两者建立连接。 该角色包含如下 Functions: RUIHS-CP, RUIHC-CP

下图为 RUI-H 各个角色在 2Box 和 3Box 模型中的应用:

模型介绍

目前 DLNA Gildeline 介绍了三种使用场景,但其实际的应用可以根据 Vendor 的产品类型灵活组合,一下例子只是为了加深大家理解尚需提到模块和功能的概念(详细说明请参考: 《DLNA GL June 2016 - Part 6-1 ...》)。

  1. 2Box 模型,其中一方为 +RUIHSRC+ 一方为 +RUIHPL+。 其中 +RUIHSRC+ 负责存储,分发 RUIH Content,而 +RUIHPL+ 负责发现,获取,与 +RUIHSRC+ 进行交互。 基于具体 vendor 的设备定义可能还会具备 A/V 内容传输展示方面的功能。
    以下为无 A/V 方面的模型的交互流程:
    1. 发现远程的 RUI-H remote 设备
    2. 请求 RUI-H Content
    3. 传输 RUI-H
    4. 传输用户交互以及 SRC 的响应信息
    具体图示如下:

    下图为有 A/V 方面的模型的交互流程,在上图的基础上增加了如下两个流程:
    5. 请求 A/V Content
    6. 传输 A/V Content
  2. 3Box 模型,该模型不涉及 A/V 内容的发现和传输。其中涉及 +RUIHCTRL+, +RUIHSRC+, +RUIHSINK+。 其中 +RUIHCTRL+ 负责 发现环境中的 +RUIHSRC++RUIHSINK+, 并协助这方建立链接。 其中 +RUIHSRC+ 主要负责存储,分发 RUIH Content。 而 +RUIHSINK+ 负责负责获取 RUI-H Content 和与 +RUIHSRC+ 进行交互。 该模型与上述模型的区别在于吧控制部分的逻辑独立出来了。
    一下为该模型下的一种使用场景(比如你的手机发现和浏览 RUI, 而你家的 TV 也支持��你突然��在手机上控制 TV 展示你在手机选中的内容 )以及操作步骤:
    1. 发现环境中的 RUI-H 服务
    2. 组织 xxx 与 xxx 建立连接
    3. 从 SRC 处获取 RUI-H Content
    4. 传输 RUI-H Content 至 SINK
    5. 或许由用户主导所触发导致 SRC 和 SINK 中的交互
    6. 发现环境中的其他 SINK2
    7. 组织 SINK2 和 SRC 建立连接
    8. 从 SRC 处获取 RUI-H Content
    9. 传输 RUI-H Content 至 SINK2
    10. 或许由用户主导所触发导致 SRC 和 SINK 中的交互
    图示如下:
  3. 3Box 模型,该模型涉及 A/V 内容的发现和传输。其中涉及 +RUIHCTRL+, +RUIHSRC+, +RUIHSINK+,而且还引入了 DMC, DMS, DMR 角色。 其中 DMR 包含 +RUIHCTRL++RUIHSINK+, DMC 则包含 +RUIHSRC+, 其流程大致如下:
    1. 设备开机之后 DMR 中的 +RUIHCTRL+ 会自动的发现环境中的 +RUIHSRC+ 并让 +RUIHSINK++RUIHSRC+ 建立链接
    2. 其中 +RUIHSRC+ 包含 DMC 功能模块,可发现环境中的 DMS, 并将 DMS 嵌入到 RUI 中,一便于展示给用户。
    3. 用户在 RUI 上的操作会导致 +RUIHSINK++RUIHSRC+ 的交互
    4. 当用户在 RUI 上选定了展示的 DMS 内容之后,+RUIHSINK+ 会告知 +RUIHSRC+, 而 +RUIHSRC+ 会调用 DMC 去促使 DMR 和 DMS 建立链接,并在 DMR 上呈现给用户
    图示如下:
RUI-H Server 与 Client

关于 RUI 的 Server 和 Client 的设备,在此我仅做一些简单的描述,具体细节还请参考 《UPnP-rui-RemoteUI... 》 相关文档。

RUI 的 Server 和 Client 都与 UDA1.0 中提到的设备服务模型兼容,在承载 Server & Client 的设备在网络环境中上线之后可向环境中广播 RUI Server 和 Client 的存在。 其中 RUI Server 的设备类型为 urn:schemas-upnp-org:device:RemoteUIServerDevice:1 RUI Server 的服务(Service)类型为 urn:schemas-upnp-org:service:RemoteUIServer:1 。 RUI Client 的设备类型为 urn:schemas-upnp-org:device:RemoteUIClientDevice:1, RUI Client 的服务(Service)类型为 urn:schemas-upnp-org:service:RemoteUIClient:1

其设备描述文件可以搭在在 root device 下, 如下图所示(在此处仅列出 RUI Server device 的描述文档 ):

RUI-H Server

RUI Server GL 中定义的标准 Status 有下面几种,其中 A_ARG_TYPE_DeviceProfile, A_ARG_TYPE_CompatibleUIs,A_ARG_TYPE_String,是必须支持的 Status, 其中 A_ARG_TYPE_DeviceProfile 指明当前 Server 支持的所有协议,而 A_ARG_TYPE_CompatibleUIs 则提供了一个当前 Server 兼容的所有 UI 列表。

  • Status
    • UIListingUpdate
    • A_ARG_TYPE_DeviceProfile
      该状态的值为一个 XML 文档,知名了当前 Server 所支持的协议列表
    • A_ARG_TYPE_URI
    • A_ARG_TYPE_CompatibleUIs
      Server 必须支持 <= 10KB 的描述文件
      该 Status 的值为一个 XML 文档,它指定了当前 Server 所能提供的 UI 列表,其每一个 UI 同时还包含了 UI 名字,ID, 简明描述, 适配各个客户端尺寸大小的 IconList, 以及最重要的所提供服务的协议以及 URI 列表。
      CP 正是通过该文档中的协议列表配置 Client 通过何种方式进行 UI 交互的。
      该变量的值可通过 GetCompatibleUIs Action 获取
      具体的文档格式请参考 《UPnP-rui-RemoteUIServer-Service ... 》文档。
    • A_ARG_TYPE_String
    • A_ARG_TYPE_Int

RUI Server GL 中定义的标准 Actions 有下面几种

  • Actions
    • GetCompatibleUIs
      该请求需要提供三个参数:InputDeviceProfile, UIFilter, UIListing
      从指定的 DeviceProfile 中提供一个 UIs 列表,因为有可能 Server 并不支持制定的 DeviceProfile 或者没有 Filter / UIListing 中指定的类型设备,则此返回值有可能为空 UI 列表。
    • SetUILifetime
      该请求需要提供两个参数:UI, Lifetime
      该 Action 用于 Set UILifetime
      因为 Server 在交互的过程中可能保留 Client 的一些信息,这些信息在 Client disconnect 之后是不需要在使用的,此时 Client 最好给 Server Set 此值, 当设置时间到达的时候 Server 将会释放与此 Client 通信的实例的相关资源。

目前 UPnP RUI 中定义的协议简称有如下几种:

  • Remote UI Protocols
    • HTTP/HTML
    • RDP 微软定义的一个远程桌面协议(Remote Desktop Protocol)
    • VNC AT&T 定义的一套虚拟网络控制台协议(Vritual Network Console)
    • XRT2 Intel 提出的一个远程 I/O 协议
    • LRDP 诺基亚提出的一个轻量级远程显示协议(Remote Display Protocol)
    • XHT Samsung 提出的可扩展的家庭影院(eXpandable Home Theater)
    • XGXML 西门子提出的一套远程控制协议
    • UIF 飞利浦提出的一套 UI Fragments protocol.
RUI-H Client

RUI Client GL 中定义的标准 Status 有下面几种

  • DeviceProfile
    Required
    该值为一个 UTF8 编码的 XML 文档,该文档下的 protocol 标签标识了 Client 支持的每一个协议
    具体的 XML 格式请参考 Server 中的 DeviceProfile 说明
  • CurrentConnections
    Required
    该状态是必须实现的
    该状态的值为一个逗号分割的的字符串,该字符串包含一个递增序列和一系列的 UI URI
    其中第一个字段为递增序列,该序列自第一次设置 ConnectionsUpdateID, 开始每一次完成 Connect() 或者 Disconnect() 之后就递增一次, 递增的最大值为 2147483647(i4 的最大值),当再次递增时,该值回归 0
    随后的第二个字段则为当前 Active 的 UI URI,如果当前没有任何 Active UI,那么展示的应该是本地的一个默认 UI 地址,如:http://localhost:8080/null
    再之后的 URI 为建立第一个会话之后使用过的所有 UI URI,
    其值格式如下:5604,XRT://1.23.345.1/My_Music_Player0xa12c67,local://127.4.6.1/null,VNC://1.23.345.2/My_Photo_Viewer,RDP://1.23.345.3/Super_Chess
  • CurrentConnectionsEvent
    Optional
    该状态变量用于在 CurrentConnections 发生变化的时候给定户发送变更消息的用途
  • CompatibleUIsUpdateIDEvent
  • A_ARG_TYPE_String
  • A_ARG_TYPE_CompatibleUIs
  • A_ARG_TYPE_DisplayMessageType
  • A_ARG_TYPE_InputDataType
  • A_ARG_TYPE_Int

RUI Client GL 中定义的标准 Actions 有下面几种:(对于能从名字一眼看清楚工用的 Action,或者不太用途不太广泛的 Action, 在此不再过多赘述)

  • Connect
    Required
    创建一个新的链接,该请求必须在建立连接成功,或者失败后才能返回
    该 Action 接受两个参数, RequestedConnections 和 CurrentConnectionsList,他们的格式的都与 status 中介绍的 CurrentConnections 一致
    一个 RequestedConnections 中只能有一个 connect,如果存在多个,那么该请求会直接返回 701; 而且如果该参数的第一个参数与 Client 目前的 ConnectionsUpdateID 不同,那么此时该请求会直接返回 705;
    而 CurrentConnectionsList 则还包含了需要保持链接的一些 UI ( 如果当前 Client 支持 on-hold session 的话 )
  • Disconnect
    Required
    参数与 Connect 一致
    区别在于对于支持 on-hold 的 Client, Disconnect() 当前的连接之后会展示 CurrentConnectionsList 中的第一个 Connections.
  • GetCurrentConnections
    Required
  • GetDeviceProfile
    Required
  • GetUIListing
  • AddUIListing
  • RemoveUIListing
  • DisplayMessage
  • ProcessInput
DeviceProfile 的示例
CompatibleUIs 的示例
一些交互指南

建立链接需要两个元素,一个是 ConnectionsUpdateID, 另外一个是 CompatiableUI URI。 其中,ConnectionsUpdateID 需要和 Client 中 Current ID 一致,因此在 Connect() 之前,最好调用 GetCurrentConnections 获取当前的 ID; 而 CompatiableUI URI 则可以向 Server 发起 GetCompatibleUIs() 去获取。 当然在匹配 Client 和 Server 的时候可能还需要用 GetDeviceProfile() 去获取 Client 的 DeviceProfile。

而 Disconnect() 的时候亦是如此,用 GetCurrentConnections() 去获取当前的 ID 和 Activity URI。

用户在交互的过程中应该是可以*的选择环境中的 UIs 的。 一种可实现此交互的方案是在 Client 中维护一个 UIs List, 而 CP 可以调用 Add(Remove/Get) 来控制这个列表。

Client 中 Display Message 这个功能是一个给用户发送提示信息的功能,比如在建立连接的时候,可能需要花费比较多的时间来 loading, 此时可以使用此方式快速的给用户一个信息,但是实现此信息展示的方式油 vendor 来决定。

参考文档
Platinum 解析
国内各个投屏软件缺陷公示以及分析 - 不完全指南
设备发现体验

LEVEL 1 体验最好,LEVEL 2 体验次之,LEVEL 3 体验最差

  1. LEVEL 1 Kugou, Netease Music, iQiyi, bilibili
  2. LEVEL 2 腾讯视频, 快点投屏, 芒果 TV
  3. LEVEL 3 QQMusic,
爱奇异
  1. BD一贯的吃相一贯就难看,其实发现体验是一流的一批,但是他做了一个很微妙的操作,在 IOS 手机 iqiyi 会屏蔽乐播的投屏接受端,在 Android 应用会屏蔽 QQ 音乐的接收端。 至于是 iqiyi 的本意,还是 iqiyi 插件供应商的本意就不得而知了。不要问我为什么知道,因为我将乐播的 SCPD 改成其他 厂商名字就可以被发现了。amazing 啊。
netease-cloud-music
  1. 目前案只支持 HH:MM:SS 格式的时间戳,不支持 H+:MM:SS[.F+] 或者 H+:MM:SS[.F0/F1] 格式的时间戳。
  2. 切换曲目的时候,如果 DMR 发送的 LastChange Size 过大(900字节),很容易出现 DLNA 失去连接。
  3. 通过 GetPositionInfo 获取 Position 信息之后并不会校验 TrackDuration, TrackMetaData, TrackURI 等信息, 从而会影响 DMR 在播放其他内容时的体验。
  4. 收到的 LastChange 消息过大会直接报 DLNA lost connection。
bilibili
  1. 发送 SetAVTransportURI 的时候 DIDL 中携带的 dc:creator 字段英文语法错误, unkown 应该改成 UNKNOWN (不区分大小写)。
  2. SetAVTransportURI 中附带的 DIDL-Lite 格式不符合规范。
用 Node.js 实现 DLNA 测试工具
最后的最后

此系列文章本着开源,*,分享的前提,尽可能的提供准确的数据,但是本人无法穷尽所有细节。 因此作如下申明: 1)如果有人或者公司因为本文而造成损失,本人不承担任何责任。 2)如果本文档中的内容有涉及到法律法规风险,请即使提醒我,我会及时做出修改或者删除,如无提前通知本人对此造成的后果不负任何责任。 3)本文不承诺,不保证会即使的跟进最新的协议状态,因此,如果因为文档过时产生的偏差,从而导致您产品的缺陷,本人不负任何责任。 4)如本文涉及侵权行为,请联系 majtsdd@163.com

原创文章,版权所有,转载请获得作者本人允许并注明出处
我是留白;我是留白;我是留白;(重要的事情说三遍)