基於Linux的視頻傳輸系統(上大學時參加的一個大賽的論文)

文件夾 html

1原創性聲明----------------------------------------------------3 算法

2 摘要----------------------------------------------------------4 編程

3系統方案------------------------------------------------------4 canvas

3.1功能與指標----------------------------------------------4 數組

3.2方案選擇與論證------------------------------------------4 緩存

系統組成框圖--------------------------------------------4 網絡

硬件平臺介紹------------------------------------------------------------------4 session

視頻採集方案的選擇---------------------------------------------------------4 數據結構

視頻編碼方案的選擇---------------------------------------------------------5 框架

視頻傳輸方案的選擇---------------------------------------------------------6

顯示方案選擇------------------------------------------------------------------6

3.4系統軟件實現--------------------------------------------6

3.4.1 server--------------------------------------------6

1)視頻採集模塊-------------------------------------6

2)視頻壓縮模塊------------------------------------10

3)網絡傳輸發送模塊---------------------------------13

3.4.2 client--------------------------------------------19

1)網絡傳輸接收模塊--------------------------------19

2)視頻解碼模塊------------------------------------19

3)視頻顯示模塊------------------------------------22

四 系統測試----------------------------------------------------25

附錄:源碼

參考書目

 

 

 

 

 

 

 

 

 

2006年英特爾杯大學生電子設計競賽嵌入式系統專題邀請賽

 

參賽做品原創性聲明

 

本人鄭重聲明:所呈交的參賽做品報告,是本人和隊友獨立進行研究工做所取得的成果。除文中已經註明引用的內容外,本論文不包括不論什麼其它我的或集體已經發表或撰寫過的做品成果,不侵犯不論什麼第三方的知識產權或其它權利。本人全然意識到本聲明的法律結果由本人承擔。

 

 

 

 

 

 

                                                        參賽隊員簽名:

 

                                                             

日期:          

 

 

 

2 摘要

本系統在LINUX平臺下實現了視頻的採集、壓縮、傳輸及組播,圖象清晰,實時性較好。本設計採用USB攝像頭結合LINUX下自帶的驅動模塊VIDEO4LINUX實現視頻採集。在XVID視頻編解碼平臺下實現視頻的壓縮和解壓。視頻傳輸採用專門爲流媒體傳輸設計的RTP協議,達到了較高的實時性。

ABSTRACT 

On the basis of Linux platform, this system realizes the videodata's collection, compression and network transmission. The videodata's collection is realized through USB camera and Video4Linux. The videodata's coding and decoding is realized under the Xvid platform. And the network transmission is realized by Rtp protocal which is designed for streammedia. All of these make hign real-time performance.

關鍵詞:視頻  RTP   XVID   SDL

3 系統方案

3.1實現功能與指標

本系統可用於足球賽場向場內或場外觀衆提供更逼真的更精彩的比賽畫面,使場內觀衆可以零距離的觀看射門等精彩畫面。用戶可用筆記本電腦由局域網鏈接server,執行client軟件就能夠讚揚近距離的比賽畫面。server由USB攝像頭採集數字視頻信息,通過MPEG4視頻編碼,而後經過JRTP網絡傳輸協議向鏈接到server的client傳輸視頻信息,實現視頻的實時組播。採集到的YUV圖像大約爲100KB壓縮後每幀圖像大小平均爲5KB在局域網環境下延遲小於0.5秒,視頻清晰無失真。鑑於server的主頻限制,組播最大鏈接數爲5,可同一時候向5個用戶提供視頻信息。

3.2方案選擇與論證

系統組成框圖:

USB攝像頭

基於GENE8310的server

 

10

 

網絡

 

遠程登陸主機

 

硬件平臺介紹

GENE-8310AAEON提供的第三代無風扇解決方式,在低功耗狀況下可以獲取更高的性能表現,主要表現在:卓越性能與可控的功耗,多種顯示模式,可擴展性,GENE8310主頻爲 500M 可以作視頻採集處理與傳輸的server。

視頻採集方案的選擇:

LINUX有自帶的攝像頭驅動模塊Video4Linux. Video4Linux爲針對視頻設備的應用程序編程提供一系列的接口函數,對於USB攝像頭,其驅動程序中需要提供主要的I/O操做接口函數 open close的實現 以及內存影射功能和對I/O操做的控制接口函數ioctl等。LINUX下視頻採集例如如下所看到的

視頻應用程序

Video4Linux

設備驅動程序

視頻採集設備

視頻編解碼方案的選擇

