2006/11/14

數位家庭產品初探 -- ushare *.c

這邊只研究跟 upnp 相關的源碼,初看之下有幾個未照規範實作或我們期待實作未實作的功能:
一、Auto-IP
二、多個網卡的處理
三、Timeout(TTL) 與重送 UpnpSendAdvertisement()
四、未提供與 hardware pnp 配合的功能,例如插入 usb disk 之類的。
五、檔案僅就附檔名分析卻未分析其內容

而用到 libupnp 的部份大致為:
UpnpEnableWebserver(), UpnpSetVirtualDirCallbacks(), UpnpAddVirtualDir(), UpnpRegisterRootDevice2(), UpnpSendAdvertisement(), UpnpAddToActionResponse(), UpnpUnRegisterRootDevice(), UpnpFinish(), UpnpInit(), UpnpGetServerPort(), UpnpGetServerIpAddress()
  • cds.c: Content Directory Service
看得出來支援四個 actions
#define SERVICE_CDS_ACTION_SEARCH_CAPS "GetSearchCapabilities"
#define SERVICE_CDS_ACTION_SORT_CAPS "GetSortCapabilities"
#define SERVICE_CDS_ACTION_UPDATE_ID "GetSystemUpdateID"
#define SERVICE_CDS_ACTION_BROWSE "Browse"

struct service_action_t cds_service_actions[] = {
{ SERVICE_CDS_ACTION_SEARCH_CAPS, cds_get_search_capabilities },
{ SERVICE_CDS_ACTION_SORT_CAPS, cds_get_sort_capabilities },
{ SERVICE_CDS_ACTION_UPDATE_ID, cds_get_system_update_id },
{ SERVICE_CDS_ACTION_BROWSE, cds_browse },
{ NULL, NULL }
};

#define SERVICE_CDS_ARG_SEARCH_CAPS "SearchCaps"
#define SERVICE_CDS_ARG_SORT_CAPS "SortCaps"
#define SERVICE_CDS_ARG_UPDATE_ID "Id"
#define SERVICE_CDS_ARG_START_INDEX "StartingIndex"
#define SERVICE_CDS_ARG_REQUEST_COUNT "RequestedCount"
#define SERVICE_CDS_ARG_OBJECT_ID "ObjectID"
#define SERVICE_CDS_ARG_FILTER "Filter"
#define SERVICE_CDS_ARG_BROWSE_FLAG "BrowseFlag"
#define SERVICE_CDS_ARG_SORT_CRIT "SortCriteria"
#define SERVICE_CDS_ROOT_OBJECT_ID "0"
#define SERVICE_CDS_BROWSE_METADATA "BrowseMetadata"
#define SERVICE_CDS_BROWSE_CHILDREN "BrowseDirectChildren"
#define SERVICE_CDS_DIDL_RESULT "Result"
#define SERVICE_CDS_DIDL_NUM_RETURNED "NumberReturned"
#define SERVICE_CDS_DIDL_TOTAL_MATCH "TotalMatches"
#define SERVICE_CDS_DIDL_UPDATE_ID "UpdateID"
還有一堆跟 DIDL 有關的變數,如 DIDL_NAMESPACE
其中 DIDL 是數位資料內部 buffer 的表示法,是 xml 格式,因此有 header, footer, tag, param, value, item 等等。

cds_get_search_capabilities(): upnp_add_response(SERVICE_CDS_ARG_SEARCH_CAPS)
cds_get_sort_capabilities(): upnp_add_response(SERVICE_CDS_ARG_SORT_CAPS)
cds_get_system_update_id (): upnp_add_response (SERVICE_CDS_ARG_UPDATE_ID, SERVICE_CDS_ROOT_OBJECT_ID)
以上是簡單的,複雜的也是處理 buffer 之後再叫用類似的函數與參數,如
upnp_add_response (SERVICE_CDS_DIDL_RESULT)


  • cms.c: Connection Management Service
#define SERVICE_CMS_ACTION_PROT_INFO "GetProtocolInfo"
#define SERVICE_CMS_ACTION_CON_ID "GetCurrentConnectionIDs"
#define SERVICE_CMS_ACTION_CON_INFO "GetCurrentConnectionInfo"

