天天看點

ushare代碼分析

轉載自:http://blog.csdn.net/lickylin/article/details/19713317 

1、upnp 介紹及工作過程

1.1 upnp介紹

    UPnP的全稱是“Universal Plug and Play”,是PnP(Device Plug and Play)的擴充,可以發現和控制各種網絡裝置,并能提供相應的服務,如網絡列印機,Internet網關等。但UPnP不僅僅是即插即用的簡單擴充,它支援“0配置”和無線網絡,可以自動發現其它供應商提供的裝置。在UPnP協定下,一個裝置可以動态的加入網絡、獲得IP位址、廣播它的功能、并了解其他裝置的功能。很多種類的裝置都可以使用UPnP協定,包括智能裝置,無線裝置。UPnP使用标準的TCP/IP和Internet協定,使其能夠很好的在現存網絡中使用,使用這些标準協定使得UPnP可以從已有的各種經驗和知識中獲利,打破了各種資訊孤島,越過各種實體層,可以是有線的,也可以是無線的,使得裝置間的互相協作成為一個基本的特點。

Upnp實用internet上的多種标準:IP、TCP、UDP、HTTP及XML作為裝置之間通信的協定。其中XML是upnp的核心部分,被用在裝置與服務描述、控制資訊與事件進行中。

    Upnp裝置的工作過程包括裝置尋址、裝置發現、裝置描述、裝置控制、事件通知、裝置表征6個部分。控制點至少應該包括發現、描述、指令轉換等。裝置尋址是當裝置加入網絡後,通過DHCP伺服器或者Auto IP獲得唯一的IP位址。

1.2 upnp工作過程

Upnp的工作過程包括:裝置尋址、裝置發現、裝置描述、裝置控制、事件通知、裝置表征。

ushare代碼分析

  0. 控制點跟裝置都先取得IP位址才能做之後的溝通。

  1. 控制點發送一個多點傳播m-http尋找整個網絡上的UPnP裝置,而裝置也可宣告他本身的存在。

  2. 控制點取得裝置與服務的描述,這包括裝置提供什麼樣的服務。

  3. 根據裝置提供的服務,控制點發出動作資訊 (對裝置操作的指令資訊)給裝置。

  4. 控制點監聽裝置的狀态,當狀态改變時做出相應的處理動作。

  5. 控制點利用HTML界面來控制裝置和監看裝置狀态。

2、upnp av的工作過程

    一個完整的upnp av體系由三部分組成:控制點(control point)、裝置(media server)、媒體播放器(media renderer)。

控制點提供使用者控制的界面,并協調伺服器與播放器之間的互相操作;媒體伺服器提供内容目錄、連接配接管理與内容傳輸等服務;播放器主要用來播放資料。下面是三者之間的聯系:

ushare代碼分析

                            (圖1 UPnP AV應用架構)

注意:從媒體伺服器向媒體播放器傳輸媒體流所用的協定是與控制點無關的。而且,媒體流也不經過 AV 控制點,AV 控制點隻負責連接配接的建立。

2.1 upnp av控制點

    AV控制點用來控制媒體伺服器與媒體播放器的操作,使使用者可以在一個指定的upnp播放裝置中播放指定upnp伺服器中的媒體檔案。在大多應用場合,upnp av控制點執行的操作有:

①發現網絡中存在的媒體伺服器與媒體播放器,即裝置發現

②列出使用者可以操作的媒體檔案,即内容浏覽

③查詢媒體伺服器與媒體播放器,找出所選媒體檔案的傳輸協定與媒體格式,協商媒體伺服器與媒體播放器都支援的傳輸協定與媒體格式

④調整播放器的屬性,如音量、螢幕亮度等,即播放器屬性控制

控制點通過調用媒體伺服器與媒體播放器的upnp av服務來完成上述操作,進而根據使用者的需求完成相應的操作。

2.2 媒體伺服器

    媒體伺服器用來管理要共享的媒體檔案,并将内容傳輸給媒體播放器進行播放。

媒體伺服器提供的服務有:

内容目錄服務:控制點通過内容目錄服務可以浏覽、查找共享的媒體資料檔案;

