第三階段應用層——2.13 視訊監控—mjpg-streamer用戶端的編寫

2020-08-12 15:37:44

視訊監控—mjpg-streamer用戶端的編寫

  • 硬體平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
  • 軟件平臺:執行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
  • 開發環境:arm-linux-gcc-4.3.2工具鏈、linux-3.4.2內核(開發版根檔案系統)
  • 原始碼倉庫:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3


一、前言

在上篇部落格已經分析過了關於mjpg_streamer的呼叫過程,下面 下麪我們來根據這個呼叫過程,自己寫一個用戶端,通過連線開發板的WIFI,在虛擬機器上顯示攝像頭的數據。

二、伺服器端的協定與數據流

1、協定

在用戶端接接收視訊數據時:

  1. 用戶端需要發送"GET /?action=stream"字串給伺服器端,使伺服器知道需要發送視訊流數據給用戶端。
  2. 接着,用戶端需要發送少於2位元組的數據,提醒伺服器端不需要提供使用者密碼登陸功能
  3. 隨後,伺服器發送一串提示資訊給用戶端,表示伺服器已經接收到上述用戶端的要求
  4. 之後,伺服器開始發送視訊流數據
    4.1 伺服器發送字串,告訴用戶端這一幀數據的格式與大小
    4.2 隨後,伺服器開始發送一幀數據給用戶端
    4.3 最後,伺服器端發送"boundarydonotcross"字串,表示一幀數據發送完畢

2、數據流

mjpg_streamer從攝像頭中接收數據時(輸入外掛)

  1. 支援攝像頭輸出數據的格式爲yuv或mjpg
  2. 通過ioctl函數讀取出輸出數據
    2.1 若爲mjpg格式,則直接拷貝到倉庫
    2.2 若爲yuv格式,則先把yuv顏色空間轉換成rgb顏色空間,最後通過libjpeg-turbo把rgb數據壓縮成jpeg格式的數據
    2.3 把壓縮後的jpeg數據拷貝到倉庫中

mjpg_streamer從倉庫中取出數據時(輸出外掛)

  1. 倉庫中把數據取出,通過socket把數據發送給用戶端

所以最終用戶端接收到的視訊數據是一幀一幀的jpeg格式的圖片

三、框架

1、程式框架

藉助之前的【2.5 視訊監控—在LCD上顯示攝像頭影象】的框架進行修改,完成通過用戶端連線,藉助mjpg_streamer在虛擬機器上動態顯示攝像頭的數據資訊
在这里插入图片描述

對於上述完成主要功能的5個部分:display顯示部分、debug偵錯資訊輸出部分、render渲染部分、video_recv視訊接收部分、convert格式轉換部分

  1. video_recv視訊接收部分:負責從伺服器端獲得攝像頭的原始數據
  2. convert格式轉換部分:負責對於攝像頭的原始數據,進行格式轉換,對於不同的攝像頭有不同的數據格式,需要包含多個檔案來支援多種轉換方式;
  3. render渲染部分:負責對轉換得到的數據格式,進行壓縮、合併成可以在LDC上顯示的數據
  4. display顯示部分合併後的數據顯示在虛擬機器上
  5. debug偵錯部分設定列印等級和列印通道,通過列印等級控製程式列印的偵錯資訊錯誤資訊警告資訊等,通過列印通道設定來控製程式列印的輸出的流向標準輸出還是網路列印輸出

2、Makefile框架

在这里插入图片描述

分爲如下3部分:

  1. 頂層目錄的Makefile:定義obj-y來指定根目錄下要編進程式去的檔案、子目錄外,主要是定義工具鏈、編譯參數、鏈接參數
  2. 頂層目錄的Makefile.build:把某個目錄及它的所有子目錄中、需要編進程式去的檔案都編譯出來,打包爲built-in.o
  3. 各級子目錄的Makefile:把當前目錄下的.c檔案編進程式裡。

四、數據流向

在这里插入图片描述
如圖所示:視訊數據總的流向爲:攝像頭—>mjpg_streamer伺服器—>用戶端