Xvid做爲第二代MPEG-4編碼具備多方面的長處,XVIDDIVX開發小組因不滿DIVX被封閉而在其基礎上開發的源代碼開放的視頻編碼解碼平臺。對於第二代的MPEG4視頻編碼內核來講。XVID的各類特色都有表明性和先進意義。1.它支持多種編碼模式:除了最原始的單重估定碼流壓縮(1-pass CBR)以外,XVID提供了包含:單重質量模式動態碼流壓縮單重量化(Quantization)模式動態碼流壓縮、和包含外部控制和內部控制的兩種雙重(2-pass)動態碼流壓縮模式。2.在量化方式上Xvid不只提供了標準的MPEG量化方式,還特意提供了更適合低碼流壓縮的.h 263量化方式。3.除了量化方式迭擇,Xvid還提供了強大的對壓縮過程當中的量化幅度的範圍控制。用戶可以選定壓縮時贊成使用的量化幅度範圍。好比設定一個量化的上限,就可以避免可能出現的畫質大幅降低的狀況。4.在運動偵測(Motion Search)和曲線平衡分配(Curve)方面,XVID對畫面幀進行運動偵測以及對全片斷的運動偵測結果進行分析後,又一次以曲線平衡分配每一幀的量化幅度,以作到:需要高碼流的運動畫面可以分配不少其它空間、更高的碼流、更低的量化幅度來保持畫面的細節;而對於不包含太多運動信息的靜態畫面,則消減分配預算。這樣的把好鋼用在刀刃上的作法,是Xvid做爲第二代MPEG-4編碼的核心內容。5.Xvid提供了多極運動偵測精度,包含半像素插值的技術以16x16像素的微區塊爲單元標示上運動矢量:以及4分運動矢量(inter4v motionvectors)的方式,以8x8的像素區塊爲單元更仔細的紀錄運動向量以供二重分析。6.動態關鍵幀距是還有一個Xvid所具備的,在空間和畫面之間得到最大平衡的技術。咱們知道在視頻壓縮中不是每一幀都記錄着全部的畫面信息,其實惟獨關鍵幀記錄着完整的畫面信息,而興許的P(P-Frame)不過紀錄下與以前一幀的差值。假設關鍵幀之間的畫面變化很是大,則會浪費寶貴的空間在P-Frame;而增長把變化很是大的那一幀記錄在關鍵幀裏,那麼由於興許的幀再也不有更大的變化,就可以節省P幀所需的空間。所以,依據畫面鏡頭切換和運動幅度來變換關鍵幀的位置,對於視頻壓縮下的畫面質量提升,就有着事半功倍的效果。

鑑於XVID以上種種長處,咱們採用XVID實現視頻的編解碼。

視頻傳輸方案的選擇

視頻傳輸可以選擇TCPUDPTCP是一個面向鏈接協議,傳輸信息前需要創建鏈接,系統資源開銷大,但可靠性較高。UDP是一個無鏈接協議,數據傳輸以前源端和終端不需要創建鏈接,資源開銷小,實時性較高。

實時傳輸協議(Real-time Transport ProtocolRTP)是在Internet上處理多媒體數據流的一種網絡協議,利用它能夠在一對一(Unicast,單播)或者一對多(Multicast,多播)的網絡環境中實現傳流媒體數據的實時傳輸。RTP一般使用UDP來進行多媒體數據的傳輸,具備UDP傳輸的長處。

鑑於可靠性考慮,在本系統中,信息傳輸以前server和client用TCP創建鏈接。而後server經過RTP向client發送視頻信息,這樣就達到了可靠性和實時性的平衡。

顯示方案選擇

SDLSimple DirectMedia Layer)是一個跨平臺的多媒體遊戲支持庫。當中包括了對圖形、聲音、線程等等的支持,眼下可以執行在不少平臺上,當中包括 X WindowX Window with DGALinux FrameBuffer 控制檯等等。

因爲 SDL 專門爲遊戲和多媒體應用而設計開發,因此它對圖形的支持很優秀,尤爲是高級圖形能力,比方 Alpha 混和、透明處理、YUV 覆蓋、Gamma 校訂等等。而且在 SDL 環境中能夠很方便地載入支持 OpenGL Mesa 庫,從而提供對二維和三維圖形的支持。

本系統client接受到的視頻解壓後爲YUV格式,考慮到SDLYUV覆蓋方面的優點,咱們選擇SDL實現視頻信息接收接壓後的顯示。

 

3.3系統軟件實現

 

3.3.1 server

server實現了採集數據而後壓縮後進行實時傳輸,用了三個線程分別實現了視頻的採集壓縮(線程1),經過TCP協議創建鏈接(線程2),壓縮後視頻流的傳輸(線程3)。server應用程序執行後,server即建立線程1進行視頻採集,線程2處於堵塞狀態。一旦有client創建鏈接,則線程2得到clientIP信息。以此IP信息爲參數創建線程3,線程3經過JRTP協議向client傳遞視頻流。此後client繼續處於堵塞狀態,直到有新的client鏈接。server端的重要的模塊包含視頻採集模塊,視頻壓縮模塊,和網絡傳輸發送模塊。

1)視頻採集模塊

Linux內核公開支持的OV511等攝像頭芯片,但由於較陳舊在市面不easy找到。咱們選用LOGITECHQUICKCAM  COOL攝像頭並從網上下載攝像頭驅動程序qc-usb- 0.6.3 .tar.gz而後進行解壓、編譯、安裝。

假定已經搭建好嵌入式Linux的開發環境,如下第一步工做就是USB攝像頭的安裝與驅動。

肯定USB攝像頭被正常驅動後,下一步就是使用Video4Linux提供的API函數集來編寫視頻採集程序。

Linux下,所有外設都被當作是一種特殊的文件,稱爲設備文件。系統調用是內核和應用程序之間的接口,而設備驅動程序則是內核和外設之間的接口。他完畢設備的初始化和釋放、對設備文件的各類操做和中斷處理等功能,爲應用程序屏蔽了外設硬件的細節,使得應用程序可以像普通文件同樣對外設進行操做。

Linux系統中的視頻子系統Video4Linux爲視頻應用程序提供了一套統一的API,視頻應用程序經過標準的系統調用就能夠操做各類不一樣的視頻捕獲設備。Video4Linux向虛擬文件系統註冊視頻設備文件,應用程序經過操做視頻設備文件實現對視頻設備的訪問。

 

