AwesomePlayer實現過程分析

在Android的多媒體框架中,stagefrightplayer是對Awesomeplayer的封裝,是AwesomePlayer的代理,所以這裏面實際幹活的當然是我們今天的主角-AwesomePlayer。AwesomePlayer說白了也是一個普通的播放器,他與VCL、mplayer、ffmpeg等開源的結構是一致的,只是實現的方式有所不同,這裏就按照以下四個步驟來分析AwesomePlayer的實現。

一、多媒體播放器的基本模型

二、AwesomePlayer的總體結構

三、AwesomePlayer的驅動方式

四、AwesomePlayer的關鍵成員分析

五、AwesomePlayer的實現過程代碼分析

一、多媒體播放器的基本模型

1. source:數據源,數據的來源不一定都是本地file,也有可能是網路上的各種協議例如:http、rtsp、HLS等。source的任務就是把數據源抽象出來,爲下一個demux模塊提供它需要的穩定的數據流。demux不用關心數據到底是從什麼地方來的。
2. demux解複用:視頻文件一般情況下都是把音視頻的ES流交織的通過某種規則放在一起。這種規則就是容器規則。現在有很多不同的容器格式。如ts、mp4、flv、mkv、avi、rmvb等等。demux的功能就是把音視頻的ES流從容器中剝離出來,然後分別送到不同的解碼器中。其實音頻和視頻本身就是2個獨立的系統。容器把它們包在了一起。但是他們都是獨立解碼的,所以解碼之前,需要把它分別獨立出來。demux就是幹這活的,他爲下一步decoder解碼提供了數據流。
3. decoder解碼:解碼器——播放器的核心模塊。分爲音頻和視頻解碼器。影像在錄製後, 原始的音視頻都是佔用大量空間, 而且是冗餘度較高的數據. 因此, 通常會在製作的時候就會進行某種壓縮 ( 壓縮技術就是將數據中的冗餘信息去除數據之間的相關性 ). 這就是我們熟知的音視頻編碼格式, 包括MPEG1(VCD)/ MPEG2(DVD)/ MPEG4 / H.264 等等. 音視頻解碼器的作用就是把這些壓縮了的數據還原成原始的音視頻數據。當然, 編碼解碼過程基本上都是有損的 .解碼器的作用就是把編碼後的數據還原成原始數據。
4. output輸出:輸出部分分爲音頻和視頻輸出。解碼後的音頻(pcm)和視頻(yuv)的原始數據需要得到音視頻的output模塊的支持才能真正的讓人的感官系統(眼和耳)辨識到。

所以,一個多媒體播放器大致分成上述4部分。怎麼抽象的實現這4大部分,以及找到一種合理的方式將這幾部分組織並運動起來。是每個播放器不同的實現方式而已。

二、AwesomePlayer的總體結構

mExtractor屬於MediaExtractor類, 對於每種格式的媒體文件,均需要實現一個MediaExtractor 的子類,例如AVI文件的extractor類爲AVIExtractor,在構造函數中完成對數據的解析,主要信息由:媒體文件中流的個數,音頻流的採樣率、聲道數、量化位數等,視頻流的寬度、高度、幀率等信息。

mAudioTrack和mVideoTrack 屬於MediaSource類,mVideoTrack和mAudioTrack在 onPrepareAsyncEvent事件被觸發時,由MediaExtractor分離出來.對於每個流,都對應一個單獨的MediaSource,以avi爲例,爲AVISource,MediaSource 提供了狀態接口(start stop),數據讀取接口(read),參數查詢接口(getFormat)。其中調用一次read函數,可以認爲是讀取對應的一個數據包。一般而言,由於MediaExtractor 負責解析工作,因此MediaSource 的read操作一般也通過MediaExtractor 的接口獲取offset和size,對於MediaSource 只進行數據的讀取工作,不參與解析,VideoTrack與AudioTrack指的是Extractor(即demux)的兩個通道,從這裏輸出的分別就是單純的解複用後的Video/Audio流了。

