DM8168移植wis-streamer【8168定製】

8168網傳模塊主要由wis-streamer進程進行,主要功能是實現高清視頻和音頻的網絡傳輸,它能夠實現以下幾個方面的功能:

(1)能夠對來自客戶端的視頻點播請求做出響應,實現高清視頻和音頻碼流的實時傳輸功能。

(2)能夠支持單播和組播兩種網絡傳輸方式,且單播支持TCP與UDP方式。


禁止私自轉載(http://blog.csdn.net/guo8113/article/details/50241971)

1.1 啓動方法

./wis-streamer-p 8557 -shm 33 -sem 1221 1220 &

./wis-streamer-s -p 8561 -shm 34 -sem 1225 1224 &

參數說明:

-s  爲靜音單播傳輸

-m  爲組播傳輸

-p 指定端口號

-shm指定共享內存號

-sem指定兩個信號量ID

注:在啓動組播傳輸(組播未測試)前,必須要設定板卡的網關,方法爲:

$routeadd default gw 192.168.1.1 eth0

 

接收視頻流的地址爲:

rtsp://192.168.1.8:8557/PSIA/Streaming/channels/H.264

rtsp://192.168.1.8:8561/PSIA/Streaming/channels/H.264

1.2 主要工作流程

(1)RTSP服務器初始化

首先按照圖3.1開始進行RTSP服務器的初始化,這個過程在wis-streamer.cpp中進行。


(2)RTSP交互處理


圖3.2

RTSP交互主要由RTSPClientSession對象管理和實現交互功能。任務調度器在監聽到有客戶端的連接請求時,會建立RTSPClientSession對象來負責處理與客戶端的對話。在初始化RTSPClientSession對象時,RTSP服務器會將請求處理函數句柄incomingRequestHandler()以及對應的socket句柄一起傳入任務調度器。incomingRequestHandler()的處理請求過程如上圖所示。函數首先從socket讀取請求報文,然後調用parseRTSPRequestString()函數來分析報文中各個字段的內容,提取出指令碼。對於不同的指令碼,進入不同的函數進行處理。

 

(3)RTP打包與發送

a、開啓碼流打包過程

startStream函數內部調用了streamstate->startPlaying(),具體函數實現在MediaSink::startPlaying()中,主要是檢查source和sink是否已經初始化成功。

b、各媒體流子類的特殊處理

MediaSink::startPlaying()函數返回一個虛函數continuePlaying()進行進一步封裝。continuePlaying()函數定義在MultiFramedRTPSink中,具體實現要在其子類中。所以,視頻流將調用H264VideoRTPSink中的continuePlaying()做特殊處理,而音頻碼流不需要處理,然後回到MultiFramedRTPSink中調用buildAndSendPacket()。

c、設置RTP頭

buildAndSendPacket()函數主要進行RTP頭的設置,它主要由版本號、標誌位、負載類型、序列號、時間戳和同步源SSRC等12字節組成。這邊先設置了版本號、負載類型、序列號以及同步源信息,爲時間戳和標誌位留出了位置,因爲要在打包的時候實時更新這兩個數據。

d、開始數據打包

packFrame()函數負責數據的打包,首先檢查是否還有上次打包時剩下的幀數據(因爲一幀數據非常大,不可能一次就打包完),如果還有幀數據,則調用afterGettingFrame1();如果沒有,就要問source要幀數據。由WisInput對象中的readFromfile()函數從共享內存獲取。afterGettingFrame1()函數主要負責處理幀的分片封包。

e、RTP包的發送

sendPacketIfNecessary()函數功能爲控制RTP的發送,通過uSecondsToGo變量控制下次打包的時間來調節音視頻的發送速度,然後調用RTPInterface類中的sendPacket()函數完成數據的發送工作,主要就是給RTP包加上TCP或UDP頭,然後送入socket發送給客戶端。


圖3.3

 

1.3 主要代碼修改

(1)在live555官網下載wis-streamer的開源代碼wis-streamer.tar.gz,在終端輸入tar zxvf wis-streamer.tar.gz進行解壓,將wis-streamer文件夾及其代碼拷貝到live555的解壓文件live下,與live555的4個庫文件BasicUsageEnvironment、groupsock、liveMedia、以及UsageEnvironment放在一個目錄下。

 

(2)在wis-streamer目錄下模仿已有文件WISMPEG4VideoServerMediaSubsession.cpp和WISMPEG4VideoServerMediaSubsession.hh新增文件WISH264VideoServerMediaSubsession.cpp和WISH264VideoServerMediaSubsession.hh.

將WISMPEG4VideoServerMediaSubsession*WISMPEG4VideoServerMediaSubsession

::createNew(UsageEnvironment&env, WISInput& wisInput, unsigned estimatedBitrate) {

  return new WISMPEG4VideoServerMediaSubsession(env,wisInput, estimatedBitrate);

}

改爲:

WISH264VideoServerMediaSubsession*WISH264VideoServerMediaSubsession

::createNew(UsageEnvironment&env, WISInput& H264Input, unsigned estimatedBitrate) {

  return newWISH264VideoServerMediaSubsession(env, H264Input, estimatedBitrate);

}

並且修改RTPSink*WISMPEG4VideoServerMediaSubsession

::createNewRTPSink(Groupsock*rtpGroupsock,

                 unsigned charrtpPayloadTypeIfDynamic,

                 FramedSource*/*inputSource*/) {

  setVideoRTPSinkBufferSize();

  returnMPEG4ESVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);

}