Linux下視頻採集流程如圖1所看到的

開啓視頻設備()

獲取設備信息及圖像信息()

初始化窗,顏色模式,幀狀態()

 

捕捉視頻幀數據()

關閉視頻設備()

送壓縮模塊

是否停止採集

終止

 

開始

 

N

Y

 

         2  LINUX下視頻採集流程圖

Video4Linux視頻設備數據結構的定義

struct vdIn {

       int fd;                 //文件描寫敘述符

       char *videodevice ;     //視頻捕捉接口文件

       struct video_mmap vmmap;

struct video_capability videocap;// 包括設備的基本信息(設備名稱、支持的最大最小分辨率、信號源信息等)

 

       int mmapsize;

       struct video_mbuf videombuf;映射的幀信息,實際是映射到攝像頭存儲緩衝區的幀信息,包含幀的大小(size,最多支持的幀數(frames)每幀相對基址的偏移(offset

       struct video_picture videopict;//採集圖像的各類屬性

       struct video_window videowin;

       struct video_channel videochan;

int cameratype ; //可否capture,彩色仍是黑白,是否  能裁剪等等。                                       值如VID_TYPE_CAPTURE

 

       char *cameraname; //設備名稱

       char bridge[9];

       int palette; // available palette

       int channel ; //信號源個數

       int grabMethod ;

       unsigned char *pFramebuffer;//指向內存映射的指針

       unsigned char *ptframe[4];//指向壓縮後的幀的指針數組

       int framelock[4];//

       pthread_mutex_t grabmutex;// 視頻採集線程和傳輸線程的相互排斥信號

       int framesizeIn ;// 視頻幀的大小

       volatile int frame_cour;// 指向壓縮後的幀的指針數組下標

       int bppIn;// 採集的視頻幀的BPP

       int  hdrwidth;// 採集的視頻幀的寬度

       int  hdrheight;// 採集的視頻幀的高度

       int  formatIn;//採集的視頻幀的格式

       int signalquit;//中止視頻採集的信號  

       };

在視頻採集以前,先要對Video4Linux進行初始化

初始化階段用ioctl(int fd, ind cmd, …) 函數和設備進行對話Fd是設備的文件描寫敘述符,cmd是用戶程序對設備的控制命令 ,省略號一般是一個表示類型長度的參數,也可以沒有。初始化過程例如如下:

1.打開視頻:

open (vd->videodevice, O_RDWR))

2. video_capability 中信息包含設備名稱,支持最大最小分辨率,信號源信息等。調用函數ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap))成功後可讀取vd->capability各份量 

3.對採集圖象的各類屬性進行設置,分爲兩步 首先獲取攝象頭緩衝區中video_picture中信息調用函數ioctl(vd->fd, VIDIOCGPICT, &(vd->picture));而後改變video_picture中份量的值,爲vd->videopict份量賦新值,調用ioctl (vd->fd, VIDIOCSPICT, &vd->videopict)就能夠實現

4.對圖象截取有兩種方式:第一種是用read()直接讀取數據,另一種是用mmap是把設備文件映射到內存,用內存映射法一個顯而易見的優勢是效率高,因爲進程可以直接讀寫內存,而不需要不論什麼數據的拷貝,因此咱們選擇這樣的方法。詳細作法是

1        獲取攝象頭存儲緩衝區的幀信息調用ioctl (vd->fd, VIDIOCGMBUF, &(vd->videombuf))

2把攝象頭相應的設備文件映射到內存區。調用函數vd->pFramebuffer =    (unsigned char *) mmap (0, vd->videombuf.size, PROT_READ | PROT_WRITE,  MAP_SHARED, vd->fd, 0),成功調用後設備文件內容映射到內存區,

返回的映象內存區指針給vd->pFramebuffer,失敗時返回-1

3改動vd->vmmap中的設置,好比設置圖象幀的垂直水平分辨率,彩色顯示格式   vd->vmmap.height = vd->hdrheight;

         vd->vmmap.width = vd->hdrwidth;

         vd->vmmap.format = vd->formatIn;

圖象採集可分爲單幀採集和連續幀採集,在本系統中採用連續幀採集的方法採集。將vd->videombuf.framese的值賦爲2肯定採集完成攝像頭幀緩衝區幀數據進行循環的次數。在循環語句中,採集當中的vd->pFramebuffer + vd->videombuf.offsets[vd->vmmap.frame],使用ioctl (vd->fd, VIDIOCMCAPTURE, &(vd->vmmap)函數,若調用成功,則激活設備真正開始一幀圖像的截取,是非堵塞的。接着使用ioctl (vd->fd, VIDIOCSYNC,&vd->vmmap.frame) 函數推斷該幀圖像是否截取完成,成功返回表示截取完成,以後就可將採集到的幀進行壓縮,而後將壓縮後的文件保存到發送緩衝區中。最後改動  vd->vmmap.frame vd->frame_cour的值進行下一次採集。

2)視頻壓縮模塊

對圖像幀的編碼是經過調用xvidcore- 1.1.0 函數庫的函數實現的在使用XVID以前要對XVID進行初始化,在初始化過程當中,首先對編碼器的各項參數即結構體xvid_enc_create中的各成員進行設定,而後調用xvid_encore(NULL, XVID_ENC_CREATE, &xvid_enc_create, NULL)創建編碼器,初始化函數例如如下:

Int  enc_init(int use_assembler)