mjpg_streamer伺服器:

  1. 輸入外掛中,mjpg_streamer伺服器通過ioctl函數,讀出攝像頭的數據,並儲存到「倉庫」中
  2. 輸出外掛中從倉庫中取出數據,通過socket協定發出視訊數據給用戶端

用戶端:

  1. video視訊裝置部分用戶端把mjpg_streamer伺服器接受到的數據儲存在VideoBuf,這些數據都是jpeg格式需要convert部分轉換格式
  2. convert格式轉換部分,會根據video視訊裝置部分傳來的數據格式videobuf,呼叫適合的轉換檔案來把原始的攝像頭數據轉換成rgb格式儲存在convertbuf
  3. display顯示部分中convertbuf的數據進行最後的排版與處理,最終顯示在虛擬機器上

五、程式的編寫

1、視訊接收管理者標頭檔案

同樣的,需要設如下的管理者,進行對模組的管理。

/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    video_recv_manager.h
 * @brief   視訊數據管理者標頭檔案,向下支援各種視訊數據,向上提供介面
 * @author  K
 * @version 0.0.1
 * @date    2021-07-26
 * @license MulanPSL-1.0
 *
 * 檔案修改歷史:
 * <時間>       | <版本>    | <作者>  | <描述>
 * 2021-08-11   | v0.0.1    | Kcode   | 視訊數據管理者標頭檔案
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <pthread.h>
#include "pic_operation.h"

#ifndef _VIDEO_MANAGER_H
#define _VIDEO_MANAGER_H

/*!
 * 儲存視訊數據
 */
typedef struct VideoBuf {
    T_PIXELDATAS pixel_data;    /**< 借用T_PixelDatas */
    int pixel_format;           /**< 畫素格式 */

    /* signal fresh frames */
    pthread_mutex_t db;
    pthread_cond_t  db_update;
}T_VIDEOBUF, *PT_VIDEOBUF;

/*!
 * 視訊數據處理結構體
 */
typedef struct VideoRecv {
    char *name; /**< 裝置名 */
    
    /* 初始化裝置 */
   int (*Init)(int *SocketClient);

    /* 連線伺服器 */
    int (*ConnectToServer)(int *SocketClient, const char *ip);

    /* 斷開伺服器 */
    int (*DisConnectToServer)(int *SocketClient);

    /* 獲得視訊格式 */
    int (*GetFormat)(void);

    /* 獲取video數據 */
     int (*GetVideo)(int *SocketClient, PT_VIDEOBUF ptVideoBuf);
    struct VideoRecv *ptNext;
}T_VIDEORECV, *PT_VIDEORECV;

/*!
 * @brief  初始化函數,把對支援的各個設備註冊進鏈表進行統一管理
 * @param  [in] 無
 * @return  無
 */
int video_recv_init(void);

/*!
 * @brief  顯示所支援的裝置
 * @param  [in] 無
 * @return 無 
 */
void ShowVideoOpr(void);

/*!
 * @brief  註冊函數
 * @param  ptVideoRecv[in] 要註冊的裝置結構體結點
 * @return 0:成功 -1:失敗
 */
int RegisterVideoRecv(PT_VIDEORECV ptVideoRecv);

int VideoRecvInit(void);

void ShowVideoRecv(void);

PT_VIDEORECV GetVideoRecv(char *pName);

#endif /* _VIDEO_MANAGER_H */

2、具體的視訊接收模組檔案

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/videodev2.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include "config.h"
#include "video_recv_manager.h"
#include "debug_manager.h"

#define BUFFER_SIZE 1024 /**< 伺服器從用戶端中接收的最大數據位元組數 */

/*!
 * @brief  與伺服器建立連線
 * @param[in] socket_client  socket的控制代碼
 * @param[in] ip  伺服器的ip
 * @return int 成功:0,失敗:-1
 */
static int connect_to_server(int *socket_client, const char *ip)
{
	int ret;
	struct sockaddr_in socket_server_addr;

	*socket_client = socket(AF_INET, SOCK_STREAM, 0);

	socket_server_addr.sin_family      = AF_INET;
	socket_server_addr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	//socket_server_addr.sin_addr.s_addr = INADDR_ANY;
 	if (0 == inet_aton(ip, &socket_server_addr.sin_addr))
 	{
		DBG_PRINTF("invalid server_ip\n");
		return -1;
	}
	memset(socket_server_addr.sin_zero, 0, 8);

	ret = connect(*socket_client, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));	
	if (-1 == ret)
	{
		DBG_PRINTF("connect error!\n");
		return -1;
	}
	
	return 0;
}

