java实现局域网内视频投屏播放(三)投屏原理

常见投屏方案

常见的投屏方案主要有以下几种:

DLNA

DLNA的全称是DIGITAL LIVING NETWORK ALLIANCE(数字生活网络联盟)。DLNA委员会已经于2017年1月5日正式解散,原因是旧的标准已经无法满足新设备的发展趋势,DLNA标准将来也不会再更新。但是DLNA协议的使用依然比较广泛,短时间内不会退出历史舞台,在某些情况下依然是最好的解决方案之一。
DLNA不是技术,而是一种方案,一种大家可以遵守的规范,其各种技术和协议都是目前所应用很广泛的技术和协议(SSDP、SOAP等)。
在我看来,DLNA协议栈为设备之间信息交流提供了一种彼此听得懂的语言工具。

AIRPLAY

AirPlay于DLNA类似,例如两种都是基于组播实现的设备发现,只不过DLNA基于SSDP(简单服务发现协议),而AirPlay基于mDNS(multicast DNS),甚至苹果曾经也是DLNA委员会的成员。相对DLNA,AirPlay提供了一套完善的官方标准实现,开发者只需要按照文档调用API即可,当然如果需要在第三方设备上实现AirPlay功能,需要自己实现一套与AirPlay兼容的功能,网上就有通过分析抓包实现的第三方AirPlay兼容库,包括发送端和接收端。

MIRACAST

以Wi-Fi Direct(和UPnP都是局域网P2P)为基础的无线显示标准,出现时间晚(2012),使用范围相对较小。支持此标准的设备可通过无线方式分享视频画面。与DLNA有较大差异的在于DLNA设备服务端(DMS,Digital Media Server)基于文件的方式提供服务,文件解码由接收端完成(DMR,Digital Media Render),因此DMR需要支持较多格式以保证兼容性;而Miracast则是由服务端完成解码并重新编码为H.264传输到接收端,接收端只需要对H.264解码即可。

基于以上对比来看,DLNA使用广泛,在主流的电视、智能机顶盒中都有支持,而且终端工作量小,是不错的方案。我们这个局域网投屏也是用的DLNA

DLNA投屏原理

UPnP

通用即插即用协议,使用了SSDP(简单设备发现协议)和SOAP(简单对象访问协议)等几个协议。可以说DLNA很大程度上是基于UPnP的。

UPnP协议中,定义了两个主要的组件,一个是设备(Device),一个是控制点(Control Point)。这就是为什么很多UPnP协议栈的SDK的接口代码一般都主要由Device和Control Point构成。设备是在网络中可见的对象,而控制点在网络中不可见。

一个UPnP的设备(Device)是不能直接访问和控制另一个UPnP的设备(Device)的,对设备的访问和控制都必须通过控制点(Control Point)来代为完成。而控制点对设备的控制则主要是由设备定义的“服务”(Service)来实现。

设备(Device)需要向网络中广播自己的信息,并提供设备描述和服务(Service)描述,并发送设备事件消息.

控制点(Control Point)则是搜索设备,并使用其提供的服务(Service)访问和控制设备,同时监听设备事件消息。

设备发现

设备发现也分为两步,第一步是获取设备基本描述,第二步是获取设备详细描述。
第一步有两种方式:主动发现和被动发现。

主动发现

主动发现是指设备主动通过UDP发出Search组播到指定地址和端口,ipv4为239.255.255.250:1900,ipv6为[FF0x::C]:1900,目标设备收到组播后会通过UDP单播发送设备基本信息(所以终端需要用Socket绑定search发送的那个随机端口,receive单播回包),然后根据基本信息中的设备描述地址获取设备的详细信息。
search包内容如下:

M-SEARCH * HTTP/1.1
ST: upnp:rootdevice
HOST: 239.255.255.250:1900
MX: 3
MAN: "ssdp:discover"

其中ST是Search Type,常见的ST有ssdp:allupnp:rootdeviceuuid:device-某UUIDurn:schemas-upnp-org:device:device-Type:version等,投屏这里使用的是upnp:rootdevice,
HOST为组播地址,
MX为最大等待时间,
MAN为固定格式。
设备回包内容如:

HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Fri, 23 Nov 2018 11:26:00 GMT
EXT: 
LOCATION: http://192.168.2.3:49153/description.xml
SERVER: SHP, UPnP/1.0, Samsung UPnP SDK/1.0
ST: upnp:rootdevice
USN: uuid:ecf9f8c1-e1a3-459e-a33e-1f6413af9aef::upnp:rootdevice
Content-Length: 0

其中最重要的是LOCATION,其中包含了目标设备的ip、upnp服务的端口、设备详细描述地址,有了这个地址就可以获取设备的详细信息,具体内容见下文;USN作为服务的唯一识别ID,在设备详细描述中还有,可以暂时忽略。

被动发现

被动发现是指目标设备通过UDP发送Notify组播到局域网(所以终端需要启动一个MulticastSocket joinGroup到上述组播地址监听组播),设备收到组播后可以得到设备描述地址获取设备的详细信息。
Notify报文的内容如下:

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=66
LOCATION: http://192.168.2.3:49153/description.xml
NT: upnp:rootdevice
NTS: ssdp:alive
SERVER: Linux/3.10.79, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
USN: uuid:F7CA5454-3F48-4390-8009-2c3aed46c9a9::upnp:rootdevice

其中也包含了LOCATION,详细信息就不愁啦;还有两个需要关注的值:NTNTS,前者是Notify Type(与Search中的ST类似),后者表示NT的子类型,其值只可以是ssdp:alivessdp:byebye,目标设备会在生命周期中定期发送alive组播,在正常退出时发送byebye组播,也有实现会在目标设备上线时先发送byebye然后发送alive,便于控制端及时更新设备信息。NT有较多类型,我们只关注upnp:rootdevice类型的Notify即可。

设备描述

获取设备详细信息 至此,无论前面通过何种方式,我们都已经得到了一个重要的信息:LOCATION,向该地址发送一个简单的HTTP请求,即可得到详细的设备信息,无论是做投屏还是做基于DLNA的打印机,原理都是一样的,尤其是前面的部分,一模一样,而后面的部分也是换汤不换药,换成了打印相关的服务而已,设备描述示例如下。

head

HTTP/1.1 200 OK
CONTENT-LENGTH: 2506
CONTENT-TYPE: text/xml
DATE: Mon, 07 Jan 2019 11:26:00 GMT
LAST-MODIFIED: Mon, 07 Jan 2019 11:25:17 GMT
SERVER: Linux/3.10.65, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
X-User-Agent: redsonic
CONNECTION: close

body

<?xml version="1.0" encoding="utf-8"?><root xmlns="urn:schemas-upnp-org:device-1-0">  <specVersion> <major>1</major>  <minor>0</minor> </specVersion>  <device> <deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>  <presentationURL>/</presentationURL>  <friendlyName>客厅电视</friendlyName>  <manufacturer>XXXX</manufacturer>  <manufacturerURL>http://www.xxx.com</manufacturerURL>  <modelDescription>xxx Media Render</modelDescription>  <modelName>xxxxx</modelName>  <modelURL>http://www.xxx.com</modelURL>  <UDN>uuid:F7CA5454-3F48-4390-8009-dce3a07b5e48</UDN>  <UID>-1254112285</UID>  <serviceList> <service> <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType><serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>  <SCPDURL>/dlna/Render/AVTransport_scpd.xml</SCPDURL>  <controlURL>_urn:schemas-upnp-org:service:AVTransport_control</controlURL><eventSubURL>_urn:schemas-upnp-org:service:AVTransport_event</eventSubURL></service>  <service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType><serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>  <SCPDURL>/dlna/Render/ConnectionManager_scpd.xml</SCPDURL><controlURL>_urn:schemas-upnp-org:service:ConnectionManager_control</controlURL><eventSubURL>_urn:schemas-upnp-org:service:ConnectionManager_event</eventSubURL></service>  <service> <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType><serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>  <SCPDURL>/dlna/Render/RenderingControl_scpd.xml</SCPDURL>  <controlURL>_urn:schemas-upnp-org:service:RenderingControl_control</controlURL><eventSubURL>_urn:schemas-upnp-org:service:RenderingControl_event</eventSubURL></service> </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.2.3: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.xx.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.2.3:49152/</URLBase> 
</root>