mAudioSource 和mVideoSource也屬於MediaSource類,這裏mAudioSource &mVideoSource可以認爲是awesomeplayer與decoder之間的橋樑,awesomeplayer從mAudioSource &mVideoSource 裏獲取數據,進行播放,而decoder則負責解碼並向mAudioSource &mVideoSource 填充數據, 這裏awesomeplayer與decoder的通信是通過OMXClient mClient; 成員進行的,OMX*解碼器是一個服務,每個awesomeplayer對象都包含一個單獨的client端與之交互。

 mAudioPlayer屬於AudioPlayer類,mVideoRenderer屬於AwesomeRenderer類,mAudioPlayer 是負責音頻輸出的模塊,主要封裝關係爲:mAudioPlayer->mAudioSink->mAudioTrack(這裏的mAudioTrack 與前面不同,此處爲AudioTrack對象)實際進行音頻播放的對象爲mAudioTrack-audioflinger 結構,mVideoRenderer 是負責視頻顯示的模塊,封裝關係爲:mVideoRenderer ->mTarget(SoftwareRenderer)->mNativeWindow ,即VideoRenderer + Surface即視頻的輸出,AudioSink即音頻的輸出.

三、AwesomePlayer的驅動方式

這裏就不得不提到android中的一個類即TimedEventQueue,這是一個消息處理類, 這裏通過對每個事件消息提供一個fire函數完成相應的操作。而整個播放過程的驅動方式爲遞歸的觸發mVideoEvent 事件來控制媒體文件的播放。TimedEventQueue資料建議自行查找下。

說明:詳細可參考下面的代碼分析

    awesomeplayer中主要有如下幾個事件

    sp<TimedEventQueue::Event> mVideoEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoEvent);   --- 顯示一幀畫面,並負責同步處理
    sp<TimedEventQueue::Event> mStreamDoneEvent = new AwesomeEvent(this, &AwesomePlayer::onStreamDone);   -- 播放結束的處理

    sp<TimedEventQueue::Event> mBufferingEvent = new AwesomeEvent(this, &AwesomePlayer::onBufferingUpdate); -- cache數據
    sp<TimedEventQueue::Event> mCheckAudioStatusEvent = new AwesomeEvent(this, &AwesomePlayer::onCheckAudioStatus); -- 監測audio 狀態:seek以及播放結束
    sp<TimedEventQueue::Event> mVideoLagEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoLagUpdate);  -- 監測video 解碼性能

    sp<TimedEventQueue::Event> mAsyncPrepareEvent = new AwesomeEvent(this, &AwesomePlayer::onPrepareAsyncEvent); -- 完成prepareAsync工作

四、AwesomePlayer的關鍵成員分析

關鍵成員之demux相關

sp<MediaSource> mVideoTrack;
sp<MediaSource> mAudioTrack

分別代表一個視頻軌道和音頻軌道,用於提取視頻流和音頻流(Demux後但未解碼的數據). mVideoTrack和mAudioTrack在 onPrepareAsyncEvent事件被觸發時,由MediaExtractor分離出來.

void AwesomePlayer::onPrepareAsyncEvent() {   
        status_t err = finishSetDataSource_l();   
}  
 
status_t AwesomePlayer::finishSetDataSource_l() {   
 sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);   
    return setDataSource_l(extractor);   
}   
 
status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {   
    for (size_t i = 0; i < extractor->countTracks(); ++i) {   
        sp<MetaData> meta = extractor->getTrackMetaData(i);   
 
        const char *mime;   
        CHECK(meta->findCString(kKeyMIMEType, &mime));   
 
        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;   
        if (haveAudio && haveVideo) {   
            break;   
        }   
    }   
}  

這裏依據提供的dataSource建立對應的MediaExtractor,這裏說明下,有了dataSource,就可以從文件中讀取數據,就可以通過分析文件頭解析出文件具體是哪種格式,然後建立相應的MediaExtractor,之前有介紹,在MediaExtractor 建立的時候便完成了對文件的解析,包括流數量,各個流的具體信息等,下面就可以直接使用了,得到extractor之後,通過setVideoSource() setAudioSource()產生獨立的mVideoTrack(視頻)、mAudioTrack(音頻)數據流,分別爲音視頻解碼器提供有各自需要的數據流。從上面這段代碼可以看到AwesomePlayer默認採用第一個VideoTrack和第一個AudioTrack,

流數量可以通過extractor->countTracks() 得到,每個流對應的信息存儲在一個MetaData中通過extractor->getTrackMetaData(i);