/*!
 * @brief  斷開與伺服器的連線
 * @param[in] socket_client  socket的控制代碼
 * @return int 0
 */
static int disconnect_to_server(int *socket_client)
{
	close(*socket_client);

	return 0;
}

/*!
 * @brief  發送報文給伺服器端,告訴其需要發送的數據型別與要求
 * @param[in] socket_client  socket的控制代碼
 * @return int 成功:總接收到的數據長度,失敗:-1
 */
static int init(int *socket_client)
{
	char send_buf[100];
	int  send_len;

	int  recv_len;
	char recv_buf[1000];

	/*!
	 * 發請求型別字串:"GET /?action=stream\n"表示需要用戶端接收視訊流數據 
	 */
	memset(send_buf, 0x0, 100);
	strcpy(send_buf, "GET /?action=stream\n");
	send_len = send(*socket_client, send_buf, strlen(send_buf), 0);
	if (send_len <= 0)
	{
		close(*socket_client);
		return -1;
	}

	/*!
	 * 如果我們不使用密碼功能!則只需發送任意長度爲小於2位元組的字串 
	 */
	memset(send_buf, 0x0, 100);
	strcpy(send_buf, "f\n");
	send_len = send(*socket_client, send_buf, strlen(send_buf), 0);
	if (send_len <= 0)
	{
		close(*socket_client);
		return -1;
	}

	/*!
	 * 將從伺服器端接收一次報文
	 * 由於之前已經做好準備工作,所以此時接收的資訊是伺服器端已ok
	 */
	/* 接收用戶端發來的數據並顯示出來 */
	recv_len = recv(*socket_client, recv_buf, 999, 0);
	if (recv_len <= 0)
	{
		close(*socket_client);
		return -1;
	}
	else
	{
		recv_buf[recv_len] = '\0';
		printf("http header: %s\n", recv_buf);
	}

	return 0;
}

/*!
 * @brief  返回視訊數據的格式
 * @return int V4L2_PIX_FMT_MJPEG
 */
static int getformat(void)
{
	/* 直接返回視訊的格式 */
	return V4L2_PIX_FMT_MJPEG;
}

/*!
 * @brief  解析伺服器端發送的報文,獲取一幀視訊數據的大小
 * @param[in]  socket_client socket的控制代碼
 * @param[out] free_buf 儲存伺服器端發送的一幀視訊數據的資訊
 * @param[out] free_len free_buf中剩餘記憶體大小
 * @return int 成功:一幀視訊數據的大小
 */
static long int get_file_len(int *socket_client, char *free_buf, int *free_len)
{
	int recv_len;
	long int videolen;
	char recv_buf[1024];
	char *plen, *buffp;

	while(1)
	{
	    /*!
	     * 從伺服器接收數據(將要接收到的一幀視訊數據大小)
	     */
		recv_len = recv(*socket_client, recv_buf, 1024, 0);
		if (recv_len <= 0)
		{
			close(*socket_client);
			return -1;
		}

		/*!
		 * 解析recv_buf,判斷接收到的數據是否是報文 
		 */
		plen = strstr(recv_buf, "Length:");
		if(NULL != plen)
		{
			plen = strchr(plen, ':');
			plen++;
			videolen = atol(plen);
			printf("the Video Len %ld\n", videolen);
		}

        /*!
         * 解析完畢的標誌
         */
		buffp = strstr(recv_buf, "\r\n\r\n");
		if(buffp != NULL)
			break;
	}

    
	buffp += 4;
	*free_len = 1024 - (buffp - recv_buf);
	memcpy(free_buf, buffp, *free_len);

	return videolen;
}

/*!
 * @brief  從http用戶端接收一幀視訊數據數據
 * @param[in] socket_client  socket的控制代碼
 * @param[out] lpbuff  儲存接收到數據的地址
 * @param[in] size  需要接收到的數據長度 
 * @return long 成功:總接收到的數據長度,失敗:-1
 */
