8168網傳模塊主要由wis-streamer進程進行,主要功能是實現高清視頻和音頻的網絡傳輸,它能夠實現以下幾個方面的功能:
(1)能夠對來自客戶端的視頻點播請求做出響應,實現高清視頻和音頻碼流的實時傳輸功能。
(2)能夠支持單播和組播兩種網絡傳輸方式,且單播支持TCP與UDP方式。
禁止私自轉載(http://blog.csdn.net/guo8113/article/details/50241971)
./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)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)在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)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