獲取kKeyMIMEType參數,此參數標示了流類型是音頻 視頻 還是字幕

關鍵成員之音頻相關

sp<MediaSource> mAudioSource;

mAudioSource可以認爲是一個音頻解碼器的封裝,

sp<MediaPlayerBase::AudioSink> mAudioSink;

mAudioSink代表一個音頻輸出設備.用於播放解碼後的音頻數據

AudioPlayer *mAudioPlayer;

mAudioPlayer把mAudioSource和mAudioSink包起來,完成一個音頻播放器的功能.如start, stop, pause, seek 等.

AudioPlayer和AudioSink通過Callback建立關聯.當AudioSink可以輸出音頻時,會通過回調通知AudioPlayer填充音頻數據.而此時AudioPlayer會嘗試從AudioSource讀取音頻數據.

關鍵成員之視頻相關

sp<MediaSource> mVideoSource

mVideoSource可以認爲是一個視頻解碼器的封裝,用於產生視頻圖像供AwesomeRender渲染, mVideoSource的數據源則由mVideoTrack提供.mVideoSource由OMXCodec創建.

status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {   
    mVideoSource = OMXCodec::Create(   
            mClient.interface(), mVideoTrack->getFormat(),   
            false, // createEncoder   
            mVideoTrack,   
            NULL, flags);   
}   
sp<AwesomeRenderer> mVideoRenderer 

mVideoRenderer負責將解碼後的圖片渲染輸出

sp<ISurface> mISurface

mISurface供播放器渲染的畫布

關鍵數據之其他

OMXClient mClient

OMX可以理解爲一個編解碼器的庫,AwesomePlayer利用OMXClient跟OMXIL進行通信.這裏OMX IL類似於一個服務端. AwesomePlayer作爲一個客戶端,請求OMX IL進行解碼的工作.可參考下圖:

TimedEventQueue mQueue

TimedEventQueue一個消息隊列類,可以認爲TimeEventQueue 是一個調度系統,調度的對象是事件Event,一個TimeEventQueue 可以同時處理多個事件TimedEventQueue中維護了一個隊列,外面通過調用其提供的方法postEvent, postEventWithDelay等等來向隊列添加事件,在執行完mQueue.start()後,TimedEventQueue將啓動一個線程,用來取出隊列中的事件,並執行之

AwesomePlayer採用定時器隊列的方式進行運作. mQueue在MediaPlayer調用 prepare的時候就開始運作, (而不是MediaPlayer.start).

status_t AwesomePlayer::prepareAsync() {  
    if (!mQueueStarted) {  
        mQueue.start();  
        mQueueStarted = true;  
    }  
    return OK;  
}  

AwesomePlayer處理了幾個定時器事件,包括:

(1)onVideoEvent();//

(2)onStreamDone();//

(3)onBufferingUpdate();//

(4)onCheckAudioStatus();//

(5)onPrepareAsyncEvent();//

總結:從關鍵的成員可以看出,AwesomePlayer擁有視頻源和音頻源 (VideoTrack, AudioTrack),有音視頻解碼器(VideoSoure, AudioSource),可以渲染圖像(AwesomeRenderer) , 可以輸出聲音 (AudioSink),具備一個播放器完整的材料了.

五、AwesomePlayer的實現過程代碼分析

5.1總的基本播放流程分析

還是從之前的一個播放器的播放序列看起

StagefrightPlayer player =newStagefrightPlayer();  
player->setDataSource(*)  
player->prepareAsync()  
player->start();  

①由於StagefrightPlayer是AwesomePlayer的代理,所以我們接下來看下AwesomePlayer的構造函數

