當前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學習 > 講師博文 > Android視頻監(jiān)控實現(xiàn)(四)
第五章 開發(fā)指南
5.1 視頻采集安卓端(spydroid)
作為遠端采集端,App啟動后,連接并保活至服務(wù)器。采集安卓攝像頭視頻和mic聲音,進行H264和AAC編碼(這里spydroid實現(xiàn)了硬編碼,目前大部分Android音視頻采集都支持硬編碼),再通過RTSP和RTP,將實時音視頻數(shù)據(jù)推送到流媒體服務(wù)器,并由流媒體服務(wù)器進行轉(zhuǎn)發(fā)和分發(fā),實現(xiàn)直播。
這里主要就是RTSP/RTP的推送過程,下面章節(jié)中DSS的先偵聽后推送式流媒體轉(zhuǎn)發(fā)詳細描述了這個過程,咱們這里直接修改spydroid中的RTSPClient就可以實現(xiàn)ANNOUNCE/SETUP/PLAY/RTP過程了。
5.1.1 Package
net.majorkernelpanic.http主要是介紹http server,spydroid自身內(nèi)置http服務(wù)器,客戶端可以通過在VLC等播放器中輸入//ip:8080/播放。
net.majorkernelpanic.spydroid主要是Application。
net.majorkernelpanic.spydroid.api主要是幾個服務(wù)。
net.majorkernelpanic.spydroid.ui主要是activity的界面部分。
net.majorkernelpanic.mp4主要是介紹提取mp4文件的profile,sps,pps等信息。
net.majorkernelpanic.streaming.rtsp 主要是介紹rtsp服務(wù)器部分,spydroid自身內(nèi)置rtsp服務(wù)器,客戶端可以通過在VLC等播放器中輸入rtsp://ip:8086/播放。
net.majorkernelpanic.streaming.rtp主要是介紹rtp協(xié)議通信。
net.majorkernelpanic.spydroid主要是activity的界面部分。
net.majorkernelpanic.streaming主要是stream接口和抽象類。
net.majorkernelpanic.streaming.audio介紹音頻部分。
net.majorkernelpanic.streaming.video介紹視頻部分。
5.1.2 spydroid運行流程
程序運行時,進入net.majorkernelpanic.spydroid.SpydroidActivity,該activity 運行時候,開啟http server ,rtsp server,RTSP Client。這里重點關(guān)注rtsp server服務(wù)與RTSP Client服務(wù)。
RTSP Server服務(wù)
rtspserver開啟后,啟動一個線程RequestListenerThread,負責監(jiān)聽客戶端(這里用VLC)的請求
public void start() throws IOException
{
if (running) return;
running = true;
listenerThread = new RequestListenerThread(port, handler);
listenerThread.start();
}
當有客戶端請求的時候,開啟一個workerTread線程。一個線程session代表一個請求
new WorkerThread(server.accept(), handler).start();
VLC向rtsp服務(wù)器進行交互時,這里就需要用到rtsp協(xié)議的內(nèi)容了,主要分為Options,Describe,Setup,play,teardown這5步驟。關(guān)于RTSP協(xié)議請查看我們提供的《RTSP協(xié)議詳解中文版》。
options請求時,發(fā)送可用的狀態(tài)。describe請求時,發(fā)送流類型,在這里是h264視頻流,以及mp4 的profile,sps,pps,在不同手機上,profile,sps,pps的數(shù)值不一定相同。這個是通過提取錄制的該手機上的mp4文件的內(nèi)容得到的。 除了H264,這里也可以是H263視頻流,或者其他audio音頻流。這里重點查看generateSessionDescriptor()方法,比如在這里,選擇H264,那么就可以看看H264Stream這個類的這個方法,看看它是如何獲取profile ,sps,pps的setup請求時,主要關(guān)注stream.prepare(),stream.start()方法,prepare()的時候調(diào)用初始化視頻錄制的參數(shù),比如H264編碼,分辨率,幀數(shù)等相關(guān)信息。而start()方法就開始通過localsocket把錄制的視頻以流的形式發(fā)送到本地,而H264Packetizer通過獲取其輸入流,然后對其rtp打包處理,發(fā)送。
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setVideoEncoder(mVideoEncoder);
mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
mMediaRecorder.setVideoSize(mRequestedQuality.resX, mRequestedQuality.resY);
//mMediaRecorder.setVideoFrameRate(mRequestedQuality.framerate);
mMediaRecorder.setVideoEncodingBitRate((int)(mRequestedQuality.bitrate * 0.8));
mMediaRecorder.setOutputFile(TESTFILE);
mMediaRecorder.setMaxDuration(3000);
// We wait a little and stop recording
mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener()
{
public void onInfo(MediaRecorder mr, int what, int extra)
{
Log.d(TAG, "MediaRecorder callback called !");
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED)
{
Log.d(TAG, "MediaRecorder: MAX_DURATION_REACHED");
}
else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)
{
Log.d(TAG, "MediaRecorder: MAX_FILESIZE_REACHED");
}
else if (what == MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN)
{
Log.d(TAG, "MediaRecorder: INFO_UNKNOWN");
}
else
{
Log.d(TAG, "WTF ?");
}
mLock.release();
}
});
// Start recording
mMediaRecorder.prepare();
mMediaRecorder.start();
RTSP Client服務(wù)
Spydroid本身并沒有RTSP Client功能,但是其提供了一個RTSPClient,這里我們參照RTSPServer直接修改spydroid中的RTSPClient實現(xiàn)ANNOUNCE/SETUP/PLAY/RTP過程了。
RTSPClient啟動后,執(zhí)行startStream(),首先進行Session的設(shè)置。
public void setSession()
{
Session msession = new Session();
msession = null;
try
{
msession = UriParser.parse("rtsp://" + mTmpParameters.host + ":" + mTmpParameters.port);
}
catch (IllegalStateException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
catch (IOException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
msession.setOrigin("127.0.0.1");
if (msession.getDestination() == null)
{
msession.setDestination("mTmpParameters.host");
}
try
{
msession.syncConfigure();
}
catch (CameraInUseException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
catch (StorageUnavailableException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
catch (ConfNotSupportedException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
catch (InvalidSurfaceException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
catch (RuntimeException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
catch (IOException e)
{
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
mTmpParameters.session = msession;
}
當然還有一些流被送到的路徑的設(shè)置,RTSP服務(wù)器的目的地址的設(shè)置,如果在服務(wù)器上啟用身份驗證,你需要設(shè)置用戶名/密碼等等。所有準備工作準備無誤后嘗試連接服務(wù)器。
private void tryConnection() throws IOException
{
mCSeq = 0;
mSocket = new Socket(mParameters.host, mParameters.port);
mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
mOutputStream = mSocket.getOutputStream();
sendRequestAnnounce();
sendRequestSetup();
sendRequestRecord();
}
在這個函數(shù)中可以清楚的看到,RTSPClient連接服務(wù)器推送視頻流所發(fā)送的請求及順序。
ANNOUNCE:該方法方法有兩個目的:當從客戶端發(fā)往服務(wù)器端,ANNOUNCE向服務(wù)器端上傳請求URL所標識的表示或媒體對象的描述。當從服務(wù)器端發(fā)往客戶端,ANNOUNCE實時更新會話描述。當一個新的媒體流加入一個表示(例如:在一個現(xiàn)場表示活動期間)時,整個表示而不僅是所增加的部分,應(yīng)該被重發(fā),以便部分刪除。
SETUP:讓服務(wù)器給流分配資源,啟動RTSP會話。
RECORD(PLAY):啟動SETUP所分配的流的數(shù)據(jù)傳輸。
5.2 流媒體服務(wù)器(Darwin Streaming Server)
5.2.1 DSS的框架
服務(wù)器的作用是充當網(wǎng)絡(luò)客戶和服務(wù)器模塊的接口,其中網(wǎng)絡(luò)客戶使用RTP和RTSP協(xié)議來發(fā)送請求和接收響應(yīng),而服務(wù)器模塊則負責處理請求和向客戶端發(fā)送數(shù)據(jù)包。核心服務(wù)器通過創(chuàng)建四種類型的線程來完成自己的工作,具體如下:
•服務(wù)器自己擁有的主線程(Main Thread)。這個線程負責檢查服務(wù)器是否需要關(guān)閉,記錄狀態(tài)信息,或者打印統(tǒng)計信息。
•空閑任務(wù)線程(Idle Task Thread)?臻e任務(wù)線程管理一個周期性的任務(wù)隊列。該任務(wù)隊列有兩種類型:超時任務(wù)和套接口任務(wù)。
•事件線程(Event Thread)。事件線程負責偵聽套接口事件,比如收到RTSP請求和RTP數(shù)據(jù)包,然后把事件傳遞給任務(wù)線程。
•一個或者多個任務(wù)(Task)線程。任務(wù)線程從事件線程中接收RTSP和RTP請求,然后把請求傳遞到恰當?shù)姆⻊?wù)器模塊進行處理,把數(shù)據(jù)包發(fā)送給客戶端。缺省情況下,核心服務(wù)器為每一個處理器創(chuàng)建一個任務(wù)線程。
5.2.2 模塊
媒體服務(wù)器使用模塊來響應(yīng)各種請求及完成任務(wù)。有三種類型的模塊:
1. 內(nèi)容管理模塊
內(nèi)容管理模塊負責管理與媒體源相關(guān)的RTSP請求和響應(yīng),比如一個文件或者一個廣播。每個模塊負責解釋客戶的請求,讀取和解析它們的支持文件或者網(wǎng)絡(luò)源,并且以RTSP和RTP的方式進行響應(yīng)。在某些情況下,比如流化mp3的模塊,使用的則是HTTP。
QTSSFileModule,QTSSReflectorModule,QTSSRelayModule,和QTSSMP3StreamingModule都是內(nèi)容管理模塊。
2. 服務(wù)器支持模塊
服務(wù)器支持模塊執(zhí)行服務(wù)器數(shù)據(jù)的收集和記錄功能。服務(wù)器模塊包括QTSSErrorLogModule, QTSSAccessLogModule,QTSSWebStatsModule,QTSSWebDebugModule, QTSSAdminModule,和QTSSPOSIXFileSystemModule。
3. 訪問控制模塊
訪問控制模塊提供鑒權(quán)和授權(quán)功能,以及操作URL路徑提供支持。
訪問控制模塊包括QTSSAccessModule,QTSSHomeDirectoryModule,QTSSHttpFileModule,和QTSSSpamDefenseModule。
5.2.3 數(shù)據(jù)
當一個模塊需要訪問客戶請求的RTSP報頭時,可以通過QTSS.h這個API頭文件中定義的請求對象來訪問相應(yīng)的請求信息。舉例來說,RTSPRequestInterface類實現(xiàn)了API字典元素,這些元素可以通過API來進行訪問。名稱是以“Interface”結(jié)尾的對象,比如RTSPRequestInterface,RTSPSessionInterface,和QTSServerInterface,則用于實現(xiàn)模塊的API。
下面是重要的接口類:
•QTSServerInterface — 這是內(nèi)部數(shù)據(jù)的存儲對象,在API中標識為QTSS_ServerObject。在API中的每一個QTSS_ServerAttributes都在基類中聲明和實現(xiàn)。
•RTSPSessionInterace — 這是內(nèi)部數(shù)據(jù)的存儲對象,在API中標識為qtssRTSPSessionObjectType。在API中的每一個QTSS_RTSPSessionAttributes都在基類中聲明和實現(xiàn)。
•RTPSessionInterface — 這是內(nèi)部數(shù)據(jù)的存儲對象,在API中標識為QTSS_ClientSessionObject。在API中的每一個QTSS_ClientSessionAttributes都在基類中聲明和實現(xiàn)。
•RTSPRequestInterface — 這是內(nèi)部數(shù)據(jù)的存儲對象,在API中標識為QTSS_RTSPRequestObject。在API中的每一個QTSS_RTSPRequestAttributes都在基類中聲明和實現(xiàn)。
5.2.4 源代碼的組織
Server.tproj
這個目錄包含核心服務(wù)器(core server)的代碼,可以分成三個子系統(tǒng):
•服務(wù)器內(nèi)核。這個子系統(tǒng)中的類都有一個QTSS前綴。QTSServer負責處理服務(wù)器的啟動和關(guān)閉。QTSServerInterface負責保存服務(wù)器全局變量,以及收集服務(wù)器的各種統(tǒng)計信息。QTSSPrefs是存儲服務(wù)器偏好設(shè)定的地方。QTSSModule,QTSSModuleInterface,和QTSSCallbacks類的唯一目的就是支持QTSS的模塊API。
•RTSP子系統(tǒng)。這些類負責解析和處理RTSP請求,以及實現(xiàn)QTSS模塊API的RTSP部分。其中的幾個類直接對應(yīng)QTSS API的一些元素(比如,RTSPRequestInterface類就是對應(yīng)于QTSS_RTSPRequestObject對象)。每個RTSP TCP連接都有一個RTSP會話對象與之相對應(yīng)。RTSPSession對象是一個Task對象,負責處理與RTSP相關(guān)的事件。
•RTP子系統(tǒng)。這些類處理媒體數(shù)據(jù)的發(fā)送。RTPSession對象包含與所有RTSP會話ID相關(guān)聯(lián)的數(shù)據(jù)。每個RTPSession都是一個Task對象,可以接受核心服務(wù)器的調(diào)度來進行RTP數(shù)據(jù)包的發(fā)送。RTPStream對象代表一個單獨的RTP流,一個RTPSession對象可以和任何數(shù)目的RTPStream對象相關(guān)聯(lián)。這兩個對象實現(xiàn)了QTSS模塊API中的針對RTP的部分。
CommonUtilitiesLib
這個目錄含有一個工具箱,包括線程管理,數(shù)據(jù)結(jié)構(gòu),網(wǎng)絡(luò),和文本解析工具。Darwin流媒體服務(wù)器及其相關(guān)工具通過這些類對類似或者相同的任務(wù)進行抽象,以減少重復(fù)代碼;這些類的封裝簡化了較高層次的代碼;借助這些類還分離了專用于不同平臺的代碼。下面是對目錄下的各個類的簡短描述:
•OS類。這些類在時間,條件變量,互斥鎖,和線程方面提供了專用于不同平臺的代碼抽象。這些類包括OS,OSCond,OSMutex,OSThread,和OSFileSource;數(shù)據(jù)結(jié)構(gòu)則包括OSQueue,OSHashTable,OSHeap,和OSRef。
•套接口類(Sockets)。這些類為TCP和UDP網(wǎng)絡(luò)通訊方面提供了專用于不同平臺的代碼抽象。通常情況下,套接口類是異步的(或者說是非阻塞的),可以發(fā)送事件給Task對象。這些類有:EventContext,Socket,UDPSocket,UDPDemuxer,UDPSocketPool,TCPSocket,和TCPListenerSocket。
•解析工具。這些類負責解析和格式化文本。包括StringParser,StringFormatter,StrPtrLen,和StringTranslator。
•Task(任務(wù)):這些類實現(xiàn)了服務(wù)器的異步事件機制。
QTFileLib
流媒體服務(wù)器的一個主要特性就是它能夠?qū)⑺饕瓿?hinted)的QuickTime電影文件通過RTSP和RTP協(xié)議提供給客戶。這個目錄包含QTFile庫的源代碼,包括負責解析索引完成的QuickTime文件的代碼。服務(wù)器的RTPFileModule通過調(diào)用QTFile庫來從索引過的QuickTime文件中取得數(shù)據(jù)包和元數(shù)據(jù)。QTFile庫可以解析下面幾種文件類型:.mov,.mp4(.mov的一種修改版本),和.3gpp(.mov的一種修改版本)。
APICommonCode
這個目錄包含與API相關(guān)的類的源代碼,比如moduletils,或者諸如記錄文件的管理這樣的公共模塊函數(shù)。
APIModules
這個目錄包含流媒體服務(wù)器模塊目錄,每個模塊都有一個目錄。
RTSPClientLib
這個目錄包含實現(xiàn)RTSP客戶端的源代碼,這些代碼可以用于連接服務(wù)器,只要該連接協(xié)議被支持。
RTCPUtilitiesLib
這個目錄包含解析RTCP請求的源代碼。
APIStubLib
這個目錄包含API的定義和支持文件。
HTTPUtilitiesLib
這個目錄包含解析HTTP請求的源代碼。
5.2.5 流轉(zhuǎn)發(fā)
引用Darwin開發(fā)文檔里面的一段來介紹一下流轉(zhuǎn)發(fā)的拉模式和推模式:
Darwin支持兩種自動播送的場景:
•先拉后推。為了發(fā)起自動播送,RTSP客戶會發(fā)送標準的RTSP請求來向服務(wù)器請求一個流,然后服務(wù)器將該流中繼到一個或者多個流媒體服務(wù)器。這種場景在"先拉后推"部分中加以描述。
•先偵聽后推送。在這個場景中,自動播送在流媒體服務(wù)器接收到ANNOUNCE請求時被發(fā)起。這個場景在"先偵聽后推送"部分中進行描述。
先拉后推
用戶可以通過發(fā)送標準的DESCRIBE/SETUP/PLAY請求來向遠程的源中請求一個流,然后將它中繼轉(zhuǎn)發(fā)到一個或者多個目的地。當只希望讓外部流的一份拷貝占用其內(nèi)部連接的帶寬時,這個功能可能有用。中繼轉(zhuǎn)發(fā)獲取一份拷貝進行多份的復(fù)制和轉(zhuǎn)發(fā)、分發(fā)到請求的客戶端。圖 1.提供了一個先拉后推(pull-then-push)場景的實例。
圖1.先拉后推式
以圖1.作為參考,先拉后推場景的步驟如下:
1.流媒體服務(wù)器A(轉(zhuǎn)發(fā)服務(wù)器)發(fā)送標準的RTSP客戶DESCRIBE/SETUP/PLAY請求給遠程服務(wù)器,即流媒體服務(wù)器B。
2.發(fā)起請求的中繼“客戶端”(流媒體服務(wù)器A)開始接受流,然后向該輸入流的中繼配置中列出的所有目的地發(fā)送ANNOUNCE推送請求。
在Darwin中,實現(xiàn)拉模式轉(zhuǎn)發(fā)的模塊為QTSSRelayModule,每一路轉(zhuǎn)發(fā)會話為一個RelaySession對象,轉(zhuǎn)發(fā)列表存儲于隊列sSessionQueue中
QTSSRelayModule一開始Initialize()讀取配置文件中關(guān)于轉(zhuǎn)發(fā)文件路徑存儲于sRelayPrefs靜態(tài)變量中
./relayconfig.xml
在ReadRelayPrefsFile()中讀取sRelayPrefs中配置并解析出分發(fā)列表,并對每一個分發(fā)配置中的source配置創(chuàng)建RelaySessionCreator開啟分發(fā),并加入到sSessionQueue中,RTSPSourceInfo::RelaySessionCreator::Run() 再通過RTSPSourceInfo::RunCreateSession()開始DESCRIBE/SETUP/PLAY拉取數(shù)據(jù),RTSP流程成功后,再配置RTP數(shù)據(jù)分發(fā)的地址,將RTP數(shù)據(jù)推送至分發(fā)列表(即destination列表與source列表同一級)中,即實現(xiàn)了Darwin文檔中所述的先拉后推模式。
先偵聽后推送
流媒體服務(wù)器可以被配置為將ANNOUNCE請求創(chuàng)建的輸入流自動發(fā)送到一個或者多個目的地。這可能可以用于配制自動播送網(wǎng)絡(luò)。圖 2.提供了一個先偵聽后推送的場景的實例。
圖2.先偵聽后推送式
以圖2.作為參考,先偵聽后推送場景的步驟如下:
•遠程機器(IpCamera等前端設(shè)備或者中繼服務(wù)器)向流媒體服務(wù)器A發(fā)送一個ANNOUNCE請求。流媒體服務(wù)器可以接受或者否認這個請求。如果它接受了請求,則流媒體服務(wù)器會檢查其中繼配置,以確定這個流是否應(yīng)該被中繼。
•如果該流應(yīng)該被中繼,則流媒體服務(wù)器將向自身發(fā)送標準的RTSP客戶DESCRIBE/SETUP/PLAY請求。
•發(fā)出請求的中繼“客戶”(流媒體服務(wù)器A)開始接收流,然后向相應(yīng)的輸入流的中繼配置中列出的所有目的地發(fā)送一個ANNOUCE請求。
注意:我們在實際的需求中常常遇到的場景為,前端設(shè)備RTSP Announce上線至中繼服務(wù)器,上報其流媒體SDP信息,前端設(shè)備再經(jīng)過'被觸發(fā)',通過SETUP/PLAY流程發(fā)起流推送,客戶端再以拉模式拉取實時視頻流,是一種先推后拉(push-then-pull)模式,如果客戶端請求的視頻流存在,則直接轉(zhuǎn)發(fā)已經(jīng)獲取的拷貝進行分發(fā)。
具體的RTSP推送流程大致為:Announce、Setup、Play、RTP(DSS為RTP over TCP)。這種模式的轉(zhuǎn)發(fā)通常用于類似于3G視頻監(jiān)控這種難以穿透的網(wǎng)絡(luò)類型的數(shù)據(jù)的轉(zhuǎn)發(fā)。我們就不具體介紹關(guān)于DSS對會話的維護以及各自自定義的RTSP頭字段的操作等等,主要就步驟:Announce->Setup->Play->RTP數(shù)據(jù)接收與轉(zhuǎn)發(fā)進行詳細的分析。在DSS中,處理推送報文的模塊為QTSSReflectorModule,其中維護了一個靜態(tài)的轉(zhuǎn)發(fā)列表sSessionMap,用于存儲各個轉(zhuǎn)發(fā)會話的信息。下面就對具體的報文解析和數(shù)據(jù)處理進行分析。
Announce:RTSP Announce命令為源端向服務(wù)器端主動發(fā)起的上報本地媒體sdp信息的命令,處理函數(shù)為QTSSReflectorModule模塊的DoAnnounce()函數(shù),這里就只對該函數(shù)的重點部分進行解析,不全部一一描述了。首先判斷server配置中的enable_broadcast_announce字段是否為true,開啟了廣播推送轉(zhuǎn)發(fā),在通過獲取inParams->inRTSPRequest(在RTSPSession::Run調(diào)用前復(fù)制的當前請求的rtspRequest對象)的字典中的qtssRTSPReqLocalPath鍵值作為標識轉(zhuǎn)發(fā)的唯一區(qū)別(例如:.\Movies/test.sdp,必須以sdp結(jié)尾,可以修改sSDPSuffix進行配置),這里的值既是一個標識,又是一個路徑,用于存儲獲取到的sdp數(shù)據(jù),后面此標識作為存儲于sSessionMap中對象的鍵值。函數(shù)中通過對頭字段的解析,獲取到Content-Length:字段值,進而去讀取具體的spd值,再存儲到qtssRTSPReqLocalPath路徑中,返回200 OK。
Setup:這里的只解析DoSetup中isPush為true(表示為推送的Session)這條路路徑,具體isPush值由Setup請求中的mode值有關(guān),mode="receive" || mode="record"表示isPush為true,
else
{
theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc, isPush, &foundSession);//根據(jù)前面Announce中存儲于qtssRTSPReqLocalPath的路徑讀取sdp信息,創(chuàng)建轉(zhuǎn)發(fā)會話ReflectorSession,或者直接引用已經(jīng)存在的Session
if (theSession == NULL)
return QTSS_RequestFailed;
// This is an incoming data session. Set the Reflector Session in the ClientSession
theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));//ReflectorSession附屬于RTPSession中的sClientBroadcastSessionAttr字典
Assert(theErr == QTSS_NoErr);
//qtss_printf("QTSSReflectorModule.cpp:SETsession sClientBroadcastSessionAttr=%"_U32BITARG_" theSession=%"_U32BITARG_" err=%"_S32BITARG_" \n",(UInt32)sClientBroadcastSessionAttr, (UInt32) theSession,theErr);
(void) QTSS_SetValue(inParams->inClientSession, qtssCliSesTimeoutMsec, 0, &sBroadcasterSessionTimeoutMilliSecs, sizeof(sBroadcasterSessionTimeoutMilliSecs));
}
這里需要注意的是,當我們前面已經(jīng)有一路相同qtssRTSPReqLocalPath路徑的ReflectorSession存在的時候,將不進行再創(chuàng)建,直接Resolve原有的ReflectorSession,所以會出現(xiàn)一種情況,當開始的推送與后面進行的推送音視頻sdp不一致的時候,就會出現(xiàn)錯誤,所以, ReflectorSession的引用與釋放需要注意!
完成ReflectorSession的創(chuàng)建,下一步解析track ID,具體的解析方法可以根據(jù)自己的實際應(yīng)用,有的按照track%d解析,有的按照trackID=%d解析,再根據(jù)trackId獲取具體的track sdp信息,AddRTPStream創(chuàng)建對應(yīng)于具體track的RTP流
theStreamInfo->fSetupToReceive = true;//標識流轉(zhuǎn)發(fā)的建立
// This is an incoming data session. Set the Reflector Session in the ClientSession
theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));//設(shè)置轉(zhuǎn)發(fā)會話的RTPSession字典的sClientBroadcastSessionAttr字段
Assert(theErr == QTSS_NoErr);
if (theSession != NULL)
theSession->AddBroadcasterClientSession(inParams);//設(shè)置ReflectorSession的fBroadcasterSession屬性為inParams->inClientSession,呵呵比較亂噢,相當于相互引用
Play: 具體到DoPlay過程,isPush為true的路徑就比較簡單了,只是將推送的RTSPSession中的sRTSPBroadcastSessionAttr屬性設(shè)置為前面DoSetup中獲取到的ReflectorSession
theLen = sizeof(inSession);
theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &inSession, &theLen);//DoSetup()中已經(jīng)設(shè)置sClientBroadcastSessionAttr屬性
if (theErr != QTSS_NoErr)
return QTSS_RequestFailed;
theErr = QTSS_SetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &sTearDownClientsOnDisconnect, sizeof(sTearDownClientsOnDisconnect));
if (theErr != QTSS_NoErr)
return QTSS_RequestFailed;
Assert(inSession != NULL);
theErr = QTSS_SetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &inSession, sizeof(inSession));//設(shè)置到inParams->inRTSPSession的sRTSPBroadcastSessionAttr屬性
if (theErr != QTSS_NoErr)
return QTSS_RequestFailed;
RTP數(shù)據(jù)處理:ProcessRTPData(),這里只處理RTP over TCP的數(shù)據(jù),根據(jù)RTP數(shù)據(jù)中的channel值,調(diào)用特定的ReflectorStream進行處理和轉(zhuǎn)發(fā),具體函數(shù)為:ProcessRTPData(),先通過前面在DoSetup() & isPush為true時設(shè)置的sRTSPBroadcastSessionAttr屬性,獲取ReflectorSession
ReflectorSession *theSession = NULL;
UInt32 theLen = sizeof(theSession);
QTSS_Error theErr = QTSS_GetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &theSession, &theLen);
if (theSession == NULL || theErr != QTSS_NoErr)
return QTSS_NoErr;
再根據(jù)channelID獲取具體的ReflectorStream并進行數(shù)據(jù)推送,給具體的ReflectorStream進行處理
UInt32 inIndex = packetChannel / 2; // one stream per every 2 channels rtcp channel handled below
ReflectorStream *theStream = NULL;
if (inIndex < numStreams)
{
theStream = theSession->GetStreamByIndex(inIndex);//獲取對應(yīng)track的ReflectorStream
SourceInfo::StreamInfo *theStreamInfo = theStream->GetStreamInfo();
UInt16 serverReceivePort = theStreamInfo->fPort;
Bool16 isRTCP = false;
if (theStream != NULL)
{
if (packetChannel & 1)
{
serverReceivePort ++;
isRTCP = true;
}
theStream->PushPacket(rtpPacket, packetDataLen, isRTCP); //推送數(shù)據(jù)給ReflectorStream并轉(zhuǎn)發(fā)給分發(fā)列表
}
}
5.2.6 二次開發(fā)模塊添加的要求
每個DSS模塊必須實現(xiàn)兩個函數(shù):一個是Main函數(shù),服務(wù)器在啟動時將調(diào)用這個函數(shù)進行必要的初始化。另一個是Dispatch函數(shù),通過實現(xiàn)此函數(shù),服務(wù)器可調(diào)用DSS模塊并完成特定處理。對于編譯到服務(wù)器里面的模塊,其主函數(shù)的地址必須傳遞到服務(wù)器的模塊Main函數(shù)中。
具體實現(xiàn)時,Main函數(shù)必須命名為MyModule_Main,其中MyModule是模塊的文件名。此函數(shù)的實現(xiàn)通常如下所示:
QTSS_Error MyModule_Main(void *inPrivateArgs)
{
return _stublibrary_main(inPrivateArgs, MyModuleDispatch);
}
每個DSS模塊都必須提供一個Dispatch函數(shù)。服務(wù)器為了特定的目的需要使用某個模塊時,是通過調(diào)用該模塊的Dispatch函數(shù)來實現(xiàn)的,調(diào)用時必須將任務(wù)的名稱及相應(yīng)的參數(shù)傳遞給該函數(shù)。在DSS中,使用角色(Role)這個術(shù)語來描述特定的任務(wù)。Dispatch函數(shù)的格式如下所示:
void MyModuleDispatch(QTSS_Role inRole,QTSS_RoleParamPtr inParams);
其中MyModuleDispatch是Dispatch函數(shù)的名稱;MyModule是模塊的文件名;inRole是角色的名稱,只有注冊了該角色的模塊才會被調(diào)用;inParams則是一個結(jié)構(gòu)體,可用于傳遞相應(yīng)的參數(shù)。
5.3 視頻播放器(VLC)
我們提供了一個裁剪過適用于此系統(tǒng)的VLC,關(guān)于原版詳細分析如下。
5.3.1 eclipse 調(diào)試源碼
原版源碼:項目/源碼/vlc_source_android.Zip
其中源碼獲取是:git clone git://git.videolan.org/vlc-ports/android.git
源碼介紹
相關(guān)源碼介紹 : VLC 源碼依賴于 另外四個工程;
•vlc-android 工程 : VLC 的主要源碼;
•appcompat 工程 : 低版本兼容庫, VLC 源碼 vlc-android 需要依賴該工程;
•cardview 工程 : VLC 源碼 vlc-android 需要依賴該工程;
•libvlc 工程 : VLC 源碼 vlc-android 需要依賴該工程;
•WheelView 工程 : VLC 源碼 vlc-android 需要依賴該工程;
源碼導(dǎo)入
將源碼導(dǎo)入 eclipse : 主需要重新設(shè)置一下依賴, 其它不用修改;
vlc 源碼依賴 : vlc 依賴其余的四個工程, 下面的是 project.properties 內(nèi)容;
target=android-21
android.library.reference.1=..\\libvlc
android.library.reference.2=../appcompat
android.library.reference.3=../cardview
android.library.reference.4=../WheelView
執(zhí)行安裝
執(zhí)行效果 :
5.3.2 包結(jié)構(gòu)詳解
VLC Android 源碼包結(jié)構(gòu)分析 :
•主包結(jié)構(gòu)截圖 : org.vedio.vlc 包下的內(nèi)容;
•audio 包 : 音頻相關(guān)的包;
•gui 包 : 界面 UI 相關(guān)包;
•interfaces 包 : 定義各種接口;
•widget 包 : 自定 義組件相關(guān)的包;
•utils 包 : 相關(guān)工具類;
5.3.3 主要類介紹
注意 : 以下介請結(jié)合自己的理解去閱讀代碼
(1) org.videolan.vlc 下類介紹
org.videolan.vlc 下類介紹 :
•MediaDatabase 介紹 : 數(shù)據(jù)庫操作相關(guān)類, 該類中定義了 SQLiteOpenHelper 子類, 并且定義了幾個數(shù)據(jù)庫, 創(chuàng)建了以下數(shù)據(jù)表 directories_table 路徑表, media_table 媒體信息表, playlist_table 單個播放列表, playlist_media_table 播放列表集合表, searchhistory_table 搜索記錄表, mrl_table mrl 表;
•MediaGroup 介紹 : 繼承了 Media 類(在 libvlc 中維護, 維護視頻音頻后綴名稱或擴展名), 用于維護一個 Media 集合;
•MediaLibrary 介紹 : Media 相關(guān)庫, 該類中維護了一個條目列表, 主要對這個條目列表進行操作 (疑問, 沒看懂);
•PhoneStateReceiver 介紹 : 廣播接收者, 一個監(jiān)聽手機電話狀態(tài)的廣播接收者, 如果有電話打入, 或結(jié)束通過, 進行對應(yīng)的操作;
•RemoteControlClientReceiver 介紹 : 廣播接收者, 通過遠程 wifi, 藍牙, 屏幕鎖定解鎖 等接收事件, 進行響應(yīng)的操作;
•Thumbnailer 介紹 : 是 Runnable 子類, thumbnail 用于代表要執(zhí)行的動作, 等待播放的流媒體;
•VLCApplication 介紹 : Application 子類, 進行全局的設(shè)置;
•VLCCallbackTask 介紹 : AsyncTask 的子類, 這是個回調(diào)的幫助類, 能夠在線程中更容易實現(xiàn)回調(diào);
•VLCCrashHandler 介紹 : 用于處理未捕獲的崩潰信息, 打印到日志 或者 文件中;
(2) org.videolan.vlc.audio 下類介紹
org.videolan.vlc.audio 包類介紹 :
•AudioService 介紹 : 集成 Service, 播放音頻的后臺服務(wù);
•AudioServiceController 介紹 : 音頻服務(wù)控制類;
•RepeatType 介紹 : 重復(fù)類型枚舉定義, 不重復(fù), 重復(fù)一次, 循環(huán);
(3) org.videolan.vlc.widget 下類介紹
org.videolan.vlc.widget 類介紹 :
•AnimatedCoverView 介紹 : 繼承 View 組件, 自定義組件, 動畫切換相關(guān)的 自定義 View;
•AudioMediaSwitcher 介紹 : 繼承結(jié)構(gòu) AudioMediaSwitcher -> FlingViewGroup -> ViewGroup, 音頻媒體切換相關(guān)類;
•AudioPlaylistItemViewGroup 介紹 : 繼承結(jié)構(gòu) AudioPlaylistItemViewGroup -> FlingViewGroup, 音頻播放列表相關(guān)類;
•ContentLinearLayout 介紹 : 繼承 LinearLayout, 重寫了 onInterceptTouchEvent 方法, 用于攔截觸摸事件, 當媒體正在播放的時候, 如果觸摸子組件, 觸發(fā)事件, 會影響播放, 此時我們需要攔截這些觸摸事件;
•EqualizerBar 介紹 : 繼承 LinearLayout, 均衡器調(diào)節(jié)條;
•ExpandableLayout 介紹 : 主要內(nèi)容;
•FlingViewGroup 介紹 : 繼承 ViewGroup, 主要是修改了一些手勢操作, 覆蓋重寫了 onScrollChanged, onTouchEvent, onInterceptTouchEvent 方法;
•HeaderScrollView 介紹 : 繼承 HorizontalScrollView, 橫向滑動的 View 組件;
•SlidingPaneLayout 介紹 : 繼承 ViewGroup, 這個類是從 Android 中剝離出來的, 屬于 support-v4 中的一個類, 如果想要上下滑動, 不是左右側(cè)劃, 需要修改一些地方;
•VerticalSeekBar 介紹 : 繼承 SeekBar, 這個組件是一個垂直的拖動條;
•VLCAppWidgetProvider 介紹 : 集成 AppWidgetProvider 類, App 組件提供者, 相當與一個廣播接收者;
(4) org.videolan.vlc.util 下類介紹
org.videolan.vlc.util 包類介紹 :
•AndroidDevices 類 : 獲取手機相關(guān)信息, 是否有內(nèi)存卡, 手機型號, 獲取存儲路徑, 獲取媒體目錄;
•BitmapCache 類 : 圖片緩存相關(guān)類, 使用 LruCache 實現(xiàn)圖片的流暢緩存;
•BitmapUtil 類 : 處理位圖相關(guān)類, 提供 邊緣切割, 縮放, 從緩存中獲取圖片;
•CustomDirectories 類 : 管理用戶信息存放路徑;
•Logcat 類 : 獲取日志, 將日志輸出到文件中;
•MurmurHash : MurmurHash算法:高運算性能,低碰撞率,由Austin Appleby創(chuàng)建于2008年;
•Preferences : SharedPreferences 操作相關(guān);
•Strings : 用于處理字符串相關(guān)的工具類;
•Util : 一些小公共方法;
•VLCInstance : libvlc 相關(guān)的類, 與 libvlc 工程相關(guān);
•VLCRunnable : 繼承 Runnable, 線程相關(guān)類;
•WeakHandler : 繼承 Handler;
(5) org.videolan.vlc.interfaces 下類介紹
org.videolan.vlc.interfaces 包介紹 :
•IAudioPlayer 介紹 : 音頻播放接口, 提供了更新 和 更新進度條方法;
•IAudioPlayerControl 介紹 : 音頻播放控制接口, 提供了一系列的音頻控制方法;
(4) org.videolan.vlc.audio 下類介紹
org.videolan.vlc.gui.audio.widget 包介紹 :
•CoverMediaSwitcher 介紹 : 繼承結(jié)構(gòu) CoverMediaSwitcher -> AudioMediaSwitcher -> FlingViewGroup -> ViewGroup;
•HeaderMediaSwitcher 介紹 : 繼承結(jié)構(gòu) HeaderMediaSwitcher -> AudioMediaSwitcher -> FlingViewGroup -> ViewGroup;