static long int http_recv(int *socket_client, char **lpbuff, long int size)
{
	int recv_len = 0;   /**< 一次從用戶端接收到數據的長度 */
    int recv_sum = 0;   /**< 總共從用戶端接收到數據的長度 */
	char recv_buf[BUFFER_SIZE]; /**< 儲存接收到的數據 */

    /*!
     * 分次接收數據,最多接收BUFFER_SIZE大小位元組的數據
     */
	while(size > 0)
	{
	    /*!
	     * 呼叫recv從用戶端接收數據
	     * 大小:(size > BUFFER_SIZE)? BUFFER_SIZE: size
	     * 數據儲存到:recv_buf[BUFFER_SIZE]
	     */
		recv_len = recv(*socket_client, recv_buf, (size > BUFFER_SIZE)? BUFFER_SIZE: size, 0);
		if (recv_len <= 0)
			break;

		recv_sum += recv_len;   /* 實際接收的位元組數 */
		size -= recv_len;       /* 剩餘需要接收的位元組數 */

        /*!
         * 判斷傳入的lpbuff是否爲空
         * 空則分配記憶體,不空則擴大記憶體
         */
		if(*lpbuff == NULL)
		{
			*lpbuff = (char *)malloc(recv_sum);
			if(*lpbuff == NULL)
				return -1;
		}
		else
		{
			*lpbuff = (char *)realloc(*lpbuff, recv_sum);
			if(*lpbuff == NULL)
				return -1;
		}

        /*!
         * 根據偏移值計算出記憶體地址,拷貝數據
         */
		memcpy(((*lpbuff) + recv_sum - recv_len), recv_buf, recv_len);
	}

	return recv_sum;
}

/*!
 * @brief  獲取一幀視訊數據
 * @param[in] socket_client  socket的控制代碼
 * @param[in] video_buf  儲存一幀數據的地址,需在函數外分配
 * @return 0 - 成功縮放,-1 - 不支援縮放
 */
static int get_video(int *socket_client, PT_VIDEOBUF video_buf)
{
	long int video_len, recv_len;
	int first_len = 0;
	char tmpbuf[1024];
	char *free_buffer = NULL;

    if (video_buf->pixel_data.PixelDatas == NULL)
    {
        DebugPrint(APP_ERR"please check that video_buf->pixel_data.PixelDatas == NULL\n");
        return -1;
    }

    /*!
     * 獲取一幀視訊數據
     */
	while(1)
	{
        /* 解析伺服器的報文,獲取一幀視訊數據的大小 */
		video_len = get_file_len(socket_client, tmpbuf, &first_len); 

        /* 解析伺服器的數據,獲取已接收的視訊數據的大小 */
		recv_len = http_recv(socket_client, &free_buffer, video_len - first_len);

        /* 原子操作 */
		pthread_mutex_lock(&video_buf->db);

		/* 將兩次接收到的視訊數據組裝成一幀數據 */
		memcpy(video_buf->pixel_data.PixelDatas, tmpbuf, first_len);
		memcpy(video_buf->pixel_data.PixelDatas + first_len, free_buffer, recv_len);
		video_buf->pixel_data.TotalBytes = video_len;

        /* 發出一個數據更新的信號,通知輸出通道來取數據 */
		pthread_cond_broadcast(&video_buf->db_update);

        /* 原子操作結束 */
		pthread_mutex_unlock(&video_buf->db);			
	}
    
	return 0;
}

/* 構造 */
static T_VIDEORECV s_video_recv = {
    .name        	     = "http",
    .ConnectToServer     = connect_to_server,
    .DisConnectToServer  = disconnect_to_server,
    .Init 				 = init,
    .GetFormat		     = getformat,
    .GetVideo			 = get_video,
};

/* 註冊 */
int video_recv_init(void)
{
    return RegisterVideoRecv(&s_video_recv);
}