AwesomePlayer::AwesomePlayer()  
    : mQueueStarted(false),  
      mUIDValid(false),  
      mTimeSource(NULL),  
      mVideoRenderingStarted(false),  
      mVideoRendererIsPreview(false),  
      mAudioPlayer(NULL),  
      mDisplayWidth(0),  
      mDisplayHeight(0),  
      mVideoScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW),  
      mFlags(0),  
      mExtractorFlags(0),  
      mVideoBuffer(NULL),  
      mDecryptHandle(NULL),  
      mLastVideoTimeUs(-1),  
      mTextDriver(NULL) {  
    CHECK_EQ(mClient.connect(), (status_t)OK);  
    DataSource::RegisterDefaultSniffers();  
    mVideoEvent =newAwesomeEvent(this, &AwesomePlayer::onVideoEvent);  
    mVideoEventPending =false;  
    mStreamDoneEvent =newAwesomeEvent(this, &AwesomePlayer::onStreamDone);  
    mStreamDoneEventPending =false;  
    mBufferingEvent =newAwesomeEvent(this, &AwesomePlayer::onBufferingUpdate);  
    mBufferingEventPending =false;  
    mVideoLagEvent =newAwesomeEvent(this, &AwesomePlayer::onVideoLagUpdate);  
    mVideoEventPending =false;  
    mCheckAudioStatusEvent =newAwesomeEvent(  
            this, &AwesomePlayer::onCheckAudioStatus);  
    mAudioStatusEventPending =false;  
    reset();  
}  

從上述代碼中可見:

在awesomeplayer的構造函數中主要就是做些準備工作:如創立event對象,並提供fire函數,設置對應的各個狀態變量

還有一點需要注意的是 mClient.connect() 建立了awesomeplayer與omx解碼器之間的鏈接

②設置數據源URI

status_t AwesomePlayer::setDataSource(   
        const char *uri, const KeyedVector<String8, String8> *headers) {     
    mUri = uri;                // 這裏只是把URL保存起來而已, 真正的工作在Prepare之後進行  
    return OK;   
}  

開啓定時器隊列,並且 Post一個AsyncPrepareEvent事件

status_t AwesomePlayer::prepareAsync() {   
    
    mQueue.start();                                               // 開啓定時器隊列 
    
    mAsyncPrepareEvent = new AwesomeEvent(   
            this, &AwesomePlayer::onPrepareAsyncEvent);           // Post AsyncPrepare 事件 
 
    mQueue.postEvent(mAsyncPrepareEvent);   
    return OK;   
}   

Prepare之後,AwesomePlayer開始運作.

AsyncPrepare事件被觸發

當這個事件被觸發時, AwesomePlayer開始創建 VideoTrack和AudioTrack ,然後創建 VideoDecoder和AudioDecoder

void AwesomePlayer::onPrepareAsyncEvent() {   
   
    finishSetDataSource_l();            // a. 創建視頻源和音頻源
    
    initVideoDecoder();                 // b. 創建視頻解碼器
    
    initAudioDecoder();                 // c. 創建音頻解碼器
}   

至此,播放器準備工作完成,可以開始播放了

Post第一個VideoEvent(顯示一幀畫面並進行同步處理)

調用順序:AwesomePlayer::play()調用 -> AwesomePlayer::play_l()調用 ->AwesomePlayer::postVideoEvent_l(int64_t delayUs)

void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {   
    mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);   
}   

VideoEvent被觸發

void AwesomePlayer::onVideoEvent() {   
    
    mVideoSource->read(&mVideoBuffer, &options);                   // 從視頻解碼器中讀出視頻圖像
    
    if (mVideoRendererIsPreview || mVideoRenderer == NULL) {       // 創建AwesomeRenderer (如果沒有的話)
        initRenderer_l();   
    }   
    
     mVideoRenderer->render(mVideoBuffer);                         // 渲染視頻圖像
    
     postVideoEvent();                                             // 再次發送一個VideoEvent, 這樣播放器就不停的播放了  
}   

總結: SetDataSource -> Prepare -> Play-> postVieoEvent -> OnVideoEvent -> postVideoEvent-> ....onVideoEvent-> postStreamDoneEvent ->播放結束

5.2、視頻 /音頻的分離

就像前面基本播放流程分析描述的那樣,當AsyncPrepare事件被觸發時, AwesomePlayer會調用finishSetDataSource_l創建 VideoTrack 和 AudioTrack,finishSetDataSource_l通過URI前綴判斷 媒體類型,比如http, rtsp,或者本地文件等 這裏的uri就是一開始 通過setDataSource設置的 根據uri創建相應的DataSource,再進一步的利用 DataSource創建MediaExtractor做A/V分離