struct service_action_t cms_service_actions[] = {
{ SERVICE_CMS_ACTION_PROT_INFO, cms_get_protocol_info },
{ SERVICE_CMS_ACTION_CON_ID, cms_get_current_connection_ids },
{ SERVICE_CMS_ACTION_CON_INFO, cms_get_current_connection_info },
{ NULL, NULL }
}; <-- 只有三個 action

#define SERVICE_CMS_ARG_SOURCE "Source"
#define SERVICE_CMS_ARG_SINK "Sink"
#define SERVICE_CMS_ARG_CONNECTION_IDS "ConnectionIDs"
#define SERVICE_CMS_ARG_CONNECTION_ID "ConnectionID"
#define SERVICE_CMS_ARG_RCS_ID "RcsID"
#define SERVICE_CMS_ARG_TRANSPORT_ID "AVTransportID"
#define SERVICE_CMS_ARG_PROT_INFO "ProtocolInfo"
#define SERVICE_CMS_ARG_PEER_CON_MANAGER "PeerConnectionManager"
#define SERVICE_CMS_ARG_PEER_CON_ID "PeerConnectionID"
#define SERVICE_CMS_ARG_DIRECTION "Direction"
#define SERVICE_CMS_ARG_STATUS "Status"
#define SERVICE_CMS_DEFAULT_CON_ID "0"
#define SERVICE_CMS_UNKNOW_ID "-1"
#define SERVICE_CMS_OUTPUT "Output"
#define SERVICE_CMS_STATUS_OK "OK"

. 看來 upnp_add_response() 是對事件的處理,本檔也只定義了那三個 action 相對應的函式
. cms_get_protocol_info() 等於是把 Mime type list 串起來傳回去
. 下面第二行很特別:
upnp_add_response (event, SERVICE_CMS_ARG_SOURCE, respText);
upnp_add_response (event, SERVICE_CMS_ARG_SINK, "");
. cms_get_current_connection_ids(): upnp_add_response (event, SERVICE_CMS_ARG_CONNECTION_IDS, ""); <-- 後面這個 "" 應該再探究 . cms_get_current_connection_info() 有空再來研究這個比較有趣的 action:

upnp_add_response (event, SERVICE_CMS_ARG_CONNECTION_ID, SERVICE_CMS_DEFAULT_CON_ID);
upnp_add_response (event, SERVICE_CMS_ARG_RCS_ID, SERVICE_CMS_UNKNOW_ID);
upnp_add_response (event, SERVICE_CMS_ARG_TRANSPORT_ID, SERVICE_CMS_UNKNOW_ID);
while (list->extension) {
upnp_add_response (event, SERVICE_CMS_ARG_PROT_INFO, list->protocol);
list++;
}

upnp_add_response (event, SERVICE_CMS_ARG_PEER_CON_MANAGER, "");
upnp_add_response (event, SERVICE_CMS_ARG_PEER_CON_ID, SERVICE_CMS_UNKNOW_ID);
upnp_add_response (event, SERVICE_CMS_ARG_DIRECTION, SERVICE_CMS_OUTPUT);
upnp_add_response (event, SERVICE_CMS_ARG_STATUS, SERVICE_CMS_STATUS_OK);


  • msr.c
這個檔很奇怪,先寫下提供的 service 就好

struct service_action_t msr_service_actions[] = {
{ SERVICE_MSR_ACTION_IS_AUTHORIZED, msr_is_authorized },
{ SERVICE_MSR_ACTION_REGISTER_DEVICE, msr_register_device },
{ SERVICE_MSR_ACTION_IS_VALIDATED, msr_is_validated },
{ NULL, NULL }
};

  • http.c 拿來研究 embedded web server 還不錯
struct UpnpVirtualDirCallbacks virtual_dir_callbacks = {
http_get_info,
http_open, <-- 這兩個函數會用到 metadata.c 的函數,應該研究 upnp_entry upnp_id = atoi (strrchr (filename, '/') + 1); upnp_get_entry (ut->root_entry, upnp_id);
http_read,
http_write,
http_seek,
http_close
};
struct web_file_t {
char *fullpath;
size_t pos;
enum {
FILE_LOCAL,
FILE_MEMORY
} type;
union {
struct {
int fd;
struct upnp_entry_t *entry;
} local;
struct {
char *contents;
size_t len;
} memory;
} detail;
};

  • metadata.c: CDS Metadata DB