{

       int xerr;

      xvid_plugin_single_t single;         //運算參數

       xvid_plugin_2pass1_t rc2pass1;

       xvid_plugin_2pass2_t rc2pass2;

       xvid_enc_plugin_t plugins[7];

       xvid_gbl_init_t xvid_gbl_init;          //xvid初始化參數

       xvid_enc_create_t xvid_enc_create;     //xvid編碼參數

       /* Set version -- version checking will done by xvidcore */

       memset(&xvid_gbl_init, 0, sizeof(xvid_gbl_init));

       xvid_gbl_init.version = XVID_VERSION;

    xvid_gbl_init.debug = 0;                   //設置版本

 

 

       /* Do we have to enable ASM optimizations ? */

       if (use_assembler) {

              xvid_gbl_init.cpu_flags = 0;

       }

       xvid_global(NULL, XVID_GBL_INIT, &xvid_gbl_init, NULL);  //初始化

      

/*------------------------------------------------------------------------

        * XviD 編碼器初始化

        *----------------------------------------------------------------------*/

       memset(&xvid_enc_create, 0, sizeof(xvid_enc_create));

       xvid_enc_create.version = XVID_VERSION;   //設置版本

 

 

 

       xvid_enc_create.width = XDIM;     //編碼器輸入寬度

       xvid_enc_create.height = YDIM;    //編碼器輸入高度

       xvid_enc_create.profile = XVID_PROFILE_S_L3;   //編碼的框架級別

 

       /* init plugins  */

    xvid_enc_create.zones = NULL;

    xvid_enc_create.num_zones = 0;

 

       xvid_enc_create.plugins = NULL;

       xvid_enc_create.num_plugins = 0;

 

       /* No fancy thread tests */

       xvid_enc_create.num_threads = 0;

 

       /* Frame rate - Do some quick float fps = fincr/fbase hack */

       if ((ARG_FRAMERATE - (int) ARG_FRAMERATE) < SMALL_EPS) {

              xvid_enc_create.fincr = 1;

              xvid_enc_create.fbase = (int) ARG_FRAMERATE;

       } else {

              xvid_enc_create.fincr = FRAMERATE_INCR;

              xvid_enc_create.fbase = (int) (FRAMERATE_INCR * ARG_FRAMERATE);

       }

 

    if (ARG_MAXKEYINTERVAL > 0) {

        xvid_enc_create.max_key_interval = ARG_MAXKEYINTERVAL;

    }else {

           xvid_enc_create.max_key_interval = (int) ARG_FRAMERATE *10;

    }

                                                   //關鍵幀之間的間距

 

 

       xvid_enc_create.max_bframes = 0;            //B幀設置

       xvid_enc_create.bquant_ratio = 150;

       xvid_enc_create.bquant_offset = 100;

 

      

       xvid_enc_create.frame_drop_ratio = 0;   //編碼棄幀率   0100

 

       /* Global encoder options */

       xvid_enc_create.global = 0;

 

       /* I use a small value here, since will not encode whole movies, but short clips */

       xerr = xvid_encore(NULL, XVID_ENC_CREATE, &xvid_enc_create, NULL);/*建立編碼器,但建立編碼器後編碼器並不當即工做,編碼器真正工做時是在enc_main函數中調用ret = xvid_encore(enc_handle, XVID_ENC_ENCODE, &xvid_enc_frame, &xvid_enc_stats)*/

 

       enc_handle = xvid_enc_create.handle;

 

       return (xerr);

}

 

在編碼過程當中一般是讓編碼器自行認爲何時候產生I幀,但爲了提升容錯性或者減少網絡傳輸量,會增大或減少I幀的產生頻率。I幀的控制由參數經過xvid_enc_create.max_key_interval來絕定,當它設置成-1時,Xvid系統本身主動選擇當前編碼是否爲I幀或P幀。當網絡情況比較良好時(丟包數較少),可以適當下降I幀數量,這樣可以提升服務質量。當網絡丟包率上升時,就要考慮添加I幀數量,這樣可以更快更好地修正、掩蓋錯誤。

XVID編碼的主函數爲例如如下:

Int  enc_main(unsigned char *image,

               unsigned char *bitstream,

               int *key,

               int *stats_type,

               int *stats_quant,

               int *stats_length,

 

               int sse[3])

{

       int ret;

 

       xvid_enc_frame_t xvid_enc_frame;

       xvid_enc_stats_t xvid_enc_stats;

 

       memset(&xvid_enc_frame, 0, sizeof(xvid_enc_frame));

       xvid_enc_frame.version = XVID_VERSION;        //幀版本

 

       memset(&xvid_enc_stats, 0, sizeof(xvid_enc_stats));

       xvid_enc_stats.version = XVID_VERSION;    //編碼狀態版本

 

       /* Bind output buffer */

       xvid_enc_frame.bitstream = bitstream;

       xvid_enc_frame.length = -1;

 

       /* Initialize input image fields */

       if (image) {

              xvid_enc_frame.input.plane[0] = image;

              xvid_enc_frame.input.csp = XVID_CSP_I420;    //視頻輸入格式

              xvid_enc_frame.input.stride[0] = XDIM;       

       } else {

              xvid_enc_frame.input.csp = XVID_CSP_NULL;

       }

 

       xvid_enc_frame.vol_flags = 0;

 

       xvid_enc_frame.vop_flags = vop_presets[ARG_QUALITY];

 

       /* Frame type -- let core decide for us */

       xvid_enc_frame.type = XVID_TYPE_AUTO;//本身主動決定幀格式

 

       /* Force the right quantizer -- It is internally managed by RC plugins */

       xvid_enc_frame.quant = 3;

 

       xvid_enc_frame.motion = motion_presets[ARG_QUALITY];

 

       /* We don't use special matrices */

       xvid_enc_frame.quant_intra_matrix = NULL;

       xvid_enc_frame.quant_inter_matrix = NULL;

 

       ret = xvid_encore(enc_handle, XVID_ENC_ENCODE, &xvid_enc_frame,

                                     &xvid_enc_stats);//編碼並把編碼狀態存入xvid_enc_stats

 

       *key = (xvid_enc_frame.out_flags & XVID_KEYFRAME);

       *stats_type = xvid_enc_stats.type;

       *stats_quant = xvid_enc_stats.quant;

       *stats_length = xvid_enc_stats.length;

       sse[0] = xvid_enc_stats.sse_y;

       sse[1] = xvid_enc_stats.sse_u;

       sse[2] = xvid_enc_stats.sse_v;

       return (ret);

}