status_t AwesomePlayer::finishSetDataSource_l() {   
        sp<datasource> dataSource;   
        /// 通過URI前綴判斷媒體類型, 比如 http, rtsp,或者本地文件等   
        /// 這裏的uri就是一開始 通過setDataSource設置的   
        /// 根據uri 創建相應的MediaExtractor   
 
        if (!strncasecmp("http://", mUri.string(), 7)) {   
            mConnectingDataSource = new NuHTTPDataSource;   
            mConnectingDataSource->connect(mUri, &mUriHeaders);   
            mCachedSource = new NuCachedSource2(mConnectingDataSource);   
            dataSource = mCachedSource;   
        } else if (!strncasecmp("rtsp://", mUri.string(), 7)) {   
            mRTSPController->connect(mUri.string());   
            sp<mediaextractor> extractor = mRTSPController.get();   
 
            /// rtsp 比較特殊, MediaExtractor對象的創建不需要DataSource   
            return setDataSource_l(extractor);   
        } else {   
            /// 本地文件   
            dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);   
        }   
 
        /// 用dataSource創建一個MediaExtractor用於A/V分離        
        sp<mediaextractor> extractor = MediaExtractor::Create(dataSource);   
 
        return setDataSource_l(extractor);   
    }  
</mediaextractor></mediaextractor></datasource>

根據根據DataSource創建MediaExtractor

先來看AwesomePlayer的構造函數,裏面有一行代碼.

AwesomePlayer::AwesomePlayer(){   
    //...   
    DataSource::RegisterDefaultSniffers();   
    //...   
}

RegisterDefaultSniffers註冊了一些了媒體的MIME類型的探測函數.

void DataSource::RegisterDefaultSniffers() {   
    RegisterSniffer(SniffMPEG4);   
    RegisterSniffer(SniffMatroska);   
    RegisterSniffer(SniffOgg);   
    RegisterSniffer(SniffWAV);   
    RegisterSniffer(SniffAMR);   
    RegisterSniffer(SniffMPEG2TS);   
    RegisterSniffer(SniffMP3);   
}  

 

這些探測用於判斷媒體的MIME類型,進而決定要創建什麼樣的MediaExtractor.

再回到 MediaExtractor::Create, MediaExtractor對象在這裏創建.下面代碼有點長,其實這段代碼只是根據MIME類型創建Extractor的各個分支而已.

 sp<mediaextractor> MediaExtractor::Create(
            const sp<datasource> &source, const char *mime) {
        sp<amessage> meta;
 
        String8 tmp;
        if (mime == NULL) {
            float confidence;
            if (!source->sniff(&tmp, &confidence, &meta)) {
                LOGV("FAILED to autodetect media content.");
 
                return NULL;
            }
 
            mime = tmp.string();
            LOGV("Autodetected media content as '%s' with confidence %.2f",
                 mime, confidence);
        }
 
        bool isDrm = false;
        // DRM MIME type syntax is "drm+type+original" where
        // type is "es_based" or "container_based" and
        // original is the content's cleartext MIME type
        if (!strncmp(mime, "drm+", 4)) {
            const char *originalMime = strchr(mime+4, '+');
            if (originalMime == NULL) {
                // second + not found
                return NULL;
            }
            ++originalMime;
            if (!strncmp(mime, "drm+es_based+", 13)) {
                // DRMExtractor sets container metadata kKeyIsDRM to 1
                return new DRMExtractor(source, originalMime);
            } else if (!strncmp(mime, "drm+container_based+", 20)) {
                mime = originalMime;
                isDrm = true;
            } else {
                return NULL;
            }
        }
 
        MediaExtractor *ret = NULL;
        if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
                || !strcasecmp(mime, "audio/mp4")) {
            ret = new MPEG4Extractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
            ret = new MP3Extractor(source, meta);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
                || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
            ret = new AMRExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
            ret = new FLACExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
            ret = new WAVExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
            ret = new OggExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
            ret = new MatroskaExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
            ret = new MPEG2TSExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)) {
            ret = new AVIExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {
            ret = new WVMExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
            ret = new AACExtractor(source);
        }
 
        if (ret != NULL) {
           if (isDrm) {
               ret->setDrmFlag(true);
           } else {
               ret->setDrmFlag(false);
           }
        }
 
        return ret;
    }
</amessage></datasource></mediaextractor>

③根據根據MediaExtractor做A/V分離

