安卓开发首次创建项目一直转圈_Android视频开发进阶(part3Android的Media API)
上兩期我們已經學習了關于視頻播放的基礎知識,還有容器格式文件的結構。那么今天終于可以開始學習安卓平臺的視頻播放知識了!相信大家早就已經等不及了。
Android視頻開發進階-關于視頻的那些術語
視頻開發進階(part2-MP4文件的解析)
但是千萬不要小看之前兩章的基礎知識,理解他們對我們接下來學習安卓平臺的Codec API大有益處。如果沒有理解之前的章節最好還是仔細再復習一遍。。。。:smile:
這一章我們會如此安排
1.Android 平臺視頻播放API的變遷歷史
2.Android 新 Media API的使用
3.一個使用新Media API播放視頻的例子
1.Android 平臺視頻播放API的變遷歷史
在很久很久以前。。。。咳咳咳,言重了。。。。在2012年以前,安卓平臺的視頻播放,一直都是非常簡單的事情(對于大部分開發者來說,因為大部分開發者都不需要深入底層MediaPlayer Service),
非常簡單,創建播放器對象,注入URL,播放,播放完畢之后release。。。。。這給安卓開發者帶來了非常大的便利,應用代碼也非常少。可以說,在2011年之前(尤其是直播業務還沒爆火之前),這款Native Player還是很好用的。但是這款播放器的缺點也非常顯而易見。
- 1.很多格式的容器文件不支持,也不支持自適應視頻播放(Adaptive Streaming)
- 2.應用開發者很難debug播放器,MediaPlayer的代碼很多都是Native Method。并不在Java層。
- 3.很難做自定義的拓展和設置,比如緩沖的大小,下載進度等等。
正是因為MediaPlayer本身的實現對開發者是完全透明的,所以它也越來越神秘,也逐漸跟不上現在的業務對播放器的需求了。所以谷歌也意識到了這一點,在2012年的Google IO大會上,谷歌宣布了Android Jelly Bean,也就是4.3之后,安卓平臺release新的Media Codec API組。這些API不再像之前傻瓜式的MediaPlayer一樣,而是把API組件設計的面向視頻播放的更底層概念。比如,編解碼API,容器文件讀取器Extractor API等等。
以上的圖都是從Google IO大會的視頻進行截圖而來的。我們可以從結構圖里看出,原來的MediaPlayer把Extractor,和Codec API全部封鎖在了Framework層,應用層完全接觸不到。在新的API設計里面,這些都挪到了應用層(其實雖然MediaCodec API,就是編解碼API還在Framework,但是應用層可以調用他們)
2.Android Codec API的使用
在全新的Media API里面,最最最重要的就是MediaExtractor和MediaCodec這兩個類,第一個可以對容器文件進行讀取控制,第二個就是對數據進行編解碼的API。
「MediaExtractor」
MediaExtractor可以同一個URL,獲取容器文件的軌道數量,軌道信息(Track)。在確定了軌道信息之后,可以選擇想要解碼的軌道(只能選擇一個,所以音軌和視頻軌道需要兩個不同MediaExtractor給兩個不同MediaCodec解碼),再從該軌道不停的讀取數據放入MediaCodec API進行解碼。
「MediaCodec」
MediaCodec API則是創建的時候就需要選擇Codec的類型。然后編碼的時候需要安卓平臺顯示視頻的Surface,MediaCrypto對象(如果視頻被加密的話,這個細節我會在DRM章節介紹)。一個MediaCodec在創建之后會在內部維護兩個對列(Queue),一個是InputQueue,一個是OutputQueue。類似生產者消費者的模式,MediaCodec會不停的從InputQueue獲取數據(InputQueue的數據又是又MediaExtractor提供),解碼,再把解碼之后的數據放入OutputQueue,再提供給Surface讓其視頻內容。這兩個類協作的方式如下圖
3.一個使用新Media API播放視頻的例子
那么我們是時候看看源代碼了!我們這次使用的是谷歌一個非官方維護的開源項目,叫grafika。這個項目其實是一個Demo app,里面使用新的Media API做了很多有意思的小實例。其中就包括我們這次要看的,使用MediaAPI播放視頻的例子。這里只有三個方法,調用順序也是依次進行。
public?void?playWithUrl()?throws?IOException?{????????MediaExtractor?extractor?=?null;
????????MediaCodec?decoder?=?null;
????????try?{
????????????/**
?????????????*?創建一個MediaExtractor對象
?????????????*/
????????????extractor?=?new?MediaExtractor();
????????????/**
?????????????*?設置Extractor的source,這里可以把mp4的url傳進來,
?????????????*/
????????????extractor.setDataSource(context,?Uri.parse(url),new?HashMap());
????????????/**
?????????????*?這里我們需要選擇我們要解析的軌道,我們在這個例子里面只解析視頻軌道
?????????????*/
????????????int?trackIndex?=?selectTrack(extractor);if?(trackIndex?????????????????throw?new?RuntimeException("No?video?track?found?in?"?+?url);
????????????}
????????????/**
?????????????*?選擇視頻軌道的索引
?????????????*/
????????????extractor.selectTrack(trackIndex);
????????????/**
?????????????*?獲取軌道的音視頻格式,這個格式和Codec有關,可以點擊MediaFormat類看看有哪些
?????????????*/
????????????MediaFormat?format?=?extractor.getTrackFormat(trackIndex);
????????????String?mime?=?format.getString(MediaFormat.KEY_MIME);
????????????/**
?????????????*?創建一個MediaCodec對象
?????????????*/
????????????decoder?=?MediaCodec.createDecoderByType(mime);
????????????/**
?????????????*?設置格式,和視頻輸出的Surface,開始解碼
?????????????*/
????????????decoder.configure(format,?mOutputSurface,?null,?0);
????????????decoder.start();
????????????doExtract(extractor,?trackIndex,?decoder,?mFrameCallback);
????????}
????????catch?(?Exception?e?){
????????????e.printStackTrace();
????????}
????????finally?{
????????????//?release?everything?we?grabbedif?(decoder?!=?null)?{
????????????????decoder.stop();
????????????????decoder.release();
????????????????decoder?=?null;
????????????}if?(extractor?!=?null)?{
????????????????extractor.release();
????????????????extractor?=?null;
????????????}
????????}
????}?/**
?????*?我們用Extractor獲取軌道數量,然后遍歷他們,只要找到第一個軌道是Video的就返回
?????*/
????private?static?int?selectTrack(MediaExtractor?extractor)?{
????????//?Select?the?first?video?track?we?find,?ignore?the?rest.
????????int?numTracks?=?extractor.getTrackCount();
????????for?(int?i?=?0;?i?????????????MediaFormat?format?=?extractor.getTrackFormat(i);
????????????String?mime?=?format.getString(MediaFormat.KEY_MIME);
????????????if?(mime.startsWith("video/"))?{
????????????????if?(VERBOSE)?{
????????????????????Log.d(TAG,?"Extractor?selected?track?"?+?i?+?"?("?+?mime?+?"):?"?+?format);
????????????????}
????????????????return?i;
????????????}
????????}
????????return?-1;
????}
?private?void?doExtract(MediaExtractor?extractor,?int?trackIndex,?MediaCodec?decoder,
???????????????????????????FrameCallback?frameCallback)?{
????????final?int?TIMEOUT_USEC?=?10000;
????????/**
?????????*?獲取MediaCodec的輸入隊列,是一個數組
?????????*/
????????ByteBuffer[]?decoderInputBuffers?=?decoder.getInputBuffers();
????????int?inputChunk?=?0;
????????long?firstInputTimeNsec?=?-1;
????????boolean?outputDone?=?false;
????????boolean?inputDone?=?false;
????????/**
?????????*?用while做循環
?????????*/
????????while?(!outputDone)?{
????????????if?(VERBOSE)?Log.d(TAG,?"loop");
????????????if?(mIsStopRequested)?{
????????????????Log.d(TAG,?"Stop?requested");
????????????????return;
????????????}
????????????//?Feed?more?data?to?the?decoder.
????????????/**
?????????????*?不停的輸入數據知道輸入隊列滿為止
?????????????*/
????????????if?(!inputDone)?{
????????????????/**
?????????????????*?這個方法返回輸入隊列數組可以放數據的位置,即一個索引
?????????????????*/
????????????????int?inputBufIndex?=?decoder.dequeueInputBuffer(TIMEOUT_USEC);
????????????????/**
?????????????????*?如果輸入隊列還有位置
?????????????????*/
????????????????if?(inputBufIndex?>=?0)?{
????????????????????if?(firstInputTimeNsec?==?-1)?{
????????????????????????firstInputTimeNsec?=?System.nanoTime();
????????????????????}
????????????????????ByteBuffer?inputBuf?=?decoderInputBuffers[inputBufIndex];
????????????????????//?Read?the?sample?data?into?the?ByteBuffer.??This?neither?respects?nor
????????????????????//?updates?inputBuf's?position,?limit,?etc.
????????????????????/**
?????????????????????*?用Extractor讀取一個sample的數據,并且放入輸入隊列
?????????????????????*/
????????????????????int?chunkSize?=?extractor.readSampleData(inputBuf,?0);
????????????????????/**
?????????????????????*?如果chunk size是小于0,證明我們已經讀取完畢這個軌道的數據了。
?????????????????????*/
????????????????????if?(chunkSize?????????????????????????//?End?of?stream?--?send?empty?frame?with?EOS?flag?set.
????????????????????????decoder.queueInputBuffer(inputBufIndex,?0,?0,?0L,
????????????????????????????????MediaCodec.BUFFER_FLAG_END_OF_STREAM);
????????????????????????inputDone?=?true;
????????????????????????if?(VERBOSE)?Log.d(TAG,?"sent?input?EOS");
????????????????????}
????????????????????else?{
????????????????????????if?(extractor.getSampleTrackIndex()?!=?trackIndex)?{
????????????????????????????Log.w(TAG,?"WEIRD:?got?sample?from?track?"?+
????????????????????????????????????extractor.getSampleTrackIndex()?+?",?expected?"?+?trackIndex);
????????????????????????}
????????????????????????long?presentationTimeUs?=?extractor.getSampleTime();
????????????????????????decoder.queueInputBuffer(inputBufIndex,?0,?chunkSize,
????????????????????????????????presentationTimeUs,?0?/*flags*/);
????????????????????????if?(VERBOSE)?{
????????????????????????????Log.d(TAG,?"submitted?frame?"?+?inputChunk?+?"?to?dec,?size="?+
????????????????????????????????????chunkSize);
????????????????????????}
????????????????????????inputChunk++;
????????????????????????/**
?????????????????????????*?Extractor移動一個sample的位置,下一次再調用extractor.readSampleData()就會讀取下一個sample
?????????????????????????*/
????????????????????????extractor.advance();
????????????????????}
????????????????}?else?{
????????????????????if?(VERBOSE)?Log.d(TAG,?"input?buffer?not?available");
????????????????}
????????????}
????????????if?(!outputDone)?{
????????????????/**
?????????????????*?開始把輸出隊列的數據拿出來,decodeStatus只要不是大于零的整數都是異常的現象,需要處理
?????????????????*/
????????????????int?decoderStatus?=?decoder.dequeueOutputBuffer(mBufferInfo,?TIMEOUT_USEC);
????????????????if?(decoderStatus?==?MediaCodec.INFO_TRY_AGAIN_LATER)?{
????????????????????//?no?output?available?yet
????????????????????if?(VERBOSE)?Log.d(TAG,?"no?output?from?decoder?available");
????????????????}?else?if?(decoderStatus?==?MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)?{
????????????????????//?not?important?for?us,?since?we're?using?Surface
????????????????????if?(VERBOSE)?Log.d(TAG,?"decoder?output?buffers?changed");
????????????????}?else?if?(decoderStatus?==?MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)?{
????????????????????MediaFormat?newFormat?=?decoder.getOutputFormat();
????????????????????if?(VERBOSE)?Log.d(TAG,?"decoder?output?format?changed:?"?+?newFormat);
????????????????}?else?if?(decoderStatus?????????????????????throw?new?RuntimeException(
????????????????????????????"unexpected?result?from?decoder.dequeueOutputBuffer:?"?+
????????????????????????????????????decoderStatus);
????????????????}?else?{?//?decoderStatus?>=?0
????????????????????if?((mBufferInfo.flags?&?MediaCodec.BUFFER_FLAG_END_OF_STREAM)?!=?0)?{
????????????????????????if?(VERBOSE)?Log.d(TAG,?"output?EOS");
????????????????????????????outputDone?=?true;
????????????????????}
????????????????????boolean?doRender?=?(mBufferInfo.size?!=?0);
????????????????????if?(doRender?&&?frameCallback?!=?null)?{
????????????????????????frameCallback.preRender(mBufferInfo.presentationTimeUs);
????????????????????}
????????????????????/**
?????????????????????*?只要我們調用了decoder.releaseOutputBuffer(),
?????????????????????*?就會把輸出隊列的數據全部輸出到Surface上顯示,并且釋放輸出隊列的數據
?????????????????????*/
????????????????????decoder.releaseOutputBuffer(decoderStatus,?doRender);
????????????????}
????????????}
????????}
????}
當然,大家可能會有很多問題,比如,你說了可拓展性呢?Extractor不還是只能讀取指定的格式?等等等等的問題。我會再接下來的幾章慢慢的講解,通過谷歌的開源播放器ExoPlayer,我們可以深入到如何使用,拓展這些API。下一章我會先講解自適應視頻的概念,然后會通過Exoplayer的例子來闡述如何使用Media API播放自適應視頻。
---END---轉發至朋友圈,是絕對的真愛讓我知道你在看
總結
以上是生活随笔為你收集整理的安卓开发首次创建项目一直转圈_Android视频开发进阶(part3Android的Media API)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tcppwebbrower 关闭安全警报
- 下一篇: 异步通知和同步通知_CCF NOI 20