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