在长长的信息中,我们需要关注的有device标签下的deviceTypefriendlyNameUDN,其中friendlyName是设备的展示名,即给人看的名字,UDN是根据UUID生成的时间无关的设备失败码,其中包含了UUID,我们可以以此为设备id区分不同设备、处理设备的掉线和重连等。
紧接着,在serviceList中列出了设备提供的服务列表service,service标签下有serviceTypeserviceIdSCPDURLcontrolURLeventSubURL5个子标签,其中serviceType是判断设备提供的服务类型的依据,对于支持投屏的设备,一般有AVTransportRenderingControlConnectionManager三种服务,投屏过程中主要使用前两种,每个服务的支持的控制指令可以通过SCPDURL查看,或继续浏览下文用法;serviceId没啥好说的;SCPDURL为服务描述地址,请求会返回该服务的详细描述,包括服务支持的指令及其参数等,因为投屏使用的是比较标准的服务和指令,所以可以不需要请求服务的详细说明也能正常使用;controlURL是服务的控制地址,指令的发送就是往这个地址发的;eventSubURL是用来向目标设备订阅该服务相关的事件回调的,需要控制端运行一个ServerSocket监听tcp请求。

设备控制

前面已经提到,设备控制就是望设备的对应服务的controlURL发送HTTP请求,这里以POST方式为例,向TV的AVTransport服务发送SetAVTransportURI指令,作用是告诉TV需要播放的直播流的地址,内容如下:

head

POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1
Connection: close
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
Content-Type: text/xml;charset="utf-8"
Content-Length: 1464
Host: 192.168.2.3:49152
User-Agent: 

其中有两个参数用法说明一下(服务类型记即上面服务列表中服务的serviceType,控制地址为controlURL)

POST 控制地址 HTTP/1.1  
SOAPACTION: "服务类型#Action"

body

<?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://xxx.xxx.com/xxx.m3u8?bizid=xxx&amp;txSecret=fcexxxxxab4cf1b8bbee6efbe6668bd4&amp;txTime=5c3c5de1&amp;uid=0</CurrentURI>  <CurrentURIMetaData>&lt;DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sec="http://www.sec.co.kr/"&gt;&lt;item id="123" parentID="-1" restricted="1"&gt;&lt;upnp:storageMedium&gt;UNKNOWN&lt;/upnp:storageMedium&gt;&lt;upnp:writeStatus&gt;UNKNOWN&lt;/upnp:writeStatus&gt;&lt;dc:title&gt;Video&lt;/dc:title&gt;&lt;dc:creator&gt;QGame&lt;/dc:creator&gt;&lt;upnp:class&gt;object.item.videoItem&lt;/upnp:class&gt;&lt;res protocolInfo="http-get:*:video/*:DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000"&gt;http://xxx.xxx.com/xxx.m3u8?bizid=xxx&amp;amp;txSecret=fcexxxxxab4cf1b8bbee6efbe6668bd4&amp;amp;txTime=5c3c5de1&amp;amp;uid=0&lt;/res&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</CurrentURIMetaData> </u:SetAVTransportURI> </s:Body>
</s:Envelope>

看起来一堆东西,其实大部分都是固定格式,只有其中的少部分参数需要说明一下(Action为服务提供的Action,可以从服务描述地址获取详细说明,下文也有常用Action的列表)

<u:Action xmlns:u="服务类型"><参数名1>参数值1</参数名1><参数名2>参数值2</参数名2>...
</u:Action>

元数据中包含了协议的相关数据,没有特殊需求的话,套用常用的元数据内容即可,值得注意的是,元数据中res标签包含了转义过的视频地址(反转义一下就明显看出来了),而元数据也经过转义才放到body中的CurrentURIMetaData标签下,也就是说元数据中的视频URL经过了两次转义,&将转义为&amp;amp;,该转义为常用的xml转义,同样在处理upnp回包时,也要留意upnp需要转义的情况,具体规则为

原字符转义字符
&&amp;
"&quot;
<&lt;
>&gt;
空格&nbsp;
&apos;

转义这里不可以偷懒,以免造成兼容性问题,在已测的设备中,绝大多数电视取视频URL都是使用CurrentURI标签提供的URL,而三星电视则是从CurrentURIMetaData标签取得视频URL,如果元数据设置不对的话,很可能导致三星这种电视无法正常播放。