改爲:

RTPSink*WISH264VideoServerMediaSubsession

::createNewRTPSink(Groupsock*rtpGroupsock,

                 unsigned charrtpPayloadTypeIfDynamic,

                 FramedSource*/*inputSource*/) {

  setVideoRTPSinkBufferSize();

  char BuffStr[200];

  extern int GetSprop(void *pBuff);

  GetSprop(BuffStr);

  return H264VideoRTPSink::createNew(envir(),rtpGroupsock,96, 0x640032, BuffStr);

}

其中GetSprop函數在WISInput.cpp中定義,主要是對h.264的SPS和PPS信息進行base64編碼。

 

(3)修改wis-streamer.cpp

a、定義了共享內存的標示符,創建了共享內存。

b、創建TaskScheduler* scheduler =BasicTaskScheduler::createNew();

UsageEnvironment* env =BasicUsageEnvironment::createNew(*scheduler);

TaskSecheduler類是一個任務調度器,是整個Live555的任務調度中心,主要負責任務的調度和執行。UsageEnvironment代表了整個系統的運行環境,主要用於錯誤的輸入和輸出。

c、StreamingMode streamingMode =STREAMING_UNICAST;設置採用的碼流傳送方式爲單播。

d、設定啓動時後面可帶的控制參數,“-m”爲組播,“-s”爲靜音(不要音頻),“-a”爲選擇的音頻編碼方式爲aac。啓動格式如下:./wis-streamer  -s 表示靜音啓動wis-streamer。

e、用fork函數是創建子進程。

對於不同的編碼方式可以進入不同的子進程,這樣就可以同時開幾種編碼方式的服務器,目前註釋掉的原因是現在只加入了H.264的網傳,所以暫時只開了一個進程,後續要添加別的編碼方式時可以將child[ ]的個數增加來增添子進程個數。

f、爲每種編碼格式的視頻設定一些參數,包括video_type,rtspserverport,視頻流bitrate以及音視頻rtpport。

g、初始化每種編碼格式的輸入接口。WISInput類在wisinput.cpp中