3、主函數的呼叫流程

  1. 初始化偵錯系統

  2. 註冊顯示模組

  3. 選擇顯示裝置

  4. 獲取顯示屏參數

  5. 獲取顯示屏視訊記憶體

  6. 獲取顯示器格式

  7. 註冊視訊數據接收模組

  8. 顯示視訊獲取通道

  9. 獲取視訊獲取操作函函數

  10. 獲取視訊數據格式

  11. 註冊轉換模組

  12. 獲取支援格式的轉換處理結構體

  13. 與伺服器端建立連線

  14. 發送報文給伺服器,告訴需要其所發送的數據型別與要求

  15. 清除video_buf的數據,併爲其分配儲存一幀數據的記憶體

  16. 清除convert_buf的數據,並設定其顯示格式與bpp

  17. 初始化 video_buf.db 成員與video_buf.db_update(條件變數),用於執行緒管理

  18. 建立獲取視訊數據的執行緒

  19. 在while(1)中:
    19.1 等待視訊數據的更新,當接收到視訊數據時,video_buf.db_update就會變換,通知主執行緒執行以下操作
    19.2 接收完一幀數據後,呼叫libjpeg_turbo轉換爲RGB格式
    19.3 調整數據位置,居中顯示在虛擬機器中(重新整理)

  20. 等待執行緒結束,以便回收它的資源


/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    main.c
 * @brief   配合多個模組,通過連線mjpg_streamer伺服器端,在虛擬機器顯示攝像頭數據
 * @author  K
 * @version 0.0.1
 * @date    2021-07-26
 * @license MulanPSL-1.0
 *
 * 檔案修改歷史:
 * <時間>       | <版本>    | <作者>  | <描述>
 * 2021-08-12   | v0.0.1    | Kcode   | 主程式
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

#include "config.h"
#include "disp_manager.h"
#include "debug_manager.h"
#include "pic_operation.h"
#include "render.h"
#include "convert_manager.h"
#include "video_recv_manager.h"

PT_VIDEORECV s_video_recv_opr;
int socket_client;  /**< socket通訊端 */

/*!
 * @brief  用戶端接收執行緒函數
 * @return long 成功:總接收到的數據長度,失敗:-1
 */
void* recv_video_thread(void *data)
{
	if(s_video_recv_opr->GetVideo(&socket_client, (PT_VIDEOBUF)data) < 0)
	{		
		DBG_PRINTF("can not Get_Video\n");
	}

	return data;
}