3)網絡傳輸發送模塊

 

流媒體協議分析:

實時傳輸協議(Real-time Transport ProtocolRTP)是在Internet上處理多媒體數據流的一種網絡協議,利用它能夠在一對一(Unicast,單播)或者一對多(Multicast,多播)的網絡環境中實現傳流媒體數據的實時傳輸。RTP一般使用UDP來進行多媒體數據的傳輸,整個RTP協議由兩個密切相關的部分組成:RTP數據協議和RTP控制協議。RTP是眼下解決流媒體實時傳輸問題的最好辦法,在Linux平臺上進行實時流媒體編程,咱們採用了開放源碼的RTPJRTPLIB 3.5.2 JRTPLIB是一個面向對象的高度封裝後的RTP庫,它全然遵循RFC 3550設計,是一個很是成熟的RTP,而且眼下仍在維護中。JRTPLIB提供了簡單易用的API供程序開發人員使用,它使得咱們僅僅需關注發送與接收數據,控制部分(RTCP jrtplib內部實現。

 RTP數據協議

RTP數據協議負責對流媒體數據進行封包並實現媒體流的實時傳輸,每一個RTP數據報都由頭部(Header)和負載(Payload)兩個部分組成,當中頭部前12個字節的含義是固定的,而負載則可以是音頻或者視頻數據。RTP數據報的頭部格式如圖3.1所看到的:

 

V=2

P

X

CC

M

PT

序列號

                         時間戳

                       同步源標識(SSRC)

                       提供源標識(CSRC)

3.1 RTP頭部格式

當中幾個域及其意義例如如下:

版本 (V): 標明RTP協議版本。

補充位(P):假設該位被設置,則在該packet末尾包括了額外的附加信息。

擴展位(X):假設該位被設置,則在固定的頭部後存在一個擴展頭部。

 標記位 (M): 該位的功能依詳細應用的而定。咱們將其做爲一結束的標誌。當M=1時,表示一幀的結束,下一個發送或接收的RTP數據包爲新的一幀。

CSRC記數(CC):表示CSRC標識的數目。CSRC標識緊跟在RTP固定頭部以後,用來表示RTP數據報的來源。

   PT

  編碼標準

採樣速率(HZ

     26

JPEG

90000

     31

H.261

90000

   34

h.263

90000

負載類型(PT):標明RTP負載的格式,包含所採用的編碼算法、採樣頻率等。常用的PT值如圖3.2所看到的:

 

 

 

 

3.2 常用的負載類型及PT

序列號:用來爲接收方提供探測數據丟失的方法,但怎樣處理丟失的數據則由應用程序負責,RTP協議自己並不負責數據的重傳。

 時間戳:記錄了負載中第一個字節的採樣時間,接收方根據時間戳能夠肯定數據的到達是否受到了延遲抖動的影響,但詳細怎樣來補償延遲抖動則由應用程序自己負責。

RTP數據報包括了傳輸媒體的類型、格式、序列號、時間戳以及是否有附加數據等信息,這些都爲實時的流媒體傳輸提供了對應的基礎。RTP中沒有鏈接的概念,它可以創建在底層的面向鏈接或面向非鏈接的傳輸協議之上;RTP也不依賴於特別的網絡地址格式,而只只需要底層傳輸協議支持組幀(Framing)和分段(Segmentation)就足夠了;RTP自己不提供不論什麼可靠性機制,這些需要由傳輸協議或者應用程序自己來保證。RTP一般是在傳輸協議之上做爲詳細的應用程序的一部分加以實現的,如圖3.3所看到的:

詳細的應用程序

RTP/RTCP

UDP

TCP

             IPv4/IPv6

局域網/廣域網

 

 

 

 

3.3 RTP與其餘協議的關係

RTCP控制協議

RTCP控制協議與RTP數據協議一塊兒配合使用,當應用程序啓動一個RTP會話時將同一時候佔用兩個port,分別供RTPRTCP使用。RTP自己並不能爲按序數據傳輸包提供可靠的保證,也不提供流量控制和擁塞控制,這些都由RTCP來負責完畢。一般RTCP會採用與RTP一樣的分發機制,向會話中的所有成員週期性地發送控制信息,應用程序經過接收這些數據,從中獲取會話參與者的相關資料,以及網絡情況、分組丟失機率等反饋信息,從而能夠對服務質量進行控制或者對網絡情況進行診斷。RTCP協議的功能是經過不一樣的RTCP數據報來實現的,主要有例如如下幾種類型:

發送端報告(SR):發送端是指發出RTP數據報的應用程序或者終端,發送端同一時候也可以是接收端。

接收端報告(RR):接收端是指僅接收但不發送RTP數據報的應用程序或者終端。

源描寫敘述(SDES):主要功能是做爲會話成員有關標識信息的載體,如username、郵件地址等,此外還具備向會話成員傳達會話控制信息的功能。

通知離開(BYE):主要功能是指示某一個或者幾個源再也不有效,即通知會話中的其它成員本身將退出會話。

RTCP數據報攜帶有服務質量監控的必要信息,能夠對服務質量進行動態的調整,並能夠對網絡擁塞進行有效的控制。由於RTCP數據報採用的是多播方式,所以會話中的所有成員均可以經過RTCP數據報返回的控制信息,來了解其它參與者的當前狀況。

通常應用場合下,發送媒體流的應用程序將週期性地產生髮送端報告SR,該RTCP數據報含有不一樣媒體流間的同步信息,以及已經發送的數據報和字節的計數,接收端依據這些信息可以預計出實際的傳輸數據速率。還有一方面,接收端會向所有已知的發送端發送接收端報告RR,該RTCP數據報含有已接收數據報的最大序列號、丟失的數據報數目、延時抖動和時間戳等重要信息,發送端應用依據這些信息可以預計出往返時延,並且可以依據數據報丟失機率和時延抖動狀況動態調整發送速率,以改善網絡擁塞情況,或者依據網絡情況平滑地調整應用程序的服務質量。

環境搭建 JRTPLIB是一個用C++語言實現的RTP庫。爲Linux 系統安裝JRTPLIB,從JRTPLIB的站點http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.html下載最新的源代碼包jrtplib- 3.5.2 .tar.bz2。同一時候爲了增長對線程的支持,需要單獨下載jthread1.2.0.tar.bz2.。將下載後的源代碼包保存在/usr/local/src文件夾下,運行如下的命令對其進行解壓縮

bzip2 -dc jrtplib- 3.5.2 b.tar.bz2 | tar xvf –

接下去對JRTPLIB進行配置和編譯:

cd jrtplib- 3.5.2

./configure

make

再運行例如如下命令完畢JRTPLIB的安裝:

make install

依照此步驟再安裝jthread 1.2.0

流媒體編程

JRTPLIB進行實時流媒體傳輸數據以前,首先應該生成RTPSession類的一個實例來表示這次RTP會話; 
     
RTPSession session; 
    
RTPSession 類的構造函數中需要一個代表UDP協議類型的參數,是基於Ipv4 
    
仍是基於Ipv6,構造函數默認的協議爲Ipv4 
     
在真正建立會話以前還需設置兩個參數  
     
第一個參數爲設置恰當的時戳單元 
     
RTPSessionParams sessionparams; 
    
sessionparams.SetOwnstampUnit(1.0/90000.0); 
    
函數SetOwnstampUnit(1.0/90000.0)的參數1.0/90000.0表示的是以秒爲單元的時戳單元。當使用RTP會話傳輸90000Hz採樣的視頻數據時,由於時戳每秒鐘將遞增90000.0,因此時戳單元應該被設置成1/90000.0。假設是音頻數據,則設置爲1.0/8000.0 
      
第二個參數爲一個指向RTPTransmissionParams實例的指針。當採用IPv4協議時,應使用類RTPUDPv4TransmissionParams,當中要設置的參數爲傳輸數據所用的端口號。 
      
RTPUDPv4TransmissionParams transparams; 
     
transparams.SetPortbase(localportbase); 
     
而後就可以調用RTPSession create()函數真正建立會話: 
     
int status=session.Create(sessionparams,&transparams); 
    

假設RTP會話建立過程失敗,Create()方法將返回一個負數,經過它儘管可以很是easy地推斷出函數調用到底是成功的仍是失敗,卻很是難明確出錯的緣由究竟什麼。JRTPLIB採用了統一的錯誤處理機制,它提供的所有函數假設返回負數就代表出現了某種形式的錯誤,而詳細的出錯信息則可以經過調用RTPGetErrorString()函數獲得。該函數將錯誤代碼做爲參數傳入,而後返回該錯誤代碼所相應的錯誤信息。

下一步就是設置發送數據的目標地址和目標端口號: 
     
RTPIPv4ADDRESS addr(desIP,desportbase); 
    
session.AddDestination(addr); 
    
其餘需要設置的參數有默認負載類型,是否設標誌位,時間戳增量。 
     
session.SetDefaultPayloadType(); 
    
session.SetDefaultMark(); 
    
session.SetDefaultTimestampIncrement(); 
    
真正發送數據是經過調用SendPacket()函數實現的。 
    
int SendPacket(const void *data,size_t len,uint8_t pt, 
    
bool mark,uint32_t timestampinc); 
    
參數data指針指向要發送的數據,數據的長度爲len,負載類型爲pt, mark爲標誌位,取值爲01,可以使用此標誌位推斷一幀的開始或結束。 
     
時間戳增量timestampinc 用於表示是不是同一幀數據,對於同一幀數據設置同一時間戳。接收端也可以根據時間戳來推斷一幀數據的開始或結束。 
     
int SendPacket(const void *data,size_t len,uint8_t pt, 
    
bool mark,uint32_t timestampinc,uint16_t hdrextID, 
    
const void *hdrextdata,size_t numhdrextwords); 
    
此函數用於發送帶附加報頭的數據幀。hdrextID用於對不一樣的報頭進行編號, 
      
hdrextdata爲指向頭數據的指針,報頭長度爲 numhdrextwords      
    

程序流程框圖:

開始發送

該幀大於1400字節?

經過RTP發送數據

將該拆成幾個不大於1400字節的數據包

幀並發送給解碼線程

 

接收到RTP包時間戳與上一個一樣?

網絡

 

循環接收直到不一樣一時候間戳的RTP包

經過RTP接收數據

 

             圖3 網絡發送接收程序流程圖

對程序流程圖的說明:

1)發送端拆幀的算法例如如下:                      

If (該數據幀小於1400個字節){

直接用RTPSessio::SendPacket()發送出去;

}

else

{

 把該幀拆成1400個字節一個包再發送。對於同一幀數據,採用一樣的時間戳來標記,以利於接收端對數據的接收。

}

2)接收端組幀算法例如如下:

while(RTP包的時間戳和上一個RTP包的時間戳一樣)

{

說明該RTP包和上一個RTP包屬於同一個視頻幀的數據。

把接收到的數據保存在緩存中。}

而後把屬於同一視頻幀的數據組裝好,發送給解碼線程。採用拆幀方法傳輸視頻數據比直接發送丟包率更低,且實時性更強,傳輸質量明顯提升。

3.3.2 client

1)網絡傳輸接收模塊

咱們在使用Jrtp 庫的同一時候增長了Jthread 的支持,使得詳細的數據接收在後臺執行,僅僅需改寫類RTPSession的成員函數OnPollThreadStep()ProcessRTPPacket(const RTPSourceData &srcdat,const RTPPacket &rtppack)由於同一個RTP會話中贊成有多個參與者,經過調用RTPSession類的GotoFirstSourceWithData()GotoNextSourceWithData()方法來遍歷那些攜帶有數據的源。在函數OnPollThreadStep()中,爲了正確接收同一數據源的數據據,必須先對數據源表加鎖。經過調用BeginDataAccess()實現,當正確接收一個數據報後,調用EndDataAccess()實現對數據源表的解鎖。

在函數ProcessRTPPacket (const RTPSourceData & srcdat,const RTPPacket & rtppack)

中對接收到的數據包進行處理。

