天天看點

DLNA投屏簡介DLNA投屏簡介

DLNA投屏簡介

本文關于DLNA的簡介隻限于控制端的實作,未涉及接收端和服務端。

文章目錄

  • DLNA投屏簡介
    • 常見投屏方案
      • DLNA
      • AirPlay
      • Miracast
    • 常用名詞解釋
    • DLNA投屏流程
      • 裝置發現
      • 裝置控制
      • 事件處理
        • 輪詢
        • 事件訂閱
      • 遇到的問題

最近項目中提了一個投屏的需求,之前完全沒有接觸過這一塊,經過一段時間的努力對DLNA有了初步了解,并順利實作了業務需求,現将自己淺顯的了解記錄一下,希望能幫助對此有興趣、有需要的同學更快速的了解其原理,本菜才疏學淺,見識不多,纰漏之處還請指正。

為什麼自己實作一套,而不是使用第三方庫(Android平台)?

其實開始使用Cling做了一下,第三方庫大而全,雖然相容性一般不錯,卻難以很友善地維護,有時候牽一發而動全身,不花大量時間分析實作邏輯難以對其改造,最嚴重的是引入Cling會讓應用增加1.2M左右,而自己實作隻有100k(aar),輕巧易維護,适合自己的項目使用。

常見投屏方案

常見的投屏方案主要有以下幾種:

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使用廣泛,在主流的電視、智能機頂盒中都有支援,而且終端工作量小,是不錯的方案。

常用名詞解釋

概念是很無聊的,容易讓人失去興趣,略作了解即可,無需深究。

  • DMS

    Digital Media Server 數位媒體伺服器: 提供了媒體檔案的擷取、錄制、儲存以及作為源頭的裝置。我的了解,簡而言之約等于一個檔案伺服器。

  • DMR

    Digital Media Renderer 數字媒體喧染器: 主要的功能是用來播放由DMC從DMS中所指定的數字媒體,例如:數字電視、智能機頂盒、電腦上實作了DLNA協定的視訊播放器。

  • DMP

    Digital Media Player 數位媒體播放器: 可尋找并播放或輸出任何由DMS所提供的媒體檔案的裝置。DMR與DMP的差別在于DMR隻有接受媒體和播放功能,而沒查找有浏覽媒體的功能。比如顯示器、音箱等。

  • 對于移動裝置還有M-DMS、M-DMR、M-DMP等
  • DMC

    Digital Media Controller 數字媒體控制器,查找DMS的内容并建立DMS與DMR之間的連接配接并控制媒體的播放。

  • ControlPoint 控制點,這個概念在不少實作中都有,但我認為這是一個虛拟的稱謂,即對控制子產品的稱謂,資料顯示控制點是對控制Action發出者的稱呼,誰能發出誰就是CP,我的實作中将每個裝置抽象為一個Device,MRDevice繼承Device,并提供對應的控制接口,沒有直接用到這個看起來很合理卻又容易把人繞暈的”控制點“概念。
  • UPnP

    通用即插即用協定,使用了SSDP(簡單裝置發現協定)和SOAP(簡單對象通路協定)等幾個協定。可以說DLNA很大程度上是基于UPnP的。

DLNA投屏流程

投屏的過程主要分為 裝置發現 和 裝置控制 兩個階段

裝置發現

裝置發現也分為兩步,第一步是擷取裝置基本描述,第二步是擷取裝置較長的描述。

第一步有兩種方式:主動發現和被動發現。

DLNA投屏簡介DLNA投屏簡介
  • 主動發現裝置

    主動發現是指裝置主動通過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:all

upnp:rootdevice

uuid:device-某UUID

urn: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

,詳細資訊就不愁啦;還有兩個需要關注的值:

NT

NTS

,前者是Notify Type(與Search中的ST類似),後者表示NT的子類型,其值隻可以是

ssdp:alive

ssdp: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
           

Head中沒有什麼需要特别關心的資訊,重點看body内容

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

标簽下的

deviceType

friendlyName

UDN

,其中friendlyName是裝置的展示名,即給人看的名字,UDN是根據UUID生成的時間無關的裝置失敗碼,其中包含了UUID,我們可以以此為裝置id區分不同裝置、處理裝置的掉線和重連等。