播放过程中不同的控制Action都是类似的,前后都是固定格式,稍作调整就成了另外一个控制Action,如PlayAction

head

POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1
Connection: close
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"
Content-Type: text/xml;charset="utf-8"
Content-Length: 327
Host: 192.168.2.3:49152
User-Agent: 

body

<?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的参数参考所属服务的详细描述,这里列出常用Action及其对应的服务和参数

ActionService参数常用值说明
SetAVTransportURIAVTransportInstanceID、CurrentURI、CurrentURIMetaData0、转义的视频地址、见上文设置视频地址
PlayAVTransportInstanceID、Speed0、1播放
PauseAVTransportInstanceID0暂停
StopAVTransportInstanceID0停止
SeekAVTransportInstanceID、Unit、Target0、见备注、见备注跳转[1]
SetMuteRenderingControlInstanceID、Channel、DesiredMute0、Master、1/0静音/取消[2]
SetVolumeRenderingControlInstanceID、Channel、DesiredVolume0、Master、0-100设置音量
GetVolumeRenderingControlInstanceID、Channel0、Master获取音量
GetCurrentTransportActionsAVTransportInstanceID0获取Action列表[3]
GetMediaInfoAVTransportInstanceID0获取媒体信息
GetPositionInfoAVTransportinstanceID0获取进度
GetTransportInfoAVTransportinstanceID0获取传输状态[4]

[1] UnitTRACK_NR 、ABS_TIME 、ABS_COUNT 、REL_COUNT 、CHANNEL_FREQ 、TAPE_INDEXFRAME 7种取值,参见微软定义,这里使用REL_TIME。 Target格式因Unit而定,如果Unit=REL_TIME,则格式为 “00:11:26”,表示跳转到某个进度,如果Unit=TRACK_NR,则格式为一个整数i,跳转到第i个视频(应该是的,未做验证)。
[2] DesiredMute设置为1表示静音,0表示取消静音。
[3] GetCurrentTransportActions返回结果不太准。
[4] GetTransportInfo可以获取当前传输状态,如STOPPED、PLAYING等,但也不准确,有的设备已经用遥控器停止播放了,获取到的还是PLAYING。
Action的成功与否主要通过POST请求返回的状态码判断,如果是 200 OK,那应该就是成功的了,大多数Action回包内容都非常简单,没有需要处理的返回值,部分Action如GetVolume具有返回值,需要解析回包。如果失败,根据状态码(如500等)及body中说明的错误信息定位问题,对比可以正常投屏的其它应用的请求内容,分析问题原因。

事件处理

当控制端通过SetAVTransportURIPlay让目标设备开始播放视频时,设备会进行加载缓冲,并开始播放,或者用户通过遥控器暂停/继续播放,甚至其它控制端抢占了TV等,都是我们需要关心的事件,以便控制端进行状态处理。获取目标设备的状态变化有两种方式,

  1. 轮询GetTransportInfoGetMediaInfo等Action,
  2. 向目标设备注册订阅。
    两种方式各有优缺点,因为目标设备实现存在差异,每个设备的在状态的处理上不完全一致。前面已经提到过,GetTransportInfo返回结果不太靠谱;第二种方式,结果较为准确,但是对控制端抢占TV导致更换URL等情况大多不会告知订阅者;所以结合两种方式,可以达到较好的效果。

事件订阅

订阅

设备描述的服务列表中,每个服务都有一个eventSubURL,我们可以在控制端运行一个ServerSocket绑定一个端口(记为端口A),通过accept监听tcp请求,并将本机ip和端口A和自定义回调路径拼接为url通过SUBSCRIBE Action发送给目标设备,即可完成订阅,在必要的时候,通过SUBSCRIBE Action(与订阅使用同一个Action,但参数不同)续订,通过UNSUBSCRIBE Action取消订阅。
参数格式如下表:
| Action | 参数 | 常用值 | 说明 |
| ----------- | :-------------------: | :---------------------------------------: | :------: |
| SUBSCRIBE | Nt、Timeout、Callback | upnp:event、Second-时间、<自定义回调地址> | 订阅[1] |
| SUBSCRIBE | SID、Timeout | 订阅ID、Second-时间 | 续订[2] |
| UNSUBSCRIBE | SID | 订阅ID | 取消订阅 |
[1] Callback的值URL用<>包裹。
[2] SID为SUBSCRIBE ID,是订阅Action返回的值。

