天天看點

基于 QPlay 的智能無線流媒體傳輸音箱的設計基于 QPlay 的智能無線流媒體傳輸音箱的設計

基于 QPlay 的智能無線流媒體傳輸音箱的設計

系統總體架構

基于 QPlay 的智能無線流媒體傳輸音箱的設計基于 QPlay 的智能無線流媒體傳輸音箱的設計

QPlay音箱裝置主要工作流程如圖所示。由于采用libupnp作為UPnP SDK進行開發,是以程式開始時需要初始化UPnP SDK。

程式主要分為裝置初始化,事件循環,裝置結束三個階段。其中事件循環是程式的核心。

裝置初始化階段

裝置初始化階段需要完成:

  1. 初始化UPnP SDK

調用庫函數UpnpInit()初始化UPnP協定棧。

◆ UpnpInit()方法:

/**
 * 初始化UPnP SDK。确定IP位址和端口号,用于監聽UPnP和HTTP請求
 * @param	HostIP
 * 			主機IP位址。如果為NULL,将自動擷取一個IP位址
 * @param	DestPort
 * 			目的端口号。如果為0,将使用一個随機的端口号。
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpInit(const char *HostIP, unsigned short DestPort);
           

如果IP位址為NULL,端口号為0。SDK将會自動去擷取一個可用的IP位址和端口号。可以使用庫函數UpnpGetServerIpAddress()獲得該IP位址(失敗傳回NULL),使用庫函數UpnpGetServerPort()擷取目的端口号。

  1. 設定WEB伺服器的根目錄

調用庫函數UpnpSetWebServerRootDir()把一個本地目錄設定為WEB伺服器的根目錄,為HTTP請求描述檔案時提供準确路徑。

◆ UpnpSetWebServerRootDir()方法:

/**
 * 設定WEB伺服器根目錄。以建構描述檔案正确路徑
 * @param	rootDir
 * 			根目錄路徑。如果為NULL,以程式所在的目錄為根目錄
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpSetWebServerRootDir(const char *rootDir);
           
  1. 注冊根裝置

注冊根裝置需要設定描述檔案和異步事件回調函數,該回調函數負責處理控制點發送的訂閱請求、控制請求等。

調用庫函數UpnpRegisterRootDevice()來完成根裝置注冊。

◆ UpnpRegisterRootDevice()方法:

/**
 * 注冊根裝置
 * @param	DescUrl
 * 			描述文檔URL
 * @param	Callback
 * 			收到異步事件請求後執行的回調函數
 * @param	Cookie
 * 			回調發生時傳給回調函數的參數。可以為NULL
 * @param	Hnd
 * 			裝置的句柄。通過該句柄可以通路裝置
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpRegisterRootDevice(const char * DescUrl, Upnp_FunPtr Fun, const void * Cookie, UpnpDevice_Handle * Hnd);
           
  1. 其它相關初始化

裝置相關資訊初始化,如打開DSP檔案(用于播放音頻)、初始化播放清單容器(用于存儲歌曲資訊)、注冊信号處理函數(綁定結束函數,程式退出時進行資源回收)以及相關服務的狀态變量初始化等。

  1. 廣播裝置存在公告

裝置初始化結束後,将廣播裝置存在資訊,等待控制點的請求。

程式調用庫函數UpnpSendAdvertisement()廣播裝置存在公告,之後裝置必須進入循環,等待事件的到來(或等待程式結束資訊)。

◆ UpnpSendAdvertisement()方法:

/**
 * 廣播裝置存在公告
 * @param	Hnd
 * 			裝置句柄
 * @param	Exp
 * 			公告生存時間。在裝置生命周期中,SDK會自動在逾時前重新廣播裝置存在公告
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpSendAdvertisement(UpnpDevice_Handle Hnd, int Exp);
           

事件循環階段

裝置廣播存在公告後,将進入事件循環階段。該階段主要接收控制點發送過來的各種異步請求:訂閱請求、動作請求、擷取狀态變量請求(QPlay架構并未提供該請求)。

UPnP SDK會将各種請求進行處理,建立線程,調用注冊根裝置時注冊的回調函數(稱為event_handler)進行處理。

該回調函數原型是:

◆ event_handler()方法:

/**
 * 事件回調函數。處理接收到的所有事件
 * @param	EventType
 * 			事件類型
 * @param	Event
 * 			指向事件結構體的指針。由于不同僚件使用的結構不一緻,是以這裡統一使用空指針,需要根據事件類型進行轉換
 * @param	Cookie
 * 			指向注冊根裝置時傳入的參數
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int event_handler(Upnp_EventType EventType, void *Event, void *Cookie);
           

UPnP SDK的事件類型(EventType)一共有14種:

◆ UPNP_CONTROL_ACTION_REQUEST

動作操作請求。由裝置接收,需要傳回動作執行的結果。

事件結構體:

struct Upnp_Action_Request 
{
		int ErrCode;						// 錯誤碼(成功時為0)
		int Socket;						// 請求方套接字辨別符
		char ErrStr[LINE_SIZE];			// 錯誤資訊
		char ActionName[NAME_SIZE];	// 動作名稱
		char DevUDN[NAME_SIZE];			// 裝置UDN
		char ServiceID[NAME_SIZE];		// 服務ID
		IXML_Document * ActionRequest;// 指向動作的DOM描述文檔的指針
		IXML_Document * ActionResult;	// 指向動作結果的DOM描述文檔的指針
		struct sockaddr_storage CtrlPtIPAddr;	// 請求方IP位址資訊
		IXML_Document * SoapHeader;	// 執行包含SOAP頭資訊的XML描述文檔的指針
};
           

◆ UPNP_CONTROL_GET_VAR_REQUEST

擷取狀态變量請求。由裝置接收,需要傳回動作執行的結果。

事件結構體:

struct Upnp_State_Var_Request
{
	int ErrCode;						// 錯誤碼(成功時為0)
	int Socket;						// 請求方套接字辨別符
	char ErrStr[LINE_SIZE];			// 錯誤資訊
	char DevUDN[NAME_SIZE];			// 裝置UDN
	char ServiceID[NAME_SIZE];		// 服務ID
	char StateVarName[NAME_SIZE];	// 狀态變量名
	struct sockaddr_storage CtrlPtIPAddr;	// 請求方IP位址資訊
	DOMString CurrentVal;			// 狀态變量的目前值
};
           

◆ UPNP_CONTROL_GET_VAR_COMPLETE

擷取狀态變量響應。調用UpnpGetServiceVarStatus()後傳回的響應。

事件結構體:

struct Upnp_State_Var_Complete
{
	int ErrCode;						// 錯誤碼(成功時為0)
	char CtrlUrl[NAME_SIZE];		// 對應服務的控制URL
	char StateVarName[NAME_SIZE];	// 狀态變量名
	DOMString CurrentVal;			// 狀态變量的目前值
};
           

◆ UPNP_DISCOVERY_ADVERTISEMENT_ALIVE

存在發現資訊。由控制點接收,有新的裝置或服務可用。

事件結構體:

struct Upnp_Discovery
{
	int  ErrCode;							// 錯誤碼(成功時為0)
	int  Expires;							// 公告逾時時間
	char DeviceId[LINE_SIZE];			// 裝置唯一ID
	char DeviceType[LINE_SIZE];		// 裝置類型
	char ServiceType[LINE_SIZE];		// 服務類型
	char ServiceVer[LINE_SIZE]; 		// 服務版本号
	char Location[LINE_SIZE];			// 裝置的描述文檔URL位址
	char Os[LINE_SIZE];					// 裝置運作的系統資訊
	char Date[LINE_SIZE];				// 響應時間
	char Ext[LINE_SIZE];				// 裝置描述資訊
	struct sockaddr_storage DestAddr;	// 目标對象IP位址資訊
};
           

◆ UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE

離線發現資訊。由控制點接收,有裝置或服務關閉。

事件結構體:struct Upnp_Discovery;

◆ UPNP_DISCOVERY_SEARCH_RESULT

逾時發現資訊。由控制點接收,沒有搜尋到比對的裝置或服務,搜尋逾時。

事件結構體:struct Upnp_Discovery;

◆ UPNP_DISCOVERY_SEARCH_TIMEOUT

離線發現資訊。由控制點接收,有裝置或服務關閉。

事件結構體:無

◆ UPNP_EVENT_SUBSCRIPTION_REQUEST

訂閱事件請求。由裝置接收,裝置的事件被訂閱。需要調用UpnpAcceptSubscription()确認訂閱并傳送初始的狀态變量表。

事件結構體:

struct Upnp_Subscription_Request
{
	char * ServiceId;	// 訂閱的服務ID
	char * UDN;			// 通用裝置名稱
	Upnp_SID Sid;			// 配置設定的訂閱ID
};
           

◆ UPNP_EVENT_RECEIVED

接收事件資訊。由控制點接收,收到訂閱的事件資訊。

事件結構體:

struct Upnp_Event
{
  Upnp_SID Sid;							// 此次訂閱的訂閱ID
  int EventKey;							// 時間序列号
  IXML_Document * ChangedVariables;	// 發生改變的狀态變量值
};
           

◆ UPNP_EVENT_RENEWAL_COMPLETE

續訂事件響應。調用UpnpRenewSubscribeAsync()後傳回的響應。

事件結構體:

struct Upnp_Event_Subscribe 
{
	Upnp_SID Sid;						// 此次訂閱的訂閱ID
	int ErrCode;						// 錯誤碼(成功時為0)
	char PublisherUrl[NAME_SIZE];	// 訂閱或退訂的事件URL
	int TimeOut;						// 訂閱時間(隻對訂閱)
};
           

◆ UPNP_EVENT_SUBSCRIBE_COMPLETE

訂閱事件響應。調用UpnpSubscribeAsync()後傳回的響應。隻有傳回成功(UPNP_E_SUCCESS)時,Sid才是有效的。

事件結構體:struct Upnp_Event_Subscribe;

◆ UPNP_EVENT_UNSUBSCRIBE_COMPLETE

退訂事件響應。調用UpnpUnSubscribeAsync()後傳回的響應。Sid表示正在退訂的事件ID。

事件結構體:struct Upnp_Event_Subscribe;

◆ UPNP_EVENT_AUTORENEWAL_FAILED

自動續訂失敗。用戶端的自動續訂失敗,訂閱失效。

事件結構體:struct Upnp_Event_Subscribe;

◆ UPNP_EVENT_SUBSCRIPTION_EXPIRED

訂閱過期。用戶端的訂閱已經過期,訂閱失效。

事件結構體:struct Upnp_Event_Subscribe;

上述結構體定義中,LINE_SIZE為180,NAME_SIZE為256。

對于本程式,隻需要處理動作操作請求(UPNP_CONTROL_ACTION_REQUEST)和訂閱事件請求(UPNP_CONTROL_SUBSCRIPTION_REQUEST)。

是以,在程式的事件循環階段,主要處理訂閱請求和動作請求。

  1. 處理訂閱事件

裝置收到控制點的事件請求,事件回調函數(event_handler)的事件類型(EventType)為UPNP_CONTROL_SUBSCRIPTION_REQUEST,進入訂閱事件處理。

判斷Upnp_Subscription_Request結構體的ServiceId标簽,可以獲悉是訂閱哪一個服務。在本程式中,提供的四個服務ID為:“urn:upnp-org:serviceId:AVTransport”(音視訊傳輸服務)、“urn:upnp-org:serviceId:RenderingControl”(播放控制服務)、“urn:upnp-org:serviceId:ConnectionManager”(連接配接管理服務)和“urn:tencent-com:serviceId:QPlay”(QPlay服務)。

如果服務ID存在,且可以訂閱。需要按照UPnP規範把相應服務的狀态變量表資訊轉換為XML描述的形式,并使用庫函數UpnpAcceptSubscription()或UpnpAcceptSubscriptionExt()接受訂閱後發送給控制點。

◆ UpnpAcceptSubscriptionExt()方法:

/**
 * 接受訂閱和發送訂閱服務的狀态變量目前值
 * @param	Hnd
 * 			裝置句柄
 * @param	DevID
 * 			裝置ID。可以使用Upnp_Subscription_Request.UDN
 * @param	ServID
 * 			服務ID。可以使用Upnp_Subscription_Request.ServiceId
 * @param	PropSet
 * 			DOM文檔屬性集。符合UPnP裝置架構的XML模式的文檔,使用相應的函數把資料轉換為IXML_Document類型
 * @param	SubsId
 * 			訂閱ID。可以使用Upnp_Subscription_Request.Sid
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpAcceptSubscriptionExt( UpnpDevice_Handle Hnd, const char * DevID, const char * ServID, IXML_Document * PropSet, Upnp_SID SubsId);
           

UpnpAcceptSubscription()與UpnpAcceptSubscriptionExt()功能一樣,隻是需要的參數有所不同。

  1. 處理動作事件

動作事件處理是程式運作的重要部分,所有功能的控制都依賴動作事件處理。該動作事件處理包含四個服務的所有動作

基于 QPlay 的智能無線流媒體傳輸音箱的設計基于 QPlay 的智能無線流媒體傳輸音箱的設計

裝置收到控制點的事件請求,事件回調函數(event_handler)的事件類型(EventType)為UPNP_CONTROL_ACTION_REQUEST,進入動作事件處理。

判斷Upnp_Action_Request結構體的ServiceId标簽,判斷是哪一個服務的動作事件。再判斷Upnp_Action_Request結構體的ActionName标簽,獲悉其動作事件名,

調用相應的動作處理函數。動作處理結束後,需要将動作響應資訊(訂閱的狀态變量值)傳回。

可以使用庫函數UpnpMakeActionResponse()生成動作響應DOM文檔資訊。

◆ UpnpMakeActionResponse()方法:

/**
 * 生成動作響應的DOM文檔資訊
 * @param	ActionName
 * 			動作名。可以使用Upnp_Action_Request.ActionName
 * @param	ServType
 * 			服務類型。可以使用Upnp_Action_Request.ServiceID
 * @param	NumArg
 * 			參數組(狀态變量名,狀态變量值)的數量
 * @param	Arg
 * 			其它狀态變量參數組
 * @return	傳回生成的DOM文檔指針。可以使用Upnp_Action_Request.ActionResult接收傳回值
 */