首先調用

char * payloadpointer = (char *)rtppack.GetPayloadData ()獲得源數據包的指針,並經過rtppack.HasMarker ()來推斷是不是一幀的結束。由於一幀數據要分紅多個數據包進行傳送,需要將收到的包臨時保存到內存緩衝區中,等到有足夠的幀數據後再調用解碼線程進行解碼。memcpy(receivepointer,payloadpointer,rtppack.GetPayloadLength ())將數據臨時保存在receivepointer指向的內存緩衝區中。假設一幀數據結束則設置標誌位並做對應處理。

JRTPLIBRTP數據報定義了三種接收模式,當中每種接收模式都詳細規定了哪些到達的RTP數據報將會被接受,而哪些到達的RTP數據報將會被拒絕。經過調用RTPSession類的SetReceiveMode()方法可以設置下列這些接收模式:RECEIVEMODE_ALL爲缺省的接收模式,所有到達的RTP數據報都將被接收;

RECEIVEMODE_IGNORESOME除了某些特定的發送者以外,所有到達的RTP數據報都將被接收,而被拒絕的發送者列表可以經過調AddToIgnoreList()DeleteFromIgnoreList()ClearIgnoreList()方法來進行設置; RECEIVEMODE_ACCEPTSOME:除了某些特定的發送者以外,所有到達的RTP數據報都將被拒絕,而被接受的發送者列表可以經過調用AddToAcceptList ()DeleteFromAcceptListClearAcceptList ()方法來進行設置。

2)視頻解碼模塊

client解碼的流程圖例如如下所看到的:

設置解碼幀緩衝區

中止顯示

解碼一幀

VOL

Y

N

Y

N

SDL顯示

釋放解碼緩衝區

終止

開始

Dec_init()解碼初始化

當client接收到發送來的壓縮後的視頻信息後,調用視頻解碼模塊將碼流解碼成可以播放的YUV格式。在調用解碼模塊以前要正確的初始化解碼器。初始化解碼器的程序例如如下所看到的:

 

static int

dec_init(int use_assembler, int debug_level)

{

       int ret;

 

       xvid_gbl_init_t   xvid_gbl_init;

       xvid_dec_create_t xvid_dec_create;

 

       memset(&xvid_gbl_init, 0, sizeof(xvid_gbl_init_t));

       memset(&xvid_dec_create, 0, sizeof(xvid_dec_create_t));

 

       /*------------------------------------------------------------------------

        * XviD 核心初始化

        *----------------------------------------------------------------------*/

 

       /* Version */

       xvid_gbl_init.version = XVID_VERSION;//版本

 

       /* Assembly setting */

       if(use_assembler)

           xvid_gbl_init.cpu_flags = 0;

       else

              xvid_gbl_init.cpu_flags = XVID_CPU_FORCE;

 

       xvid_gbl_init.debug = debug_level;

 

       xvid_global(NULL, 0, &xvid_gbl_init, NULL);

 

       /*------------------------------------------------------------------------

        * XviD 解碼器初始化

        *----------------------------------------------------------------------*/

 

 

       xvid_dec_create.version = XVID_VERSION;//解碼器版本

 

 

       xvid_dec_create.width = 0;// 幀的寬 本身主動適應

       xvid_dec_create.height = 0;// 幀的高 本身主動適應

 

       ret = xvid_decore(NULL, XVID_DEC_CREATE, &xvid_dec_create, NULL);

//建立解碼實例

 

       dec_handle = (int *)xvid_dec_create.handle;  //傳遞句柄

 

       return(ret);

}