事件订阅说白了就是给某个服务的 订阅 URL<eventSubURL> 发送一条包含 回调 URL<Callback URL> 和 订阅期限 <duration> 的订阅请求。订阅地址如下的<eventSubURL>标签

<service><serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType><serviceId>urn:upnp-org:serviceId:AVTransport</serviceId><controlURL>/dev/88024158-a0e8-2dd5-ffff-ffffc7831a22/svc/upnp-org/AVTransport/action</controlURL><eventSubURL>/dev/88024158-a0e8-2dd5-ffff-ffffc7831a22/svc/upnp-org/AVTransport/event</eventSubURL><SCPDURL>/dev/88024158-a0e8-2dd5-ffff-ffffc7831a22/svc/upnp-org/AVTransport/desc.xml</SCPDURL>
</service>

上述服务的订阅请求如下,其中注意点就是 回调URL CALLBACK 必须带有 <> 否则回调不成功。为了接受回调还需要手机上运行一个 HTTP Server,具体实现请看下一部分。

SUBSCRIBE /dev/88024158-a0e8-2dd5-ffff-ffffc7831a22/svc/upnp-org/AVTransport/event HTTP/1.1
HOST: 192.168.1.243:46201
USER-AGENT: iOS/9.2.1 UPnP/1.1 SCDLNA/1.0
CALLBACK: <http://192.168.1.100:5000/dlna/callback>
NT: upnp:event
TIMEOUT: Second-3600    // 订阅期限
成功响应

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

HTTP/1.1 200 OK
Server: Linux/3.10.33 UPnP/1.0 IQIYIDLNA/iqiyidlna/NewDLNA/1.0
SID: uuid:f392-a153-571c-e10b
Content-Type: text/html; charset="utf-8"
TIMEOUT: Second-3600
Date: Thu, 03 Mar 2016 19:01:42 GMT
订阅失败

若订阅失败,发布者必须返回一个订阅失败响应。格式如下:

HTTP/1.1 error code errordescrioption
Server: OS/Version UPnP/1.1 product/version
SID: uuid:subscibe-UUID
Content-Length: 0
Date: Thu, 03 Mar 2016 19:01:42 GMT

续订

如果需要续订某个服务,则必须在订阅期限过期前,将续订消息发往服务器进行续订。

SUBSCRIBE /dev/88024158-a0e8-2dd5-ffff-ffffc7831a22/svc/upnp-org/AVTransport/event HTTP/1.1
HOST: 192.168.1.243:46201
SID: uuid:subscibe-UUID
TIMEOUT: Second-3600    // 订阅期限

取消订阅

UNSUBSCRIBE /dev/88024158-a0e8-2dd5-ffff-ffffc7831a22/svc/upnp-org/AVTransport/event HTTP/1.1
HOST: 192.168.1.243:46201
SID: uuid:subscibe-UUID

回调

当服务器上的状态变量发生变数时,通过单播给订阅者发送通知。单播通过 HTTP 协议发送。需要在本地运行一个 HTTP Server 来接受请求。接收事件消息成功后,只需要简单返回一个 HTTP/1.1 200 OK 作为回应即刻。

坑:有些设备返回的xml中 < > 被转义,导致解析时候出错。所以需要先反转义,然后再解析。
单播消息格式如下

NOTIFY /dlna/callback HTTP/1.0
Host: 192.168.1.100:5000
Content-Length: 325
Content-Type: text/xml; charset="utf-8"
User-Agent: Neptune/1.1.3, 6
SID: uuid:ac6dce5a-6047-7862-fd41-e5596960f57a  // 订阅标识符
NTS: upnp:propchange                            // GENA规定,必须是 upnp:propchange 
NT: upnp:event                                  // GENA规定,必须是 upnp:event 
SEQ: 4                                          // 事件编号,初始值为0。<?xml version="1.0" encoding="UTF-8"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"><e:property><!-- 消息内容 --><variableName>new values</variableName></e:property>
</e:propertyset>
播放消息

忽略请求头