再來看看setDataSource_l(const sp&extractor) ,這是A/V分離的最後步驟

status_t AwesomePlayer::setDataSource_l(const sp<mediaextractor> &extractor) {   
 
        /// 從全部的Track中選取一個Video Track和一個AudioTrack   
        for (size_t i = 0; i < extractor->countTracks(); ++i) {   
            sp<metadata> meta = extractor->getTrackMetaData(i);   
 
            const char *mime;   
            CHECK(meta->findCString(kKeyMIMEType, &mime));   
 
            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;   
            }   
 
            if (haveAudio && haveVideo) {   
                break;   
            }   
        }  
        /// Flags 標誌這個媒體的一些屬性:   
        /// CAN_SEEK_BACKWARD 是否能後退10秒   
        /// CAN_SEEK_FORWARD 是否能前進10秒   
        /// CAN_SEEK 能否Seek?   
        /// CAN_PAUSE 能否暫停   
        mExtractorFlags = extractor->flags();   
        return OK;   
    }

從這個函數可以看到MediaExtractor需要實現的基本比較重要的接口 (這個幾個接口都是純虛函數,可見Extractor的子類是一定要搞定它們的)

virtual size_t countTracks() = 0; 

該媒體包含了幾個Track?

virtual sp getTrack(size_t index) = 0;

獲取指定的Video/Audio Track,可以看到一個Track本質上就是一個MediaSource

virtual sp getTrackMetaData ( size_t index,uint32_t flags = 0) = 0;

獲取指定的Track的MetaData.在AwesomePlayer 中, MetaData實際上就是一塊可以任意信息字段的叉燒, 字段類型可以是字符串或者是整形等等.這裏Track的MetaData包含了Track的MIME類型.這樣AwesomePlayer就可以知道這個Track是Video還是Audio的了
總結:至此,AwesomePlayer 就擁有VideoTrack和AudioTrack了 (可能只有VideoTrack或者只有AudioTrack,例如MP3).接下來 音視頻解碼器VideoSource/AudioSource 將從Video/Audio Track 中讀取數據進行解碼.

④創建視頻解碼器

VideoTrack/AudioTrack創建完畢之後, 緊接着就是創建 VideoSource了 (見 1.2.3).看看initVideoDecoder

status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {   
    mVideoSource = OMXCodec::Create(   
            mClient.interface(), mVideoTrack->getFormat(),   
            false, // createEncoder   
            mVideoTrack,   
            NULL, flags);   
    /// ...   
    return mVideoSource != NULL ? OK : UNKNOWN_ERROR;   
}

VideoSource是由OMXCodec::Create 創建的.從OMXCodec::Create的參數可以看出創建一個視頻解碼器需要什麼材料:

a. OMXClient.用於跟OMXIL 通訊.假如最後用的是OMXCodec 也不是SoftCodec的話,需要用到它.

b. mVideoTrack->getFormat (). getFormat返回包含該video track格式信息的MetaData.

c. mVideoTrack,解碼器會從 Video Track 中讀取數據進行解碼.

⑤創建視頻解碼器的函數OMXCodec::Create

sp<mediasource> OMXCodec::Create(   
            const sp<iomx> &omx,   
            const sp<metadata> &meta, bool createEncoder,   
            const sp<mediasource> &source,   
            const char *matchComponentName,   
            uint32_t flags) {   
 
        /// 獲取MIME類型   
        const char *mime;   
        bool success = meta->findCString(kKeyMIMEType, &mime);   
 
        /// 根據MIME找出可能匹配的Codec   
        Vector<string8> matchingCodecs;   
        findMatchingCodecs(   
                mime, createEncoder, matchComponentName, flags, &matchingCodecs);   
 
        IOMX::node_id node = 0;   
 
        /// 對每一種可能匹配的Codec, 嘗試申請Codec   
        const char *componentName;   
        for (size_t i = 0; i < matchingCodecs.size(); ++i) {   
            componentName = matchingCodecs[i].string();   
 
            /// 嘗試申請軟Codec   
            sp<mediasource> softwareCodec = createEncoder?   
                InstantiateSoftwareEncoder(componentName, source, meta):   
                InstantiateSoftwareCodec(componentName, source);   
 
            if (softwareCodec != NULL) {   
                return softwareCodec;   
            }   
 
            /// 嘗試申請OMXCodec   
            status_t err = omx->allocateNode(componentName, observer, &node);   
            if (err == OK) {   
                sp<omxcodec> codec = new OMXCodec(   
                        omx, node, quirks,   
                        createEncoder, mime, componentName,   
                        source);   
 
                /// 配置申請出來的OMXCodec   
                err = codec->configureCodec(meta, flags);   
                if (err == OK) {   
                    return codec;   
                }   
            }   
        }   
 
        return NULL;   
    }  
