android中stagefright和OMXCodec原理分析
1. 框架結構
1.1StageFright和openCore和NuPlayer的關係
上圖可知,stagefright是在MediaPlayerService這一層加入的,和opencZ喎�"/kf/ware/vc/" target="_blank" class="keylink">vcmXKx7KiwdC1xKOs1NrRodPDb3BlbmNvcmW7ucrHc3RhZ2VmcmlnaHS1xLT6wuvH0Lu7yc/SsrfHs6PI3dLXoaM8L3A+DQo8cD5BbmRyb2lkyc+1xE1lZGlhUGxheWVysqW3xbXXsuO/8rzc0tG+rb6twPrBy7bgtM6x5Lavo6y009fu1OfPyLXET3BlbkNvcmW1vbrzwLS1xFN0YWdlRnJpZ2h01Nm1vc/W1Nq1xE51UGxheWVyRHJpdmVyo6zU2rmk1/e/qsq8vdO0pUFuZHJvaWS1xMqxuvLS0b6t0saz/cHLT3BlbkNvcmXL+dLUttRPcGVuQ29yZbXEwcu94r32vfbNo8H01NrM/cu1uf2jrNXi0Km/8rzc1NrR3b34uf2zzNbQ0ruw47a8ysfPyMG91ta/8rzcsqK05qOsyLu689TZ1NrEs7j2sOaxvtbQvavG5NLGs/2jrNTnz8hBbmRyb2lk1tDKudPDtcTKx1N0YWdlZnJpZ2h0ICsgTnVQbGF5ZXKyorTmtcS3vcq9o6zG5NbQx7DV37i61PCypbfFsb612LXEw73M5c7EvP6jrLrz1d/Tw9PasqW3xc34wufB98O9zOXOxLz+o6y1q8rH1Nq688C0tcRBbmRyb2lkIEy/qsq8TnVQbGF5ZXJpvaW9pb+qyrzM5rT6wctTdGFnZWZyaWdodKOsxL/HsLG+tdiypbfF0tG+rcfQu7u1vU51UGxheWVyyc/By6Os1NpBbmRyb2lkIE4gQU9QUyDUtLT6wuvW0Mn11sHSxrP9wctTdGFnZWZyaWdodKGjPC9wPg0KPGgyIGlkPQ=="12-openmax">1.2 OpenMAX
1.3 StageFright
基於Stagefight的MediaPLayer框架的結構:
stageFright is a player .
上圖可以看出播放過程主要涉及3個程序: app端程序,媒體框架服務(stageFright),OMX服務.實際使用還經常用到一個專門做跨程序記憶體共享管理的程序(MemoryDeal).注意後面會經常講到”客戶端”,有時並不是指app端,要區分開來. - 應用層和framework層?使用到MediaPlayer的應用很多,最常見的就是Music和Video,如果要了解這些應用的實現可以看下AOSP程式碼中的packages/apps,這些程式碼中用到了frameworks/base/media/所提供的MediaPlayer介面,這些介面都十分簡單,我們只需要知道這些介面的具體功能就可以開發出一款功能較為齊全的Music Player. - Native Media Player 層: 應用層的native實現.通過binder機制
Media Player Service 部分:從Native層發出的IPC請求將會由Media Player Service 部分進行處理.MediaPlayerService是在frameworks/av/media/mediaserver/main_mediaserver.cpp的main方法中初始化的,在main方法中還啟動了多個Android系統服務比如AudioFlinger, CameraService等,例項化Media Player Service 子系統的工作包括MediaPlayerService物件的建立,以及內建底層Media PLayer播放框架工廠的註冊,一旦 MediaPlayerService 服務啟動,MediaPlayerService將會接受Native MediaPlayer 層的IPC請求,並且為每個操作media內容的請求例項化一個MediaPlayerService::Client物件, Client有一個createPlayer 的方法可以使用特定的工廠類為某個特定的型別建立一個本地media player,後續的發向native層的請求都會交給剛剛提到的native 層的 media palyer來處理,這裡的media player指的是StagefrightPlayer或者Nuplayerdriver.但是我們這裡先不討論Nuplayerdriver。
分析檔案:
StageFright主要是對AwesomePlayer的封裝.AwesomePlayer是事件驅動的播放器.本文主要分析它對視訊流的處理.重點在
AwesomePlayer::onVideoEvent函式.其中從流中read packet,parse,decode .都在
mVideoSource->read(&mVideoBuffer, &options);函式完成
.該函式在
OMXCodec.cpp實現.其中read(extract)和parse 在AwesomePlyaer呼叫其它元件(如MPEG4Extractor)完成,引數mVideoBuffer即為解碼後的幀影象,decode則是呼叫OMXCodec的服務介面.也就是解碼時又通過Binder做了一次跨程序通訊.關於OMXCodec Service的一些檔案:
- 介面定義:
IOMX.h
- 客戶端類:OMXCodec.cpp
OMXClient.cpp
IOMX.cpp (BpOMX類/BnOMX類)
- 服務端類:
OMX.cppOMXNodeInstance.cpp
示例函式
fillOutpuBuffer. 見原始碼書籤和註釋.
以上類是通過Binder機制實現的.因為這我第一個學習的android Framework.下面先簡單介紹Binder機制:
2. Binder機制簡單介紹
2. Binder機制簡單介紹
binder是android 系統下的一種IPC機制。是程序間互動的一種方式。在開發android應用時,腦袋一定要一直保持C/S結構的思想。
android應用的開發說白了就是通過android提供的一系列的服務來完成自己的目的,咱們剛才也的那個播放器的apk也是需要android提供的播放器的服務來完成的。
apk是一個獨立的程序,android的系統服務也是很多個獨立的程序。binder的功能就是把client 和 service 連線起來。
2.1 入門例項
2.1 入門例項
2.2 總結
2.2 總結
a. IInterface 的繼承類裡宣告將要跨程序呼叫的函式.宣告為純虛擬函式. (如IOMX.h中IOMX類).
a. 如果服務端和客戶端的建立都在同一個程序中,interface_cast會直接獲得xx的Bn例項,就是相當於直接聲明瞭一個xx型別。
OMXClient#getOMX()
c. BnBinder是Service端介面.binder是代理(BpBinder).Service實現業務功能.客戶端需要實現傳送功能.
2.3 Imemory
2.3 Imemory
呼叫OMXCodec Component需要程序間共享記憶體.android在實現程序共享記憶體時使用Binder和匿名共享記憶體實現了一套共享記憶體機制.
MemoryHeapBase和MemoryBase.前者是匿名共享記憶體類,以頁為單位.後者是基於MemoryHeapBase的匿名共享記憶體,使用偏移值表示.ta解決多個clinets一個Service 記憶體共享問題.
重要函式總結:
getMemory
成員函式getMemory用來獲取內部的MemoryHeapBase物件的IMemoryHeap介面.如果成員變數mHeap的值為NULL,就表示這個BpMemory物件尚未建立好匿名共享記憶體,於是,就會通過一個Binder程序間呼叫去Server端請求匿名共享記憶體資訊,在這些資訊中,最重要的就是這個Server端的MemoryHeapBase物件的引用heap了,通過這個引用可以在Client端程序中建立一個BpMemoryHeap遠端介面,最後將這個BpMemoryHeap遠端介面儲存在成員變數mHeap中,同時,從Server端獲得的資訊還包括這塊匿名共享記憶體在整個匿名共享記憶體中的偏移位置以及大小。這樣,這個BpMemory物件中的匿名共享記憶體就準備就緒了。
pointer()
成員函式pointer()用來獲取內部所維護的匿名共享記憶體的基地址;
size()
成員函式size()用來獲取內部所維護的匿名共享記憶體的大小; offset()
用來獲取內部所維護的這部分匿名共享記憶體在整個匿名共享記憶體中的偏移量。
使用例項:
使用例項:
在BpSharedBuffer類的成員函式transact中,向Server端發出了一個請求程式碼為GET_BUFFER的Binder程序間呼叫請求,請求Server端返回一個匿名共享記憶體物件的遠端介面IMemory,它實際指向的是一個BpMemory物件,獲得了這個物件之後,就將它返回給呼叫者;
在BnSharedBuffer類的成員函式onTransact中,當它接收到從Client端傳送過來的程式碼為GET_BUFFER的Binder程序間呼叫請求後,便呼叫其子類的getBuffer成員函式來獲一個匿名共享記憶體物件介面IMemory,它實際指向的是一個MemoryBase物件,獲得了這個物件之後,就把它返回給Client端。
(詳細示例見連結最後一節)
2.4 IOMX
2.4 IOMX
IOMX定義了OMXCodec的介面.如fillBuffer,emptyBuffer.
3. StageFright底層具體實現
3. StageFright底層具體實現
說白了既然StageFright是個播放器,那麼它至少有4大部分:source、demux、decoder、output。
1.source:資料來源,資料的來源不一定都是本地file,也有可能是網路上的各種協議例如:http、rtsp、HLS等。
2.demux解複用:視訊檔案一般情況下都是把音視訊的ES流交織的通過某種規則放在一起。這種規則就是容器規則。現在有很多不同的容器格式。如ts、mp4、flv、mkv、avi、rmvb等等。demux的功能就是把音視訊的ES流從容器中剝離出來,然後分別送到不同的解碼器中。
3.decoder解碼:解碼器–播放器的核心模組。分為音訊和視訊解碼器。
4.output輸出:輸出部分分為音訊和視訊輸出。解碼後的音訊(pcm)和視訊(yuv)的原始資料需要得到音視訊的output模組的支援才能真正的讓人的感官系統(眼和耳)辨識到。
所以,播放器大致分成上述4部分。怎麼抽象的實現這4大部分、以及找到一種合理的方式將這幾部分組織並運動起來。是每個播放器不同的實現方式而已。
AwesomePlayer是實現播放的底層操作者,它在StagefrightPlayer初始化的時候被建立,它負責將對應的音訊視訊和對應的解碼器對應起來。這裡涉及到了MediaExtractor,它會從媒體檔案中抽取到有效的頭資訊。並返回對應的引用。在準備播放的時候AwesomePlayer通過OMXCodec來根據媒體檔案型別建立解碼器,解碼器是駐留在OMX子系統上(OMX是OpenMAX在Android上面的實現),這些解碼器主要用於處理記憶體緩衝,轉化成原始資料格式,這部分的實現程式碼主要在frameworks/av/media/libstagefright/omx 以及frameworks/av/media/libstagefright/codecs 目錄下, Stagefright Media Player和 OMX部件(Component)是通過IPC方式互動的.
AwesomePlayer最終會處理應用層發出的播放,暫停,停止等請求,這些請求往往和媒體型別有關聯對於音訊檔案.AwesomePlayer 將會建立一個AudioPlayer來對檔案進行處理,比如當前檔案只有音訊部分需要播放,這時候AwesomePlayer將會呼叫AudioPlayer::start()進行播放,一旦使用者提交了其他新的請求AudioPlayer會使用MediaSource物件來和底層的OMX子系統進行互動。
3.1 StageFright和OMXCodec通訊
3.1 StageFright和OMXCodec通訊
StageFright資料處理流程
StageFright資料處理流程
重點在read函式.從流中read packet,parse,decode .都在
mVideoSource->read(&mVideoBuffer, &options);函式完成.
3.2底層程序通訊對快取的管理:
3.2底層程序通訊對快取的管理:
read函式將資料讀到快取並處理後,如何傳到OMXCodec Servcie?
read迴圈最終呼叫了:
OMXCodec::drainInputBuffer(BufferInfo *info),最終通過emptyBuffer傳遞快取.
1 2 3 4 5 |
|
通過分別分析客戶端和服務端的emptyBuffer函式得到以下資訊:服務端:
a. 約定buffer_id 為
OMX_BUFFERHEADERTYPE *指標.客戶端
如何對應客戶端讀出的快取給服務端?
1 2 3 |
|
mBuffer即為BufferId.實質為指標.服務端轉為
OMX_BUFFERHEADERTYPE *型別後,呼叫
CopyToOMX(header),呼叫OpenMAX介面,將輸入buffer拷貝到Codec硬體記憶體.
解碼後呼叫
fillOutputBuffer將快取從Codec拷貝回來,同樣是跨程序呼叫,實現函式是
fillBuffer.服務端fillBuffer實現把快取拷貝到對應的buffer_id.
如何初始化快取?
如何初始化快取?
AwesomePlayerAwesomePlayerOMXCodecOMXCodecMemoryDealerMemoryDealerOMXClient.cppOMXClient.cppIOMX.cppIOMX.cppOMX.cppOMX.cppOMXNodeInstance.cppOMXNodeInstance.cpp媒體框架程序allocateBuffersOnPortnew MemoryDealer();然後呼叫MemoryDealer::allocate函式分配得到一個Imemory指標spmOMX->allocateBuffer[backup]這裡還封裝了一次,只有使用remote Codec時才跨程序呼叫上一步中的判斷也是使用遠端Codec時呼叫allocateBufferBackupgetOMX(node)->allocateBufferBackupallocateBufferbackup 這裡就是跨程序呼叫了.IOMX.cpp裡BpIOMX::allocateBufferbackup 使用writeInt32等完成傳送,Service端BnBinder::OnTransact也在這裡實現接收.其中最特別的又是writeStrongBinder,直接把Imemory物件傳送出去,服務端得到一個Imemory的客戶端BpBinder,以便可以訪問共享記憶體.allocateBufferbackup繼續封裝
note right of OMXNodeInstance.cpp:呼叫Codec介面的地方,也就是呼叫晶片廠商提供的介面.
例如OMX_AllocateBuffer分配得到.OMX_BUFFERHEADERTYPE 指標.再通過reply寫回客戶端,也就是客戶端可以得到一個OMX_BUFFERHEADERTYPE 的指標.OMX_BUFFERHEADERTYPE的一個重要成員BufferMeta將Imemory的共享記憶體封裝進來.最後函式返回客戶端一個buffer_id,實際就是一個在服務端儲存的OMX_BUFFERHEADERTYPE指標.然後把這個指標繫結到對應的portIndex, MMediaPlayer使用時再繫結到對應的MediaInfo上.
這樣下次OMXCodec呼叫drainInputBuffer這樣的函式時,就能通過MediaInfo在OMXCodec Component 找到對應bufferId ,進而通過emptyBuffer這樣的跨程序呼叫通知服務端(Component),服務端有bufferId後,轉為BufferHeader指標後遍知道該使用那塊共享記憶體了.
再補充幾個其中的幾個資訊:
通過MemoryDealer分配匿名共享記憶體. 通過
mOMX->allocateBufferWithBackup(mNode, portIndex, mem, &buffer);分配指定大小共享快取.並繫結到buffer_id.(以下帶mOMX 字樣的都是IPC通訊 ,對應的服務端函式在
OMXNodeInstance.cpp).也就是客戶端的每個bufferInfo儲存了一個Service對應的OMX_BUFFERHEADERTYPE物件指標. 將客戶端的共享快取地址通過
mem->pointer();賦值給info::mData
. 注意MediaBuffer的初始化:
info.mMediaBuffer = new MediaBuffer(info.mData, info.mSize);MediaBuffer::mData 即為info.mData.也就是MediaSource,例如MPEG4Extractor 的read /parse 操作也是直接對共享快取操作.
OMXCodec::allocateBuffersOnPort為每個buffer分配快取和index.同時儲存快取資訊到服務端:
mOMX->storeMetaDataInBuffers. 以及把快取佇列傳到MediaSource(此例是MPEG4Exctractor):
mSource->setBuffers(buffers) 服務端分配快取見
OMXNodeInstance::allocateBufferbackup.服務端的共享記憶體指標儲存在BufferMeta buffer_meta.例如emptyBuffer呼叫
CopyTOOMX可以把晶片快取拷貝到共享記憶體快取.
順便學習以下如何寫Binder機制中客戶端的傳送函式,一個BpBinder函式是長這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
通過IMemory的學習可知,IOMX客戶端通過MemoryDealer構建MemoryBase物件是Imemory的遠端介面實現.而OMX Compoent的接收
onTransact中,讀取的是一個匿名共享記憶體物件的遠端介面Imemory,它實質指向一個BpMemory物件.接收完通過
interface_cast(data.readStrongBinder())轉為Imemory呼叫.
//todo
- node_id 是啥? 怎麼區分remoteOMX & localOMX?
OMXCodec.cpp不是直接呼叫OMXCodec的客戶端介面.中間還封裝了一個類OMXClient.cpp.當判斷使用remoteOMX時才呼叫remote介面.即BpOMX.而BpOMX在IOMX.cpp實現.node_id是區分local/remote的關鍵.猜測noteId是標記媒體框架服務(MediaPlayer)的客戶端程式.
Given a node_id and the calling process’ pid, returns true iff the implementation of the OMX interface lives in the same process. 猜測是如果同進程呼叫的話不再重新對映共享記憶體?
OMXObserver是幹麼用的?
如何讀取packet,Source Input?
err = mSource->read(&srcBuffer, &options); 儲存在
MediaBuffer::mData.
MediaCodec和OMXCodec的關係.
MediaCodec和OMXCodec的關係.
其實在openmax介面設計中,他不光能用來當編解碼。通過他的元件可以組成一個完整的播放器,包括sourc、demux、decode、output。
1. android系統中只用openmax來做code,所以android向上抽象了一層OMXCodec,提供給上層播放器用。
播放器中音視訊解碼器mVideosource、mAudiosource都是OMXCodec的例項。
2. OMXCodec通過IOMX 依賴binder機制 獲得 OMX服務,OMX服務 才是openmax 在android中 實現。
3. OMX把軟編解碼和硬體編解碼統一看作外掛的形式管理起來。
由文中可知,MediaPlayer(stageFright),MediaCodec 都是呼叫OMXCodec,stageFright和MediaCodec是平行關係,無相互呼叫. OMXCodec和ACodec都是更底層的東西.
OMXCodec和ACocdec
OMXCodec和ACocdec
NuPlayer呼叫ACodec(支援網路流).