我知道的是在 djmount 會產生所謂的 metadata, 看來這東西很重要,而在源碼裡,這個檔好像是內部資料結構與 xml 的轉接橋樑, 這邊所謂的內部資料指的是「多媒體檔案」, 有空再來看

mime.c: media file MIME-type association
#define UPNP_VIDEO "object.item.videoItem.movie"
#define UPNP_AUDIO "object.item.audioItem.musicTrack"
#define UPNP_PHOTO "object.item.imageItem.photo"
#define UPNP_PLAYLIST "object.item.playlistItem"
#define UPNP_TEXT "object.item.textItem" <-- 純文字是當歌詞用? 其他的就不多說了,ushare 支援的格式非常多,看來只是純分析檔尾而已
  • presentation.c: UPnP Presentation Page

#define CGI_ACTION "action="
#define CGI_ACTION_ADD "add"
#define CGI_ACTION_DEL "del"
#define CGI_ACTION_REFRESH "refresh"
#define CGI_PATH "path"
#define CGI_SHARE "share"
看來所謂 presentation 跟執行 cgi 沒兩樣,但是它是 html 而非 xml


  • services.c: UPnP services

static struct service_t services[] = {
{ CDS_SERVICE_ID, CDS_SERVICE_TYPE, cds_service_actions },
{ CMS_SERVICE_ID, CMS_SERVICE_TYPE, cms_service_actions },
{ MSR_SERVICE_ID, MSR_SERVICE_TYPE, msr_service_actions },
{ NULL, NULL, NULL }
};

這邊定義了像 upnp_add_response() 這個常見的函式,此外有
find_service_action(), upnp_get_ui4(), upnp_get_string() <-- 這個是分析 xml 格式的 request
  • ushare.c: UPnP Media Server

比較精典的函式為: handle_action_request(), device_callback_event_handler()
後者有三個狀況:

case UPNP_CONTROL_ACTION_REQUEST:
handle_action_request ((struct Upnp_Action_Request *) event);
break;
case UPNP_CONTROL_ACTION_COMPLETE:
case UPNP_EVENT_SUBSCRIPTION_REQUEST:
case UPNP_CONTROL_GET_VAR_REQUEST:

上面是照抄,感覺好像有些該做的事沒去做,得查查 upnp 怎麼規範的

整個 ushare 流程如下:
. ushare_new() 初始化
. setup_i18n(); setup_iconv();
. 讀 config 檔
. 分析命令列
. 若 ushare 以 daemon 來跑,則啟動 syslog
. 在判斷有內容(多媒體檔及網卡)之後,會根據 MAC address create_udn()
. 取得 Ip address
. 若是 daemon, 則呼叫 daemon()
. 設定 signal (SIGINT, UPnPBreak); signal (SIGHUP, reload_config);
. init_upnp (ut): UpnpInit(), UpnpGetServerPort(), UpnpGetServerIpAddress(), UpnpEnableWebserver(), UpnpSetVirtualDirCallbacks(), UpnpAddVirtualDir(), UpnpRegisterRootDevice2(device_callback_event_handler), UpnpSendAdvertisement(1800)
比較怪異的是,竟然有「註冊->取消->再註冊」這樣的程序?而有效期限看來是 1800s
init_upnp() 就算是啟動真正的 upnp media server 服務了。可以參考 restart_upnp()
. build_metadata_list(ut)
. 進入等待 while (true) sleep (1000000); <-- 這樣寫好像怪怪的耶 11d13h46m40s?

2 意見:

老貓爸 提到...

您好,
我從網路上有看到您對 djmount 有研究,想跟您請教一個問題,
我現在用djmount遇到一個問題,
就是用它在 parse xml 時,Embedded 的 CPU 處理很慢,往往要超過20-30 秒以上,
不知您有遇到這樣的問題嗎? 是否可以給我什麼樣的建議??
請回信 keilven.lin@rdc.com.tw
謝謝!

菠蘿麵包 提到...

基本分析 xml 算是非常有效率的,當然它需要比較多的記憶體,我猜是系統記憶體不夠,而這通常相依於檔案個數,也就是數量愈多就會需要愈多記憶體。

解決之道,可能要動手腳,分多次送,每次都是完整的 XML, 但是要想辦法讓 client 知道是附加的。當然這樣的話 client server 雙方都要配合。

根本解決之道當然是加記憶體啦。

其實還有一種方法,修改分析的方式,分析到中段,就把前面所 allocate 的記憶體釋放掉。