int main(int argc, char **argv)
{
	int error;
    int video_pixel_format;
    int display_pixel_format;
	
    int lcd_width;
    int lcd_height;
    int lcd_bpp;
    int topleft_x;
    int topleft_y;
	
    T_VIDEOBUF video_buf;   
    T_VIDEOBUF convert_buf;
    T_VIDEOBUF framebuf;
	
    PT_VIDEOBUF cur_video_buf;
    PT_VIDEOCONVERTOPR video_convert_opr;   /**< 儲存一幀數據的資訊 */
	
	pthread_t recv_video_Id;

    /*!
     * 初始化偵錯系統
     */
	error = DebugInit();
	if (error) {
		printf(APP_ERR"DebugInit error! File:%s Line:%d\n", __FILE__, __LINE__);
		return -1;
	}
	
	error = InitDebugChanel();
	if (error) {
		printf(APP_ERR"InitDebugChanel error! File:%s Line:%d\n", __FILE__, __LINE__);
		return -1;
	}

    /*!
     * 操作資訊提示:./mjpg_streamer_client 192.168.7.0
     */
    if (argc != 2)
    {
        DebugPrint(APP_NOTICE"Usage:\n");
        DebugPrint(APP_NOTICE"%s <ip>\n", argv[0]);
        return -1;
    }
   
    /*!
     * 註冊顯示模組
     */
    error = DisplayInit();
    if (error)
    {
        DebugPrint(APP_ERR"DisplayInit err\n");
        return -1;
    }
    
    /* 選擇顯示裝置 */
    SelectAndInitDefaultDispDev("crt");

    /* 獲取顯示屏參數 */
    GetDispResolution(&lcd_width, &lcd_height, &lcd_bpp);

    /* 獲取顯示屏視訊記憶體 */
    GetVideoBufForDisplay(&framebuf);

    /* 獲取顯示器格式 */
    display_pixel_format = framebuf.pixel_format;
    
    /*!
     * 註冊視訊數據接收模組 
     */
    error = VideoRecvInit();
    if (error)
    {
        DebugPrint(APP_ERR"VideoInit err\n");
        return -1;
    }    

    /*!
     * 顯示視訊獲取通道
     */
	ShowVideoRecv();

    /* 獲取視訊獲取操作函數 */
	s_video_recv_opr = GetVideoRecv("http");

    /* 獲取視訊數據格式 */
	video_pixel_format = s_video_recv_opr->GetFormat();

    /*!
     * 註冊轉換模組
     */
    error = VideoConvertInit();
    if (error)
    {
        DebugPrint(APP_ERR"VideoConvertInit err\n");
        return -1;
    }
    
    /* 獲取支援格式的轉換處理結構體 */
    video_convert_opr = GetVIdeoConvertForFormats(video_pixel_format,
                                            display_pixel_format);
    if (video_convert_opr == NULL)
    {
        DebugPrint(APP_ERR"Can not support this format convert\n");
        return -1;
    }
    
    /*!
     * 與伺服器端建立連線
     */
	if(s_video_recv_opr->ConnectToServer(&socket_client, argv[1]) < 0)
	{		
		DebugPrint(APP_ERR"Can not Connect_To_Server\n");
		return -1;
	}
    
    /*!
     * 發送報文給伺服器,告訴需要其所發送的數據型別與要求
     */
	if(s_video_recv_opr->Init(&socket_client) < 0)
	{
		DebugPrint(APP_ERR"Can not Init\n");
		return -1;
	}

    /*!
     * 清除video_buf的數據,併爲其分配儲存一幀數據的記憶體
     */
    memset(&video_buf, 0, sizeof(T_VIDEOBUF));
    video_buf.pixel_data.PixelDatas = (unsigned char *)malloc(30000);

    /*!
     * 清除convert_buf的數據,並設定其顯示格式與bpp
     */
	memset(&convert_buf, 0, sizeof(T_VIDEOBUF));
	convert_buf.pixel_format   = display_pixel_format;
    convert_buf.pixel_data.bpp = lcd_bpp;

    /* 初始化 video_buf.db 成員 */
	if(pthread_mutex_init(&video_buf.db, NULL) != 0)		
	{
		return -1;
	}
    
    /* 初始化 video_buf.db_update(條件變數) 成員 */
	if(pthread_cond_init(&video_buf.db_update, NULL) != 0)	
	{
		DBG_PRINTF("could not initialize condition variable\n");
		return -1;
	}
	
	/*!
	 * 建立獲取視訊數據的執行緒 
	 */
	pthread_create(&recv_video_Id, NULL, &recv_video_thread, &video_buf);

    /*!
     * 處理攝像頭數據
     * 如果沒有按鍵輸入,則回圈顯示,否則退出
     */
    while(1)
    {
        /* 等待數據的更新 */
		pthread_cond_wait(&video_buf.db_update, &video_buf.db);
        cur_video_buf = &video_buf;
    
        /*!
         * 轉換爲RGB 
         */
        if (video_pixel_format != display_pixel_format)
        {
            error = video_convert_opr->Convert(&video_buf, &convert_buf);
			DebugPrint(APP_ERR"Convert is begin\n");
            if (error)
            {
                DebugPrint(APP_ERR"Convert for %s err\n", argv[1]);
                /*! 
                 * 由於網路的問題可能會出現一幀的數據非jpeg數據,
                 * 使用continue可忽略這種情況 
                 */
                continue; 
            }

            cur_video_buf = &convert_buf;
        }

        /* 居中顯示,計算此時的左上角座標 */
        topleft_x = (lcd_width - cur_video_buf->pixel_data.width) / 2;
        topleft_y = (lcd_height - cur_video_buf->pixel_data.height) / 2;
        
        PicMerge(topleft_x, topleft_y, &cur_video_buf->pixel_data, &framebuf.pixel_data);
    
        /*!
         * 把framebuffer的數據刷到虛擬機器,顯示 
         */
        FlushPixelDatasToDev(&framebuf.pixel_data);     
    }

   pthread_detach(recv_video_Id);		// 等待執行緒結束,以便回收它的資源
   
   return 0;
    
}