H264InputDevice=WISInput::createNew(*env,video_type);

     if(H264InputDevice == NULL) {

          err(*env)<< "Failed to create H264 input device\n";

          exit(1);

h、建立rtsp server, 用來創建socket,並綁定和監聽端口。

RTSPServer* rtspServer =NULL;

rtspServer =RTSPServer::createNew(*env, rtspServerPortNum, NULL);

i、創建描述該媒體流的會話,包括流的名稱和sdp等。

添加視頻流的子回話,此處添加的爲H.264視頻流,包括輸入接口,視頻比特率。

如果音頻不是在靜音情況下,則繼續添加音頻流子回話,此處添加的是G711,也就是PCM的u律。

j、char *url =rtspServer->rtspURL(sms);

獲得服務器的IP地址,此處是自動獲得板卡IP,無需手動設置。

k、env->taskScheduler().doEventLoop();

SingleStep()函數主要完成兩種任務,分別是socket任務(socket handler)和延遲任務(delay task)。Socket任務主要是用於執行socket操作的任務,對已經準備好的socket進行讀寫操作。延遲任務主要是一個延遲隊列,隊列中每一項代表了一個要調度的任務,同時保存了距離執行這個任務剩下的時間,來進行任務的先後調度。

如果是組播模式,chooseRandomIPv4SSMAddress()函數將從[232.0.1.0, 232.255.255.255)隨機選擇一個地址作爲組播地址。

rtpGroupsockVideo = newGroupsock(*env, dest, rtpPortVideo, ttl);

 rtcpGroupsockVideo= new Groupsock(*env, dest, rtcpPortVideo, ttl)

分別爲音視頻設置RTP、RTCP組播端口號,再設置TTL(生存時間)指定數據包被路由器丟棄之前允許通過的網段數量,接着用組播地址和服務器地址構造一個組播組。

創建videosource、audiosource獲取音視頻源碼流,相應的創建videosink和audiosink接收source送來的碼流。

sinkVideo->startPlaying(*sourceVideo,NULL, NULL);

sinkAudio->startPlaying(*sourceAudio,NULL, NULL);

開始向支持組播的路由器發送RTP數據。組播與單播不同的是,它並不需要客戶端請求後纔開啓碼流打包傳輸工作,而是在RTSP服務器初始化階段已經開始傳輸碼流。

 

(4)修改WISInput.cpp

a、加入頭文件share_mem.h、semaphore.h,因爲share_mem.c和semaphore.c爲c語言文件,wis-streamer爲c++文件,引用時方法如下:

extern "C"

{

#include"share_mem.h"

#include"semaphore.h"

}

b、同樣和8168前端一樣,加入音視頻信號量

int semEmpty;  int semFull; int semEmpty_audio;  intsemFull_audio;

c、在class WISOpenFileSource類中加入以下變量:

public:

   int uSecsToDelay;           //任務調度器等待的時間

   int uSecsToDelayMax;       //同上

   int srcType;                //數據源類型,srcType=0爲視頻,srcType=1爲音頻

d、在視頻source類中加入以下變量:

int nal_state;           //nal幀類型

  int startI;              //I幀是否開始

e、在音頻source類中加入以下變量和函數:

  int getAudioData();     //獲取音頻幀數據的函數

  struct timeval fPresentationTimePre;

   int IsStart;          //是否開始傳輸音頻數據

f、改寫creatNew函數

WISInput*WISInput::createNew(UsageEnvironment& env ,int vType) {

  return new WISInput(env,vType);

}

g、改寫wisinput函數,初始化視頻和音頻source

WISInput::WISInput(UsageEnvironment&env,int vType)

  :Medium(env),videoType(vType),fOurVideoSource(NULL),fOurAudioSource(NULL){

}

改寫析構函數~WISInput函數,刪除音視頻source

h、修改voidWISOpenFileSource::doGetNextFrame()函數

改爲只保留incomingDataHandler(this);函數

i、改寫void WISOpenFileSource::incomingDataHandler1()函數

加入對readfromFile函數的返回值的不同處理:

對於ret<0,handleClosure(this);

對於ret=0,延遲一段時間後,繼續執行incomingDataHandler

對於ret>0,執行aftergetting;

 

j、改寫WISVideoOpenFileSource和WISAudioOpenFileSource類的構造函數:

對各成員變量進行初始化,並對delay的時間進行設置。

k、改寫視頻readfromfile函數:intWISVideoOpenFileSource::readFromFile(),這裏的代碼量很大,就不貼出來了,主要做的工作爲從共享內存獲取數據。

在該函數中用到了AV_DATA這個結構體變量,裏面存放了幀序列號(暫時沒用到,先預留着),幀大小,是否是I幀以及時間戳信息。這個結構體裏存放的數據是要從共享內存接數據用的。

ShareMemRead(&av_data,buffer);  進入共享內存讀取數據。在ShareMemRead()函數中,shared_use *shared_stuff這個結構體指針綁定共享內存,這個結構體中包含了視頻結構體VideoBuf,音頻結構體AudioBuf,在8168前端和wis這端定義的必須是一樣的。然後把碼流數據和一些參數從共享內存拷貝到buffer和AV_DATA中。

然後是一個大的if從句:if(av_data.isIframe)判斷如果是I幀的話進入這個從句。這個if從句的目的就是把sps、pps和I幀分開打包。這邊特別要注意的是,在讀到sps和pps時並沒有釋放共享內存,因爲如果讀完就釋放的話,8168前端就會放新的數據進去,所以這邊直到I幀結束才釋放了共享內存。

採取的方法是:原本一對鎖應該是semaphore_p(semFull)和semaphore_v(semEmpty),而如果是希望不釋放的話,應該是semaphore_p(semFull)和semaphore_v(semFull)。

把sps和pps以及I幀存放到fTo後,調用gettimeofday(&fPresentationTime,NULL);來獲取系統時間作爲時間戳(注意,我們此處並沒有使用8168前端傳來的是時間戳,而是獲取的當前時間)。

if(!startI)這個從句的目的就是如果第一幀不是I幀,那麼直接捨棄,返回0,delay一段時間繼續進入incomingDataHandler直到找到I幀爲止。

接下來的操作就說針對P或B幀的,直接將其拷貝到fTo指針指向的地址,fFrameSize就是一幀的大小。然後獲取當前時間作爲系統時間。然後返回1,走到afterGetting。

l、改寫音頻readfromfile函數:intWISAudioOpenFileSource::readFromFile()

   if( IsStart||I_start)

   {

         ret = getAudioData();

         IsStart = 0;

   }

這邊IsStart是因爲在啓動流的時候,必須播放一下音頻,不然就無法啓動,vlc會報錯,所以這邊初始化時將IsStart設爲1,播放一次後變爲0。I_start是爲了讓音頻在視頻幀出來後再播放音頻(也就是視頻找到I幀開始封裝後才讓音頻也封裝)。

getAudioData();就是進入共享內存拷貝出數據的部分,在進入共享內存前先將信號量初始化,然後調用AudioShareMemRead(&av_data,buffer);獲取音頻數據,將其放到fTo。

m、編寫GetSprop函數

其中調用ret =GetVolInfo(tempBuff,sizeof(tempBuff)); 這個函數的作用就是從共享內存處讀取一幀數據(因爲這邊是首次連接,第一幀一定是I幀,並且前面含有sps和pps信息)此函數僅僅是把第一幀數據讀取,然後存到pbuff中。

然後回到GetSprop中,從剛纔獲取的buffer中解析出sps和pps(sps是00000001*7開始的,pps是00000001*8開始),解析出sps和pps後,將其進行編碼,生成的就是放到config(SDP中sprop-parameter-sets)裏的sps和pps信息了。

 

(5)改寫live555的livemedia庫文件

a、將DM365源代碼中livemedia文件下的H264VideoRTPSink.cpp、H264VideoRTPSource.cpp、H264VideoStreamFramer.cpp以及H264VideoStreamParser.cpp文件替換掉官網最新下載的live555的livemedia庫文件下的上述文件,同時將livemedia下的include下的上述cpp文件的.hh文件也替換掉。

b、修改livemedia下的MultiFramedRTPSink.cpp文件

在voidMultiFramedRTPSink::sendPacketIfNecessary()函數中,加入如下內容:

  if(fRTPPayloadType == 0)

      {

        uSecondsToGo = 64000;

      }

因爲經測試發現,視頻幀因爲分辨率高,數據量很大,需要打包的個數也很多,耗時明顯比音頻打包的時間長,然後用VlC播放的時候,視頻放不出來,音頻很流暢。分析原因,發現音頻包打包太快,音頻包數據就優先排隊在socket端口,導致了端口的獨佔,視頻包雖然打包好了,但是無法從端口處發送出去。所以我的解決辦法是讓音頻下次打包的時間間隔延長,fRTPPayloadType=0時表示的是音頻(H.264是96),這邊延長時間爲多少還不確定,暫時設爲一個音頻包播放時間的一半(一個音頻包播放的時間爲128000ms),也可以使用別的方法解決,有待研究。

1.4 代碼移植和編譯

(1)live555移植

Live555開源代碼可以到官網上下載,它可以移植到很多不同的平臺,包括linux、armlinux、windows、os等,移植到DM8168板卡上的步驟如下:

a、解壓軟件包。在終端輸入tar zxvf live555-latest.tar.gz,出現live文件夾。

b、修改配置文件。進入live文件夾,找到並打開config.armlinux文件,將

CROSS_COMPILE?  = arm-elf-  修改爲DM8168板卡的交叉編譯工具

CROSS_COMPILE? =  arm-none-linux-gnueabi-

c、剪裁Live555。在live文件下有很多文件,而在建立本服務器時,只有4個庫文件是必須的,分別是BasicUsageEnvironment、groupsock、liveMedia、以及UsageEnvironment,所以只保留這幾個文件夾,並且修改makefile文件,將與編譯mediaServer和testProgs的相關內容刪去。

d、編譯庫文件。在終端中輸入./genMakefile armlinux,然後make。在c中所述的4個文件夾中將生成4個靜態庫文件,分別是libBasicUsageEnvironment.a、libgroupsock.a、libliveMedia.a 以及libUsageEnvironment.a。

 

(2)wis-streamer的makefile文件的修改

a、設置編譯工具

CC =arm-none-linux-gnueabi-gcc

CPLUSPLUS =arm-none-linux-gnueabi-g++

#CC=gcc

#CPLUSPLUS=g++

b、設置頭文件路徑,主要爲加入live555的4個庫文件的include

INCLUDES = -I . \

       -I$(LIVE_DIR)/BasicUsageEnvironment/include \

       -I$(LIVE_DIR)/UsageEnvironment/include \

       -I$(LIVE_DIR)/groupsock/include \

       -I$(LIVE_DIR)/liveMedia/include

c、設置靜態鏈接庫文件路徑,主要加入live555的4個靜態鏈接庫。

LIBS = -L$(LIVE_DIR)/liveMedia -lliveMedia \

       -L$(LIVE_DIR)/BasicUsageEnvironment -lBasicUsageEnvironment \

       -L$(LIVE_DIR)/UsageEnvironment -lUsageEnvironment \

       -L$(LIVE_DIR)/groupsock –lgroupsock

d、設置生成的OBJ文件

OBJS = wis-streamer.o  Err.o WISInput.o \

       share_mem.o \

       semaphore.o \

       WISServerMediaSubsession.o \

       WISH264VideoServerMediaSubsession.o \

       WISPCMAudioServerMediaSubsession.o \

e、設置c++文件依賴的頭文件

wis-streamer.cpp:            Err.hh

Err.cpp:                             Err.hh

WISInput.cpp:                         WISInput.hh share_mem.hsemaphore.h

share_mem.c:              share_mem.h

semaphore.c:               semaphore.h

WISServerMediaSubsession.cpp:         WISServerMediaSubsession.hh

WISServerMediaSubsession.hh:            WISInput.hh

WISH264VideoServerMediaSubsession.hh:      WISServerMediaSubsession.hh

WISH264VideoServerMediaSubsession.cpp:    WISH264VideoServerMediaSubsession.hh

WISPCMAudioServerMediaSubsession.cpp:    WISPCMAudioServerMediaSubsession.hh

(3)編譯wis-streamer

將(2)中的makefile文件進行編譯,生成二進制的可執行文件wis-streamer,將其拷貝到8168板的NFS目錄下。

1.5 進一步修改與定製與當前版本說明

       主要對main函數中的參數解析部分進行了調整,可以指定共享內存和信號量的ID,對程序中相關的代碼進行了修改,實現通過指定參數可以運行多次wis-streamer實現多路的RTP。

1.將gSEMV和gSEMVE在./wis-streamer/WISInput.cpp中聲明爲全局變量,並在wis-streamer.cpp中聲明外部變量。

externint gSEMV;

externint gSEMVE;

2.在main中對參數解析:

3.另外對代碼進行了部分的重構主要集中在WISVideoOpenFileSource類。如:WISVideoOpenFileSource::readFromFile()


QQ羣交流139696200

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章