緊接着,在

serviceList

中列出了裝置提供的服務清單

service

,service标簽下有

serviceType

serviceId

SCPDURL

controlURL

eventSubURL

5個子标簽,其中serviceType是判斷裝置提供的服務類型的依據,對于支援投屏的裝置,一般有

AVTransport

RenderingControl

ConnectionManager

三種服務,投屏過程中主要使用前兩種,每個服務的支援的控制指令可以通過SCPDURL檢視,或繼續浏覽下文用法;serviceId沒啥好說的;SCPDURL為服務描述位址,請求會傳回該服務的較長的描述,包括服務支援的指令及其參數等,因為投屏使用的是比較标準的服務和指令,是以可以不需要請求服務的詳細說明也能正常使用;controlURL是服務的控制位址,指令的發送就是往這個位址發的;eventSubURL是用來向目标裝置訂閱該服務相關的事件回調的,需要控制端運作一個ServerSocket監聽tcp請求。

裝置控制

DLNA投屏簡介DLNA投屏簡介

前面已經提到,裝置控制就是望裝置的對應服務的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;

空格

&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及其對應的服務和參數

Action Service 參數 常用值 說明
SetAVTransportURI AVTransport InstanceID、CurrentURI、CurrentURIMetaData 0、轉義的視訊位址、見上文 設定視訊位址
Play AVTransport InstanceID、Speed 0、1 播放
Pause AVTransport InstanceID 暫停
Stop AVTransport InstanceID 停止
Seek AVTransport InstanceID、Unit、Target 0、見備注、見備注 跳轉[1]
SetMute RenderingControl InstanceID、Channel、DesiredMute 0、Master、1/0 靜音/取消[2]
SetVolume RenderingControl InstanceID、Channel、DesiredVolume 0、Master、0-100 設定音量
GetVolume RenderingControl InstanceID、Channel 0、Master 擷取音量
GetCurrentTransportActions AVTransport InstanceID 擷取Action清單[3]
GetMediaInfo AVTransport InstanceID 擷取媒體資訊
GetPositionInfo AVTransport instanceID 擷取進度
GetTransportInfo AVTransport instanceID 擷取傳輸狀态[4]

[1]

Unit

TRACK_NR

ABS_TIME

ABS_COUNT

REL_COUNT

CHANNEL_FREQ

TAPE_INDEX

FRAME

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中說明的錯誤資訊定位問題,對比可以正常投屏的其它應用的請求内容,分析問題原因。

事件處理

當控制端通過

SetAVTransportURI

Play

讓目标裝置開始播放視訊時,裝置會進行加載緩沖,并開始播放,或者使用者通過遙控器暫停/繼續播放,甚至其它控制端搶占了TV等,都是我們需要關心的事件,以便控制端進行狀态處理。擷取目标裝置的狀态變化有兩種方式,

  1. 輪詢

    GetTransportInfo

    GetMediaInfo

    等Action,
  2. 向目标裝置注冊訂閱。

    兩種方式各有優缺點,因為目标裝置實作存在差異,每個裝置的在狀态的處理上不完全一緻。前面已經提到過,GetTransportInfo傳回結果不太靠譜;第二種方式,結果較為準确,但是對控制端搶占TV導緻更換URL等情況大多不會告知訂閱者;是以結合兩種方式,可以達到較好的效果。

輪詢

沒啥好說的,不端發送擷取想要狀态資訊的請求就行了。

事件訂閱

DLNA投屏簡介DLNA投屏簡介
  • 訂閱

    裝置描述的服務清單中,每個服務都有一個

    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傳回的值。
  • 回調處理

    ServerSocket接收Socket連結,并讀取回調内容,後面過程與處理Action回包類似,解析upnp内容即可(upnp是XML子集),其中需要關注的隻有lastchange标簽裡的内容,以播放事件為例,播放事件的LastChange标簽内容為:

<Event xmlns = "urn:schemas-upnp-org:metadata-1-0/AVT/">
<InstanceID val="0">
    <TransportState val="PLAYING"/>
    <TransportStatus val="OK"/>
</InstanceID>
</Event>
           

遇到的問題