</omxcodec></mediasource></string8></mediasource></metadata></iomx></mediasource>

Create中OMXCodec::findMatchingCodecs函數找出可能匹配的Codec

findMatchingCodecs根據傳入的MIME 從kDecoderInfo中找出MIME對於的Codec名 (一種MIME可能對應多種Codec)

void OMXCodec::findMatchingCodecs(   
            const char *mime,   
            bool createEncoder, const char *matchComponentName,   
            uint32_t flags,   
            Vector<string8> *matchingCodecs) {   
 
        for (int index = 0;; ++index) {   
            const char *componentName;   
 
        componentName = GetCodec(   
                        kDecoderInfo,   
                        sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),   
                        mime, index);   
 
 
            matchingCodecs->push(String8(componentName));   
        }   
    }  
</string8>

看看 kDecoderInfo裏面包含了什麼Codec吧,有點長.

static const CodecInfo kDecoderInfo[] = {   
    { MEDIA_MIMETYPE_IMAGE_JPEG, "OMX.TI.JPEG.decode" },   
//    { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.TI.MP3.decode" },   
    { MEDIA_MIMETYPE_AUDIO_MPEG, "MP3Decoder" },   
//    { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.PV.mp3dec" },   
//    { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.decode" },   
    { MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBDecoder" },   
//    { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.PV.amrdec" },   
    { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.decode" },   
    { MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBDecoder" },   
//    { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.PV.amrdec" },   
    { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.decode" },   
    { MEDIA_MIMETYPE_AUDIO_AAC, "AACDecoder" },   
//    { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.PV.aacdec" },   
    { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "G711Decoder" },   
    { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "G711Decoder" },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.decoder.mpeg4" },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.Decoder" },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.SEC.MPEG4.Decoder" },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TCC.mpeg4dec" },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Decoder" },   
//    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.PV.mpeg4dec" },   
    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.decoder.h263" },   
    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.decoder.h263" },   
    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.SEC.H263.Decoder" },   
    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.TCC.h263dec" },   
    { MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Decoder" },   
//    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.PV.h263dec" },   
    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.decoder.avc" },   
    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.decoder.avc" },   
    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.Decoder" },   
    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.SEC.AVC.Decoder" },   
    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TCC.avcdec" },   
    { MEDIA_MIMETYPE_VIDEO_AVC, "AVCDecoder" },   
//    { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.PV.avcdec" },   
    { MEDIA_MIMETYPE_AUDIO_VORBIS, "VorbisDecoder" },   
    { MEDIA_MIMETYPE_VIDEO_VPX, "VPXDecoder" },   
 
    // TELECHIPS, SSG   
    { MEDIA_MIMETYPE_AUDIO_MPEG_TCC, "OMX.TCC.mp3dec" },   
    { MEDIA_MIMETYPE_AUDIO_AAC_TCC, "OMX.TCC.aacdec" },   
    { MEDIA_MIMETYPE_AUDIO_VORBIS_TCC, "OMX.TCC.vorbisdec" },   
    { MEDIA_MIMETYPE_AUDIO_WMA, "OMX.TCC.wmadec" },   
    { MEDIA_MIMETYPE_AUDIO_AC3, "OMX.TCC.ac3dec" },   
    { MEDIA_MIMETYPE_AUDIO_RA, "OMX.TCC.radec" },   
    { MEDIA_MIMETYPE_AUDIO_FLAC, "OMX.TCC.flacdec" },   
    { MEDIA_MIMETYPE_AUDIO_APE, "OMX.TCC.apedec" },   
    { MEDIA_MIMETYPE_AUDIO_MP2, "OMX.TCC.mp2dec" },   
    { MEDIA_MIMETYPE_AUDIO_PCM, "OMX.TCC.pcmdec" },   
    { MEDIA_MIMETYPE_AUDIO_DTS, "OMX.TCC.dtsdec" },   
 
    { MEDIA_MIMETYPE_VIDEO_VC1, "OMX.TCC.wmvdec" },   
    { MEDIA_MIMETYPE_VIDEO_WMV12, "OMX.TCC.wmv12dec" },   
    { MEDIA_MIMETYPE_VIDEO_RV, "OMX.TCC.rvdec" },   
    { MEDIA_MIMETYPE_VIDEO_DIVX, "OMX.TCC.divxdec" },   
    { MEDIA_MIMETYPE_VIDEO_MPEG2, "OMX.TCC.mpeg2dec" },   
    { MEDIA_MIMETYPE_VIDEO_MJPEG, "OMX.TCC.mjpegdec" },   
    { MEDIA_MIMETYPE_VIDEO_FLV1, "OMX.TCC.flv1dec" },   
};  

可以看到MPEG4就對應了6種Codec.

InstantiateSoftwareCodec創建軟解碼器

InstantiateSoftwareCodec從 kFactoryInfo (軟解碼器列表)挑挑看有沒有. 有的話就創建一個軟解碼器.看看kFactoryInfo裏面有哪些軟解碼器

static const FactoryInfo kFactoryInfo[] = {   
    FACTORY_REF(MP3Decoder)   
    FACTORY_REF(AMRNBDecoder)   
    FACTORY_REF(AMRWBDecoder)   
    FACTORY_REF(AACDecoder)   
    FACTORY_REF(AVCDecoder)   
    FACTORY_REF(G711Decoder)   
    FACTORY_REF(M4vH263Decoder)   
    FACTORY_REF(VorbisDecoder)   
    FACTORY_REF(VPXDecoder)   
};  

解碼器名稱的一點說明

OMX.XXX.YYY  中間的XXX是廠商名稱.如OMX.TI.Video.Decoder 就是TI芯片的硬視頻解碼器. 而 OMX.TCC.avcdec則是TCC的AVC視頻解碼器. 沒有OMX開頭的,說明是軟解碼器.

以AVC爲例:

{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.decoder.avc" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.decoder.avc" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.Decoder" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.SEC.AVC.Decoder" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TCC.avcdec" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "AVCDecoder" },

可以看到軟解碼器被放到最後.這樣的話後面嘗試申請解碼器的時候便會優先申請硬Codec. 除非硬Codec不存在.

創建OMXCodec

申請OMXCodec比較簡單,調用IOMX::allocateNode 申請即可.編解碼器的名稱例如 OMX.TCC.avcdec 即是OMX組件(Component)的名稱

IOMX::node_id node = 0;
omx->allocateNode(componentName, observer, &node); 

這個時候就已經是和OMX IL 層進行通訊了,雖然是進程間通訊. 但是封裝成這個樣子,我們也看不出來了,和本地調用一樣.

sp<OMXCodec> codec = new OMXCodec(
                    omx, node, quirks,
                    createEncoder, mime, componentName,
                    source);
codec->configureCodec(meta, flags);              // codec 創建出來後, 要配置一下codec.

如果進去看看configureCodec的代碼,可以看到實際上是調用 IOMX::setParameter, 和IOMX::setConfig. 同樣,也是屬於IPC,因爲是和OMX IL 通訊.

總結:理想的情況下, 調用OMXCodec::Create應該返回一個OMXCodec 對象而不是軟解對象. Android默認的策略也是優先創建硬解碼器. 至此AwesomePlayer通過OMXCodec 進而跟OML IL打交道. 其中關鍵的對象爲IOMX和IOMX::node_id. node_id相當於一個OMX Component的句柄.音頻解碼器的創建過程跟視頻解碼器的創建過程幾乎一樣, 所以不分析了.

⑥解封裝, 解碼

看之前的我們知道,當VideoEvent 被觸發時, AwesomePlayer::onVideoEvent會被調用. onVideoEvent 會嘗試調用 mVideoSource.read讀取視頻圖像,然後將視頻圖像交給AwesomeRenderer進行渲染.

如果採用硬解碼的話 mVideoSource實際是就是一個OMXCodec 對象.

至此,一個AwesomePlayer播放的實現就完成了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章