連接配接管理服務:連接配接管理服務提供媒體伺服器所支援的資料傳輸協定與媒體資料格式;

傳輸服務(可選):當支援的傳輸協定是http-get時,由媒體播放器執行av傳輸服務,提供資料流的控制,當支援的傳輸協定是http-post時,由媒體伺服器執行av傳輸服務,提供資料流的控制。Ushare使用的傳輸協定是http-get,是以在其代碼中沒有實作av傳輸服務。

2.3 媒體播放器

    媒體播放器用來接收媒體伺服器所共享的媒體資料,并在本地播放,媒體播放器包括電視、音響、電子圖檔顯示器等。

媒體播放器提供的upnp av服務有:

播放控制:控制點通過播放控制服務控制如何播放媒體檔案(音量調節、螢幕亮度調節等);

連接配接控制:連接配接管理服務與媒體伺服器相似,提供媒體播放器所支援的媒體傳輸協定與媒體資料格式;

傳輸服務:當其支援的媒體資料傳輸協定是http-get時,則必須要有傳輸服務,當支援的媒體資料傳輸協定是http-post時,則由媒體伺服器來實作傳輸服務,媒體播放器可以不提供傳輸服務。

3、媒體伺服器ushare的工作架構

3.1 相關的結構體

對于媒體伺服器ushare,其工作過程如下:

Ushare有關的結構體:

struct ushare_t {
  char *name;
  char *interface;//eth0、eth1
  char *model_name;
  content_list *contentlist;//共享内容目錄
  struct rbtree *rb;//平衡二叉樹
  struct upnp_entry_t *root_entry;
  int nr_entries;//
  int starting_id;//
  int init;
  UpnpDevice_Handle dev;//傳遞給upnp的handler
  char *udn;
  char *ip;//ip位址
  unsigned short port;
  unsigned short telnet_port;
  struct buffer_t *presentation;
  bool use_presentation;
  bool use_telnet;
#ifdef HAVE_DLNA
  bool dlna_enabled;
  dlna_t *dlna;
  dlna_org_flags_t dlna_flags;
#endif 
  bool xbox360;
  bool verbose;
  bool daemon;
  bool override_iconv_err;
  char *cfg_file;
  pthread_mutex_t termination_mutex;
  pthread_cond_t termination_cond;
};
           

該結構體定義了upnp裝置的名稱、接口(eth0、eth1)、配置檔案指針等,該結構體包含ushare工作所需的大部分變量。

struct upnp_entry_t {
  int id;
  char *fullpath;  //内容目錄
#ifdef HAVE_DLNA
  dlna_profile_t *dlna_profile;
#endif 
  struct upnp_entry_t *parent;
  int child_count;//判斷是檔案還是檔案夾(0:檔案夾 -1:檔案)
  struct upnp_entry_t **childs;
  struct mime_type_t *mime_type;//媒體資料格式與協定資料
  char *title;//内容目錄的最後一個'/'後面的内容
  char *url;//id.ext example:32768.avi
  off_t size;
  int fd;//
};
           

該結構體主要用于内容目錄有關的函數中.

struct mime_type_t {
  char *extension;//資料格式 ex:wmv、rmvb、mp3、mp4等
  char *mime_class;
  char *mime_protocol;
};
           

該結構體主要用于媒體格式與資料傳輸協定相關的函數中.

3.2 ushare代碼架構

Ushare程式的開發流程與upnp官方文檔中所規定的基本步驟是一緻的,

①首先是調用upnp庫接口函數init_upnp(),通過upnp庫的接口函數UpnpInit()初始化一個upnp,并調用函數UpnpSetVirtualDirCallbacks注冊http協定有關的回調函數get_info、open、read、write、close,這些函數用于媒體播放器與媒體伺服器建立連接配接與進行資料的傳輸

②然後調用upnp庫的接口函數UpnpRegisterRootDevice2()注冊ushare裝置的文檔資訊與事件響應回調函數device_callback_event_handler 。ushare注冊的裝置描述文檔用于控制點的裝置發現階段,當控制點發現ushare媒體伺服器後,就會得到ushare的描述文檔,該描述文檔描述了裝置的名稱和支援的服務資訊(ushare支援的服務:ConnectionManager 、ContentDirectory 、

X_MS_MediaReceiverRegistrar),控制點根據裝置所提供的服務,就可以向upnp裝置發送請求相關服務的事件(主要通過回調函數函數

device_callback_event_handler實作)