IXML_Document * UpnpMakeActionResponse(const char * ActionName, const char * ServType, int NumArg, const char * Arg, ...);
           

也可以使用庫函數UpnpAddToActionResponse()往動作響應DOM文檔添加狀态變量資訊。

◆ UpnpAddToActionResponse()方法:

/**
 * 在動作響應的DOM文檔加入一個狀态變量資訊
 * @param	ActionResponse
 * 			動作響應資訊DOM文檔的二級指針。可以使用&Upnp_Action_Request.ActionResult
 * @param	ActionName
 * 			動作名。可以使用Upnp_Action_Request.ActionName
 * @param	ServType
 * 			服務類型。可以使用Upnp_Action_Request.ServiceID
 * @param	ArgName
 * 			狀态變量名
 * @param	ArgVal
 * 			狀态變量值
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpAddToActionResponse(IXML_Document ** ActionResponse, const char * ActionName, const char * ServType, const char * ArgName, const char * ArgVal);
           

裝置結束階段

QPlay2.0規定,當裝置切換網絡、關機等情況下,需要發出裝置離線公告通知在網的QQ音樂應用程式等控制點該裝置不可用。是以,在觸發裝置切換網絡、關機等事件時,程式進入結束階段。

結束階段需要執行登出根裝置,廣播裝置離線資訊,釋放占用的系統資源等操作,最後退出程式。

調用庫函數UpnpUnRegisterRootDevice()登出根裝置,再使用庫函數UpnpFinish()執行廣播裝置離線資訊、關閉定時器線程、停止Mini Server、登出線程池等操作。UpnpFinish()必須是UPnP SDK最後調用的API。

◆ UpnpUnRegisterRootDevice()方法:

/**
 * 登出根裝置
 * @param	Hnd
 * 			裝置句柄
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpUnRegisterRootDevice(UpnpDevice_Handle Hnd);
           

◆ UpnpFinish()方法:

/**
 * 廣播裝置離線資訊,登出線程池等
 * @param	無
 * @return	成功傳回0(UPNP_E_SUCCESS),失敗傳回錯誤碼
 */
int UpnpFinish(void);
           

除了UPnP SDK内部的資源回收等,還需要回收程式中其它申請的資源。