解碼過程調用xvid_decore(dec_handle, XVID_DEC_DECODE, &xvid_dec_frame, xvid_dec_stats)將獲得的解碼狀態放入xvid_dec_stats中,依據xvid_dec_stats再對解碼後的幀進行處理,解碼的主程序例如如下:

static int  dec_main(unsigned char *istream,unsigned char *ostream, int istream_size,xvid_dec_stats_t *xvid_dec_stats)

{

 

       int ret;

       xvid_dec_frame_t xvid_dec_frame;

 

       /* Reset all structures */

       memset(&xvid_dec_frame, 0, sizeof(xvid_dec_frame_t));

       memset(xvid_dec_stats, 0, sizeof(xvid_dec_stats_t));

 

       /* Set version */

       xvid_dec_frame.version = XVID_VERSION;  // 幀版本

       xvid_dec_stats->version = XVID_VERSION;  //解碼狀態版本

 

       /* No general flags to set */

       xvid_dec_frame.general          = 0;

 

       /* Input stream */

       xvid_dec_frame.bitstream        = istream;  //指向輸入的視頻流的指針

       xvid_dec_frame.length           = istream_size;//輸入視頻流的大小

 

       /* Output frame structure */

       xvid_dec_frame.output.plane[0]  = ostream; //指向輸出的視頻流的指針

       xvid_dec_frame.output.stride[0] = XDIM*BPP;

       xvid_dec_frame.output.csp = CSP;//視頻輸出格式

 

       ret = xvid_decore(dec_handle, XVID_DEC_DECODE, &xvid_dec_frame, xvid_dec_stats);

 

       return(ret);

}

3)視頻顯示模塊

在本系統中解碼獲得的YUV格式的視頻流用SDL顯示,顯示的流程圖例如如下所看到的:

推斷幀長寬變化

重建YUVOverlay

Y

N

將幀信息複製到顯示緩衝區

鎖定SDL

解鎖SDL

接收一幀並解壓

顯示

檢測鍵盤信息QUIT

 

N

Y

初始化SDL

 

結束,調用SDLQUIT()

 

            

SDL的初始化步驟例如如下:

首先調用SDL_Init (SDL_INIT_VIDEO) 初始化SDL的視頻顯示系統,而後調用函數SDL_SetVideoMode (320, 240, 0, SDL_HWSURFACE | SDL_DOUBLEBUF| SDL_ANYFORMAT| SDL_RESIZABLE)設置視頻模式,包含長,寬,和 BPP,參數SDL_HWSURFACE創建一個帶視頻存儲的SURFACESDL_DOUBLEBUF使能雙緩衝區,消除BppSURFACE的影響,SDL_RESIZABLE使窗體大小可調整。

最後調用函數SDL_CreateYUVOverlay(XDIM, YDIM, SDL_YV12_OVERLAY, screen)創建一個長爲XDIM寬爲YDIM,格式爲SDL_YV12_OVERLAYYUV平面,返回一個指向新建SDL_overlay的指針。SDL_overlay結構體例如如下:

typedef struct SDL_Overlay {

        Uint32 format;                          /* Read-only */

        int w, h;                               /* Read-only */

        int planes;                             /* Read-only */

        Uint16 *pitches;                        /* Read-only */

        Uint8 **pixels;                         /* Read-write */

        /* Hardware-specific surface info */
        struct private_yuvhwfuncs *hwfuncs;
        struct private_yuvhwdata *hwdata;

        /* Special flags */
        Uint32 hw_overlay :1;   /* Flag: This overlay hardware accelerated? */

        Uint32 UnusedBits :31;

 } SDL_Overlay;

 

SDL_DisplayYUVOverlay(overlay, &rect) 這就是要顯示YUV的詳細函數,第一個參數是已經建立的YUV平面,第二個參數是一個矩形寬,設置在顯示平面的哪一個區域內顯示。在使用前,需要將要顯示的數據指針送給pixels

              outy = out_buffer;

                            outu = out_buffer+XDIM*YDIM;

                            outv = out_buffer+XDIM*YDIM*5/4;

                            for(y=0;y<screen->h && y<overlay->h;y++)

                            {

                                op[0]=overlay->pixels[0]+overlay->pitches[0]*y;

                                   op[1]=overlay->pixels[1]+overlay->pitches[1]*(y/2);

                                   op[2]=overlay->pixels[2]+overlay->pitches[2]*(y/2);         

                                   memcpy(op[0],outy+y*XDIM,XDIM);

                                   if(y%2 == 0)

                                   {

                                  memcpy(op[1],outu+XDIM/2*y/2,XDIM/2);

                                         memcpy(op[2],outv+XDIM/2*y/2,XDIM/2);   

                            }

                            }
pitches
是指YUV存儲數據相應的stride(步長)

系統測試

測試環境 局域網

延遲

測試系統延時採用網絡時間server(NTP),在視頻傳輸以前從網絡時間server上得到時間,在client接收到視頻播放以前再一次得到時間,計算兩次差值從而獲得延時,屢次測試獲得,在局域網的條件下,延時小於0.1秒。

圖象質量:在局域網的條件下,圖象無失真。

相關文章
相關標籤/搜索