在實作的過程中遇到了一系列的問題,解決了一部分。

  • 應用收不到多點傳播

    某些ROM定制的廠商不接收UDP多點傳播包(可能是為了省電),為了保險起見,需要在開始接收多點傳播之前如下設定

WifiManager.MulticastLock multicastLock = null;
 WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wm != null) {
    multicastLock = wm.createMulticastLock(TAG);
    multicastLock.setReferenceCounted(true);
    multicastLock.acquire();
}
           

結束的時候調用

  • Sony電視投屏失敗(業務層)

    測試相容性的時候發現,Sony電視在開機之後我們的應用無法投屏,但是在使用騰訊視訊等其它應用投屏一次之後,我們就可以正常投屏了。我們在業務層使用的投屏政策是投屏之前先

    Stop

    一下,然後

    SetAVTransportURI

    ,再

    Play

    ,常用的三步走政策,每個目标裝置實作都存在差異,可能有一些裝置不Stop就無法播放新的内容,也有的電視SetAVTransportURI就開始播放了,而有的需要Play才會開始播放,三步走可以保證目标裝置可以正常投屏。調試發現,Sony電視在開機之後,未投屏之前直接Stop會傳回失敗(500錯誤碼),一旦開始投屏之後,無論是否正在投屏,Stop都會正常傳回。當時業務邏輯寫的Stop成功才會繼續後續兩步,Stop失敗就導緻了整個流程中斷,後面改為Stop失敗也繼續往下走,問題解決。
  • 視訊流相容問題
    • LG

      直播使用的是沒m3u8的流,LG電視無法播放我們的m3u8流,後做了失敗重試邏輯,可以自動/手動切換flv流,可以成功投屏(但flv也有一定的失敗率)。

    • 創維企鵝盒子1v

      這個盒子使用flv播放一切正常,但是使用m3u8會crash(盒子的DLNA服務crash并重新開機服務),是以在業務層做了裝置更新管理。當檢測到有相同uuid的裝置出現在區域網路,但ip或端口發生變化時,認為該裝置發生了變化(重新開機或重連網絡等),裝置管理器(自己實作的負責維護裝置狀态的類)會移除原來的裝置并添加新裝置,業務層會收到對應事件,并判斷如果該裝置是已選中的正在投屏的裝置,會自動重試(切流重試),此類問題得到解決。另外,在大概2018年11月的一次更新(盒子投屏版本4.01.41)已經修複了該問題。

  • 三星電視(流校驗問題)

    一般電視等支援投屏的裝置在從

    SetAVTransportURI

    拿到視訊流的URL之後就直接傳回200 OK,并開始加載和播放了,但是三星電視在SetAVTransportURI傳回之前會向流位址發送一個Head請求:
HEAD 流Path HTTP/1.1
Host: 流Host
getcontentFeatures.dlna.org: 1
getCaptionInfo.sec: 1
           

流位址需要回包:

HTTP/1.1 200 OK
Server: httpserver
Content-Length: 204698197
Accept-Ranges: bytes
Connection: Keep-Alive
Keep-Alive: timeout=60, max=100
Content-Type: video/mp4
Cache-Control: max-age=7200
Client-Ip: 59.37.125.xxx
X-ServerIp: 113.106.207.xxx
X-RespTime: 7/Mon/2019:11:26:00 +0800
E-tag: 9ea6259368108444e72082ef7db728b88b8xxxxxx
X-Cache-Lookup: 2-225908140
X-Cache-UUID: 6dc6696f-c936-4b7c-bd63-cd5d5f46f4ae
           

三星電視才會接受位址并開始播放,否則向控制端傳回500錯誤碼。是以聯合背景申請了支援Head請求的新位址才之後,三星電視可以正常投屏了,但尚存在小部分三星依然投不了。

裝置描述中,有一個

presentationURL

是通過web頁面控制目标裝置的UI,UI目标裝置自己實作的,但很多裝置并未提供。如果希望能夠讓電視播放手機中的檔案,需要實作DMS的功能(可以借助NanoHTTPD)。DLNA還有很多深層次的東西值得探索,限于時間精力能力的限制,适可而止了。

至此,簡易DLNA投屏的實作方法已經梳理完畢,如有疑問或糾錯,歡迎留言,謝謝。

繼續閱讀