③ushare提供的ConnectionManager服務,主要是用來擷取ushare所支援的媒體資料格式與傳輸協定,控制點通過該服務擷取媒體伺服器和媒體播放器的連接配接管理服務來确定進行資料傳輸的協定與都支援的資料格式。

④ushare提供的ContentDirectory 服務,主要用來确定在媒體伺服器上共享的資源,該服務提供目錄浏覽與目錄搜素cds_browsecds_search(),媒體播放器獲得了ushare共享的資源的url後,就可以通過http協定來擷取相應資料了,這主要通過 UpnpSetVirtualDirCallbacks注冊的回調函數get_info、open、read、write、close來實作。  

下面是ushare程式總體的架構:

ushare代碼分析

                                                      (圖2 ushare main() 總體架構)

3.3 ushare的裝置描述文檔與服務描述文檔

Ushare是一個媒體伺服器,它需要設定自己的媒體描述文檔與服務描述文檔,以便在網絡上釋出或者被控制點在裝置發現階段獲得。控制點獲得了媒體伺服器的裝置描述文檔與服務描述文檔後,就可以通過事件請求向媒體伺服器發送指令請求,獲得媒體伺服器支援的服務。Ushare是在init_upnp()函數裡來将裝置描述文檔注冊到upnp中去的。

ushare代碼分析

(圖3 ushare注冊裝置描述文檔流程)

裝置描述文檔是以XML形式編寫的,下面是ushare的裝置描述文檔,

在ushare的描述裡,有ushare的名稱、編寫者、ushare所支援的服務,控制點獲得了ushare的裝置描述文檔後,就能了解ushare主要支援哪些服務了,如果控制點要獲得關于服務更詳細的内容,就需要去獲得關于服務的文檔資訊。主要元素有:

:包含所有描述根裝置的其它元素如:、、

  包括、訓示upnp裝置的主版本、副版本

包括裝置類型、名稱、制造者等

包括upnp裝置提供的所有服務清單

ushare代碼分析

Ushare所支援的服務的描述文檔中會列出動作清單action,動作清單用來表示ushare的服務所支援的動作。服務描述文檔的每個元素介紹如下:

 動作清單,用于定義動作;

  每一個動作的具體定義,包括動作名稱、

僅當為動作定義參數時要求,用于說明與一個動作有關的所有變量

     每一個變量的具體說明,包括變量名、、

 要求。無論變量是輸入還是輸出參數。必須采用in xorout 格式。

               任何 in變量必須列在任何 out 變量的前面。

     要求。必須是一個狀态變量的名稱。

 包含以下定義:

   sendEvents 屬性将定義當這一狀态變量的值

                                      發生變化時是否生成事件消息

     要求:狀态變量的名稱

     string、i4、i2、int等

 列舉合法的字元串值

   字元串變量的合法值

 為合法數值定義範圍,有、兩個子

                        元素

ushare代碼分析

當控制器獲得ushare服務的較長的描述文檔後,就獲得了ushare所支援的動作指令,然後可以根據ushare提供的事件響應回調函數device_callback_event_handler向ushare發送動作指令。

3.4 ushare提供的服務

Ushare支援的服務有ConnectionManager、ContentDirectory。控制點可以通過向ushare發出動作指令請求,來獲得ushare的相關服務,下面是ushare的動作響應過程:

ushare代碼分析

圖4 ushare的動作響應過程

當有一個來自控制點的動作請求後,回調函數device_callback_event_handler()就會調用handle_action_request()來進行處理,然後handle_action_request()就會調用find_service_action()來查找ushare程式是否支援該動作請求,若支援就會調用數組services->actions[i].function()執行請求動作的回應。

3.4.1 ConnectionManager服務及其響應函數

連接配接管理服務主要用來向控制點提供ushare所支援的傳輸協定與資料格式,以便控制點對伺服器與播放器之間的傳輸協定與資料格式進行比對,來确定伺服器與播放器之間傳輸時所使用的傳輸協定,下面是ConnectionManager服務所支援的動作響應函數。比較重要的服務是cms_get_protocol_info()

