一、 StageFright介紹
Android froyo版本號多媒體引擎作了變更,新加入了stagefright框架,並且默認狀況android選擇stagefright,並無全然拋棄opencore,主要是作了一個OMX層,不過對 opencore的omx-component部分作了引用。stagefright是在MediaPlayerService這一層加入的,和opencore是並列的。Stagefright在 Android中是以shared library的形式存在(libstagefright.so),當中的module -- AwesomePlayer可用來播放video/audio。 AwesomePlayer提供不少API,可以讓上層的應用程序(Java/JNI)來調用。
二、 StageFright數據流封裝
2.1》由數據源DataSource生成MediaExtractor。經過MediaExtractor::Create(dataSource)來實現。Create方法經過兩步來生成對應的 MediaExtractor(MediaExtractor.cpp):
經過dataSource->sniff來探測數據類型
生成對應的Extractor:
if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
|| !strcasecmp(mime, "audio/mp4")) {
return new MPEG4Extractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
return new MP3Extractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
return new AMRExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
return new WAVExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
return new OggExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
return new MatroskaExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
return new MPEG2TSExtractor(source);
}
2.2》把音視頻軌道分離,生成mVideoTrack和mAudioTrack兩個MediaSource。代碼例如如下(AwesomePlayer.cpp):
if (!haveVideo && !strncasecmp(mime, "video/", 6)) {
setVideoSource(extractor->getTrack(i));
haveVideo = true;
} else if (!haveAudio && !strncasecmp(mime, "audio/", 6)) {
setAudioSource(extractor->getTrack(i));
haveAudio = true;
}
2.3》獲得的這兩個MediaSource,僅僅具備parser功能,沒有decode功能。還需要對這兩個MediaSource作進一步的包裝,獲取了兩個MediaSource(具備parse和decode功能):
mVideoSource = OMXCodec::Create(
mClient.interface(), mVideoTrack->getFormat(),
false, // createEncoder
mVideoTrack,
NULL, flags);
mAudioSource = OMXCodec::Create(
mClient.interface(), mAudioTrack->getFormat(),
false, // createEncoder
mAudioTrack);
當調用MediaSource.start()方法後,它的內部就會開始從數據源獲取數據並解析,等到緩衝區滿後便中止。在AwesomePlayer裏就可以調用MediaSource的read方法讀取解碼後的數據。
對於mVideoSource來講,讀取的數據:mVideoSource->read(&mVideoBuffer, &options)交給顯示模塊進行渲染,mVideoRenderer->render(mVideoBuffer);
對mAudioSource來講,用mAudioPlayer對mAudioSource進行封裝,而後由mAudioPlayer負責讀取數據和播放控制。
三、 StageFright的Decode
通過「數據流的封裝」獲得的兩個MediaSource,事實上是兩個OMXCodec。AwesomePlayer和mAudioPlayer都是從MediaSource中獲得數據進行播放。AwesomePlayer獲得的是終於需要渲染的原始視頻數據,而mAudioPlayer讀取的是終於需要播放的原始音頻數據。也就是說,從OMXCodec中讀到的數據已是原始數據了。
OMXCodec是怎麼把數據源通過parse、decode兩步之後轉化成原始數據的。從OMXCodec::Create這個構造方法開始,它的參數:
IOMX &omx指的是一個OMXNodeInstance對象的實例。
MetaData &meta這個參數由MediaSource.getFormat獲取獲得。這個對象的主要成員就是一個KeyedVector<uint32_t, typed_data> mItems,裏面存放了一些表明MediaSource格式信息的名值對。
bool createEncoder指明這個OMXCodec是編碼仍是解碼。
MediaSource &source是一個MediaExtractor。
char *matchComponentName指定一種Codec用於生成這個OMXCodec。
先使用findMatchingCodecs尋找相應的Codec,找到之後爲當前IOMX分配節點並註冊事件監聽器:omx->allocateNode(componentName, observer, &node)。最後,把IOMX封裝進一個OMXCodec:
sp<OMXCodec> codec = new OMXCodec(
omx, node, quirks,
createEncoder, mime, componentName,
source);
這樣就獲得了OMXCodec。
AwesomePlayer中獲得這個OMXCodec後,首先調用mVideoSource->start()進行初始化。 OMXCodec初始化主要是作兩件事:
向OpenMAX發送開始命令。mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle)
調用allocateBuffers()分配兩個緩衝區,存放在Vector<BufferInfo> mPortBuffers[2]中,分別用於輸入和輸出。
AwesomePlayer開始播放後,經過mVideoSource->read(&mVideoBuffer, &options)讀取數據。mVideoSource->read(&mVideoBuffer, &options)詳細是調用OMXCodec.read來讀取數據。而OMXCodec.read主要分兩步來實現數據的讀取:
經過調用drainInputBuffers()對mPortBuffers[kPortIndexInput]進行填充,這一步完畢 parse。由OpenMAX從數據源把demux後的數據讀取到輸入緩衝區,做爲OpenMAX的輸入。
經過fillOutputBuffers()對mPortBuffers[kPortIndexOutput]進行填充,這一步完畢 decode。由OpenMAX對輸入緩衝區中的數據進行解碼,而後把解碼後可以顯示的視頻數據輸出到輸出緩衝區。
AwesomePlayer經過mVideoRenderer->render(mVideoBuffer)對通過parse和decode 處理的數據進行渲染。一個mVideoRenderer事實上就是一個包裝了IOMXRenderer的AwesomeRemoteRenderer:
mVideoRenderer = new AwesomeRemoteRenderer(
mClient.interface()->createRenderer(
mISurface, component,
(OMX_COLOR_FORMATTYPE)format,
decodedWidth, decodedHeight,
mVideoWidth, mVideoHeight,
rotationDegrees));
四、 StageFright處理流程
Audioplayer 爲AwesomePlayer的成員,audioplayer經過callback來驅動數據的獲取,awesomeplayer則是經過 videoevent來驅動。兩者有個共性,就是數據的獲取都抽象成mSource->Read()來完畢,且read內部把parser和dec 綁在一塊兒。Stagefright AV同步部分,audio全然是callback驅動數據流,video部分在onVideoEvent裏會獲取audio的時間戳,是傳統的AV時間戳作同步。
4.1》AwesomePlayer的Video主要有下面幾個成員:
mVideoSource(解碼視頻)
mVideoTrack(從多媒體文件裏讀取視頻數據)
mVideoRenderer(對解碼好的視頻進行格式轉換,android使用的格式爲RGB565)
mISurface(重畫圖層)
mQueue(event事件隊列)
4.2》stagefright執行時的Audio部分抽象流程例如如下:
設置mUri的路徑
啓動mQueue,建立一個線程來執行 threadEntry(命名爲TimedEventQueue,這個線程就是event調度器)
打開mUri所指定的文件的頭部,則會依據類型選擇不一樣的分離器(如MPEG4Extractor)
使用 MPEG4Extractor對MP4進行音視頻軌道的分離,並返回MPEG4Source類型的視頻軌道給mVideoTrack
依據 mVideoTrack中的編碼類型來選擇解碼器,avc的編碼類型會選擇AVCDecoder,並返回給mVideoSource,並設置mVideoSource中的mSource爲mVideoTrack
插入onVideoEvent到Queue中,開始解碼播放
經過mVideoSource對象來讀取解析好的視頻buffer
假設解析好的buffer還沒到AV時間戳同步的時刻,則推遲到下一輪操做
mVideoRenderer爲空,則進行初始化(假設不使用 OMX會將mVideoRenderer設置爲AwesomeLocalRenderer)
經過mVideoRenderer對象將解析好的視頻buffer轉換成RGB565格式,併發給display模塊進行圖像繪製
將onVideoEvent又一次插入event調度器來循環
4.3》數據由源到終於解碼後的流程例如如下:
URI,FD
|
DataSource
|
MediaExtractor
|
mVideoTrack mAudioTrack//音視頻數據流
|
mVideoSource mAudioSource//音視頻解碼器
| |
mVideoBuffer mAudioPlayer
說明:
設置DataSource,數據源可以兩種URI和FD。URI可以http://,rtsp://等。FD是一個本地文件描寫敘述符,能過FD,可以找到相應的文件。
由DataSource生成MediaExtractor。經過sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);來實現。 MediaExtractor::Create(dataSource)會依據不一樣的數據內容建立不一樣的數據讀取對象。
經過調用setVideoSource由MediaExtractor分解生成音頻數據流(mAudioTrack)和視頻數據流(mVideoTrack)。
onPrepareAsyncEvent()假設DataSource是URL的話,依據地址獲取數據,並開始緩衝,直到獲取到mVideoTrack和mAudioTrack。mVideoTrack和mAudioTrack經過調用initVideoDecoder()和initAudioDecoder()來生成 mVideoSource和mAudioSource這兩個音視頻解碼器。而後調用postBufferingEvent_l()提交事件開啓緩衝。
數據緩衝的運行函數是onBufferingUpdate()。緩衝區有足夠的數據可以播放時,調用play_l()開始播放。play_l()中關鍵是調用了postVideoEvent_l(),提交了 mVideoEvent。這個事件運行時會調用函數onVideoEvent()。這個函數經過調用 mVideoSource->read(&mVideoBuffer, &options)進行視頻解碼。音頻解碼經過mAudioPlayer實現。
視頻解碼器解碼後經過mVideoSource->read讀取一幀幀的數據,放到mVideoBuffer中,最後經過 mVideoRenderer->render(mVideoBuffer)把視頻數據發送到顯示模塊。當需要暫停或中止時,調用cancelPlayerEvents來提交事件用來中止解碼,還可以選擇是否繼續緩衝數據。
五、 代碼標記Log
根據第4》項StageFright描寫敘述的Vide視頻播放流程,做Log標記跟蹤視頻DATA獲取、CODEC過程。從AwesomePlayer.cpp中方法着手,過程例如如下:
在改動的/mydroid/frameworks/base/media/libstagefrigh/下,用mm編譯,並調試直到生成對應的.so文件。注:贊成單模塊編譯時,需事先在/mydroid下贊成. ./build/envsetup.sh文件。
在/mydroid/文件夾下make進行整體編譯,生成system.img文件。說明:先單模塊編譯,後再整體編譯的優勢是,可以縮短調試編譯的時間。
將system.img文件copy到/android-sdk-linux/platforms/android-8/下。注意:事先備份原有的system.img。
帶sdcard啓動模擬器,在/android-sdk-linux/tools/下執行./adb shell文件,再執行logcat
打開Gallery選擇視頻文件執行,並同步查看log。
反饋結果例如如下:
I/ActivityManager( 61): Starting: Intent { act=android.intent.action.VIEW dat=content://media/external/video/media/5 typ=video/mp4 cmp=com.cooliris.media/.MovieView } from pid 327
I/RenderView( 327): OnPause RenderView com.cooliris.media.RenderView@4054a3b0
E/AwesomePlayer( 34): beginning AwesomePlayer... by jay remarked...
E/AwesomePlayer( 34): returning AwesomeEvent...by jay remarked...
E/AwesomePlayer( 34): returning AwesomeEvent...by jay remarked...
E/AwesomePlayer( 34): returning AwesomeEvent...by jay remarked...
E/AwesomePlayer( 34): returning AwesomeEvent...by jay remarked...
E/AwesomePlayer( 34): ending AwesomePlayer... by jay remarked...
E/AwesomePlayer( 34): setting video source now... by jay remarked...
E/AwesomePlayer( 34): setting Video Type... by jay remarked...
E/AwesomePlayer( 34): returning AwesomeEvent...by jay remarked...
E/AwesomePlayer( 34): beginning initVideoDecoder by jay remarked...
D/MediaPlayer( 327): getMetadata
I/ActivityManager( 61): Displayed com.cooliris.media/.MovieView: +1s761ms
E/AwesomePlayer( 34): beginning AwesomeLocalRenderer init ...by jay remarked...
E/AwesomePlayer( 34): returning open(libstagefrighthw.so) correctly by jay remarked...
E/MemoryHeapBase( 34): error opening /dev/pmem_adsp: No such file or directory
I/SoftwareRenderer( 34): Creating physical memory heap failed, reverting to regular heap.
E/AwesomePlayer( 34): ending AwesomeLocalRenderer init close ...by jay remarked...
E/AwesomePlayer( 34): returning AwesomeLocalRenderer...by jay remarked...
I/CacheService( 327): Starting CacheServicenode