<?xml version="1.0" encoding="UTF-8"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"><e:property><LastChange><Event xmlns="urn:schemas-upnp-org:metadata-1-0/AVT/"><InstanceID val="0"><TransportState val="PLAYING"/></InstanceID></Event></LastChange></e:property>
</e:propertyset>
停止播放消息

忽略请求头

<?xml version="1.0" encoding="UTF-8"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"><e:property><LastChange><Event xmlns="urn:schemas-upnp-org:metadata-1-0/AVT/"><InstanceID val="0"><TransportState val="STOPPED"/></InstanceID></Event></LastChange></e:property>
</e:propertyset>

主要看TransportState中的val属性

TRANSITIONING=正在传输
PLAYING=播放
PAUSED_PLAYBACK=暂停播放
STOPPED=停止播放

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/234502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】数据库基础入门 安装MySQL

目录 介绍&#xff1a; 安装MySQL: 设置 root 账号密码 2.配置环境变量 2.找到 Path 系统变量, 点击 "编辑" 介绍&#xff1a; MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是一种用于管理和存储数据的软件。 安装MySQL: …

MyBatis进行CRUD中添加数据实现主键回填

文章目录 MyBatis进行CRUD中添加数据实现主键回填1、创建一个mybatis项目2、实现添加数据时主键回填在MyBatisTest.java中添加下面方法在UserMapper.java中添加对应的属性在UserMapper.xml中添加sql语句如下运行结果如下(取消commit方法注释后就不会出现Rolling back回滚进行真…

JRT打印元素绘制协议整合PDF

打印不光要能打印内部的单据&#xff0c;对于检验的打印还有外送回传的PDF报告也需要能够打印&#xff0c;所以需要把打印PDF文件整合进来&#xff0c;为此给打印元素绘制协议增加PDF类型的元素。 定义如下&#xff0c;由绘制协议按地址下载文件后和其他打印元素整合&#xff…

107基于matlab的模糊推理系统(ANFIS)的时间序列预测

基于matlab的模糊推理系统&#xff08;ANFIS&#xff09;的时间序列预测&#xff0c;输出训练集、测试集和预测数据结果&#xff0c;数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 107 时间序列预测模糊推理系统 (xiaohongshu.com)

[ZJCTF 2019]NiZhuanSiWei1

[ZJCTF 2019]NiZhuanSiWei1 预测试 打开网页就是代码&#xff1a; <?php $text $_GET["text"]; $file $_GET["file"]; $password $_GET["password"]; if(isset($text)&&(file_get_contents($text,r)"welcome to the zjct…

【论文笔记】MCANet: Medical Image Segmentation withMulti-Scale Cross-Axis Attention

医疗图像分割任务中&#xff0c;捕获多尺度信息、构建长期依赖对分割结果有非常大的影响。该论文提出了 Multi-scale Cross-axis Attention&#xff08;MCA&#xff09;模块&#xff0c;融合了多尺度特征&#xff0c;并使用Attention提取全局上下文信息。 论文地址&#xff1a…

蔚来打败“蔚来”

作者 | 魏启扬 来源 | 洞见新研社 继2019年后&#xff0c;又一次深陷倒闭传闻的蔚来汽车&#xff0c;“在关键时刻找到钱了”。 12月18日&#xff0c;蔚来汽车宣布&#xff0c;与阿布扎比投资机构CYVN Holdings签订新一轮股份认购协议&#xff0c;CYVN Holdings将通过其附属公…

四色问题(图论)python

四色问题是一种著名的图论问题&#xff0c;它要求在给定的地图上给每个区域着一种颜色&#xff0c;使得相邻的区域颜色不同&#xff0c;而只使用四种颜色。这个问题可以通过图的着色来解决&#xff0c;其中图的节点表示区域&#xff0c;边表示相邻的关系。 在 Python 中&#…

excel导出,post还是get请求?

1&#xff0c;前提 今天在解决excel导出的bug时&#xff0c;因为导出接口查询参数较多&#xff0c;所以把原来的get请求接口修改为post请求 原代码&#xff1a; 修改后&#xff1a; 2&#xff0c;修改后 postman请求正常&#xff0c;然后让前端对接口进行同步修改&#xff0…

Clion自定义管理和配置软件构建过程的工具(代替CMake)构建程序

在公司由于需要x86环境和其他arm环境&#xff0c;同时需要使用公司自定义的mine_x86或者mine_orin对代码进行编译。 编译命令如下mine_x86 build -Dlocal1 -j8,为使用Clion对程序进行调试&#xff0c;需要对程序进行设置。方便调试代码时能够断点查看变量。尝试了很多次&#…

@WebMethod 这个注解的作用

WebMethod 注解是 Java 中 JAX-WS&#xff08;Java API for XML Web Services&#xff09;的一部分&#xff0c;用于将一个特定的方法标记为 Web 服务操作。当你在类方法上使用 WebMethod 注解时&#xff0c;这表明该方法是一个对外暴露的 Web 服务方法&#xff0c;即这个方法可…

pytorch实现DCP暗通道先验去雾算法及其onnx导出

pytorch实现DCP暗通道先验去雾算法及其onnx导出 简介实现ONNX导出导出测试 简介 最近在做图像去雾&#xff0c;于是在Pytorch上复现了一下dcp算法。暗通道先验去雾算法是大神何恺明2009年发表在CVPR上的一篇论文&#xff0c;还获得了当年的CVPR最佳论文。 实现 具体原理就不…

计算机组成原理综合1

1、完整的计算机系统应包括______。D A. 运算器、存储器和控制器 B. 外部设备和主机 C. 主机和实用程序 D. 配套的硬件设备和软件系统 2、计算机系统中的存储器系统是指______。D A. RAM存储器 B. ROM存储器 C. 主存储器 …

安捷伦Agilent 34970A数据采集

易学易用 从34972A简化的配置到内置的图形Web界面&#xff0c;我们都投入了非常多的时间和精力&#xff0c;以帮助您节约宝贵的时间。一些非常简单的东西,例如模块上螺旋型端子连接器内置热电偶参考结、包括众多实例和提示的完整用户文档&#xff0c;以及使您能够在开机数分钟后…

接口测试和测试用例分析

只要有软件产品的公司百分之九十以上都会做接口测试&#xff0c;要做接口测试的公司那是少不了接口测试工程师的&#xff0c;接口测试工程师相对于其他的职位又比较轻松并且容易胜任。如果你想从事接口测试的工作那就少不了对接口进行分析&#xff0c;同时也会对测试用例进行研…

node.js mongoose middleware

目录 官方文档 简介 定义模型 注册中间件 创建doc实例&#xff0c;并进行增删改查 方法名和注册的中间件名相匹配 执行结果 分析 错误处理中间件 手动抛出错误 注意点 官方文档 Mongoose v8.0.3: Middleware 简介 在mongoose中&#xff0c;中间件是一种允许在执…

DDD领域驱动设计(二)

软件系统复杂性的应对 解决复杂和大规模软件的武器可以粗略的归位三种:抽象 分治和知识 抽象: 使用抽象能够精简问题空间&#xff0c;而且问题越小越容易理解。比如你去一个地方 一开始的时候并不需要确定用什么方式到达。分治: 类似算法里面的dp用的就是分治的想法。分割后的…

破局新渠道|2023年热度全域达人分销生态大会回顾

12月7日&#xff0c;由热度电商、热度云、集脉新电商联合举办的「破局新渠道」热度全域达人分销生态大会暨热度云3.0发布会在杭州国际博览中心圆满收官。大会邀请了平台官方、电商协会、品牌方、业务操盘手、数据专家、团长机构、达人等达人分销生态中的多个角色&#xff0c;从…

Python办公—pandas读取Excel表格增加列、两列保持一致、依条件修改单元格内容(附代码)

目录 专栏导读背景插入一列插入多列依条件修改单元格内容(2个条件以内)依条件修改单元格内容(3个条件以上)两列保持一致结尾 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&#x1f308; 博客主页&am…

2023年中国数据智能管理峰会(DAMS上海站2023)-核心PPT资料下载

一、峰会简介 数据已经成为企业的核心竞争力&#xff01;谁掌控数据、更好的利用数据、实现资产化&#xff0c;谁就会真正率先进入大数据时代。 1、数据智能管理趋势和挑战 在峰会上&#xff0c;与会者讨论了数据智能管理的最新趋势和挑战。随着数据量的不断增加&#xff0c…