ushare代碼分析

Ushare支援的傳輸協定為http-get,支援的資料格式為avi、rmvb、mp3、mp4、wma、wmv等,下面是部分ushare支援的資料格式與傳輸協定定義:

ushare代碼分析

3.4.1.1 動作響應函數cms_get_protocol_info()

ushare代碼分析

圖5 cms_get_protocol_info

該函數的作用是擷取ushare所支援的傳輸協定與資料格式,該函數首先會周遊數組const struct mime_type_t MIME_Type_List[],并将ushare所支援的傳輸協定與資料格式傳遞給字元指針resptext,然後通過函數upnp_add_response()調用libupnp庫的接口函數UpnpAddToActionResponse()添加到動作回應中。Ushare支援的傳輸協定為http-get。

3.4.2 ContentDirectory服務及其響應函數

ContentDirectory服務中比較重要的兩個動作響應函數為cds_search、cds_browse

它們提供内容目錄的查找與浏覽功能。下面是該服務提供的全部動作響應函數:

ushare代碼分析

對于内容目錄服務,要用到以下結構體:

ushare代碼分析

該結構體中的fullpath存儲的是ushare共享目錄的位址;child_count用來判斷一個upnp_entry_t類型的變量指向的是一個共享目錄中的一個檔案還是一個檔案夾,0表示是一個檔案夾,-1表示是一個資料檔案;title指的是一個目錄位址的最後一個'/'後面的内容,如對于目錄/home/film,則title = film;url指的是id+媒體格式,如id.avi、id.rmvb等。

3.4.2.1 動作響應函數cds_browse()

控制點通過該函數來擷取ushare中共享目錄下的目錄清單或者其子目錄下的目錄清單,以便控制點來根據目錄清單請求響應的資料檔案。

ushare代碼分析

圖 6 cds_browse

該事件響應的是一個xml文檔,下面是它的一個示例:其中藍色是指浏覽的是一個檔案時所需要填充的内容,黃色是指浏覽的是一個檔案夾時需要填充的内容

    xmlns:dc="http://purl.org/dc/elements/1.1/" 

xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/">

upnp:class    dc:title

...

upnp:class    dc:title

3.4.2.2 動作響應函數cds_search

控制節點通過該函數來搜尋一個共享資料檔案夾下的所有檔案。該動作響應調用upnp_add_response()将一個xml形式的應答文檔傳送給sdk,該xml文檔的内容與3.4.2.1中的内容大緻是相似的。

①cds_search()首先會從控制點傳來的事件請求變量Upnp_Action_Request中讀取與搜尋有關的值,包括index、count、id、search_criteria和filter。②然後根據讀取的id值,調用函數upnp_get_entry (ut, id)得到要搜尋目錄的結構體;③接着調用函數cds_search_directchildren (event, out, index, count, entry, filter, search_criteria)開始搜尋,1)若搜尋的子節點是一個資料檔案,則會調用函數matches_search (search_criteria, *childs)來判斷該檔案是否符合搜尋條件,若符合搜尋條件則調用didl_add_item()将其放入out->buf中2)若搜尋的子節點是一個目錄,則調用遞歸函數cds_search_directchildren_recursive,搜尋該目錄下所有的資料檔案,并與搜尋條件進行比對,若符合搜尋條件則調用didl_add_item()将其放入out->buf中;out->buf中存放的是xml形式的搜尋結果;④最後調用upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf)将事件應答傳遞給upnp。

下面是該函數的流程圖:

ushare代碼分析

                                          (圖7 cds_search)

3.5 ushare生成的目錄清單

生成目錄清單時會用到以下結構體

ushare代碼分析

該結構體中的fullpath存儲的是ushare共享目錄的位址;child_count用來判斷一個upnp_entry_t類型的變量指向的是一個共享目錄中的一個檔案還是一個檔案夾,0表示是一個檔案夾,-1表示是一個資料檔案;title指的是一個目錄位址的最後一個'/'後面的内容,如對于目錄/home/film,則title = film;url指的是id+媒體格式,如id.avi、id.rmvb等。

在ushare的main()裡,當進行了upnp的初始化、注冊了事件響應回調函數與資料傳輸的回調函數後,就是調用build_metadata_list (ut)生成内容目錄清單。

build_metadata_list()的執行過程如下:

①調用程式upnp_entry_new (ut, "root", NULL, NULL, -1, true)建構ut->root_entry 根節點;

②然後通過for循環,對共享的每個目錄進行周遊,從共享目錄中添加檔案;

③對其中的一個共享目錄,建構以root_entry為父節點的entry;

④接着調用upnp_entry_add_child()将entry作為root_entry的子節點加入到ut->rb資料結構中(平衡二叉樹);

⑤調用metadata_add_container()将entry目錄下的所有子目錄和子資料進行添加檔案操作。

    在metadata_add_container()裡, 

    1)首先調用scandir 獲得entry目錄下的所有子目錄或者子檔案的名稱;

    2)對于entry的所有子檔案,将會調用metadata_add_file()完成檔案添加操作,對于entry的所有子目錄,将會遞歸調用metadata_add_container();

⑥通過以上各步和遞歸函數,就實作了将共享目錄下的所有資料檔案都添加到ut->rb資料結構中。

下面是build_metadata_list的流程圖

ushare代碼分析

                                                     (圖8 build_metadata_list)

3.6 ushare支援的資料傳輸

對于ushare,其支援的傳輸協定為http-get,在upnp av中媒體流的傳輸是不經過控制點的,而是在媒體伺服器與媒體播放器之間進行的。

下圖9中是upnp av體系中控制點、媒體伺服器、媒體播放器的建立過程。在upnp av中,控制點的作用是發現網絡上存在的媒體伺服器,獲得媒體伺服器支援的服務與事件;然後擷取媒體伺服器與媒體播放器支援的資料傳輸協定與格式清單,通過對比确定雙方都支援的傳輸協定與資料格式,然後通過媒體播放器提供的媒體傳輸服務就可以對資料流進行控制。因為ushare使用的http-get傳輸協定,其定義了關于http-get協定的資料流操作函數open、read、write、seek、get_info、close。

ushare代碼分析

                  (圖9 upnp av體系的互動過程)

與http協定操作函數有關的結構體:

ushare代碼分析

在該結構體中的detail成員中的memory成員用于FILE_MEMORY類型的檔案;而local成員用于FILE_LOCAL類型的檔案,即需要傳輸的媒體資料檔案,local.fd即打開的檔案句柄,entry為該檔案的upnp_entry_t結構體,其中包含檔案的完整路徑資訊。

3.6.1 http_open()

該函數的作用有兩個

1):從記憶體中打開一個媒體服務描述文檔;

2)從共享目錄中打開一個資料檔案。

該函數傳回的是一個web_file_t類型的結構體變量,該結構體會記錄已打開的檔案的路徑、檔案的類型等。将該結構體變量傳遞http_read()後,就可以讀取檔案資訊了。對于檔案類型是FILE_LOCAL的媒體資料檔案,将會對entry.detail.local.fd進行指派,在http_read()裡調用read()時就會傳遞該變量,讀取媒體資料流;對于檔案類型是FILE_MEMORY的媒體服務描述文檔,将會對entry.detail.memory.contents進行指派,将媒體服務描述文檔的内容指派給它。

下圖是http_open的流程圖

ushare代碼分析

3.6.2 http_read()

    該函數的作用是讀取媒體資料流或者媒體服務描述文檔的資訊。當要讀取的檔案類型是FILE_MEMORY時,就直接調用memcpy()将file.detail.memory.contents的值複制給buf即可;當要讀取的檔案類型是FILE_LOCAL時,就調用檔案操作函數read将讀取到的媒體資料檔案流放入buf中即可。

下面是該函數的流程圖

ushare代碼分析

3.6.3 http_close()

用來關閉一個以打開的檔案,當檔案類型為FILE_LOCAL時,調用檔案操作函數close()完成關閉檔案;當檔案類型為FILE_MEMORY時,調用free()釋放file.detail.memory.contents的記憶體即可。

繼續閱讀