天天看點

1 TimedEventQueue

目錄

1、概述

2、一個典型的使用TimedEventQueue的例子

3、TimedEventQueue類介紹

1概述

    TimeEventQueue是android多媒體系統中實作異步操作的一種手段

    在Awesomeplayer中,依賴mVideoEvent、mStreamDoneEvent、mBufferingEvent、mCheckAudioStatusEvent等幾個事件驅動着整個播放流程

    可以認為TimeEventQueue 是一個排程系統,排程的對象是事件Event,一個TimeEventQueue 可以同時處理多個事件

    本篇隻分析TimeEventQueue 類結構及其用法

2、一個典型的使用TimedEventQueue的例子

一個簡單的使用TimedEventQueue 的序列如下

mEventRead = new dtEvent(this,&dtPlayer::onReadEvent);             
mQueue.start();             
mQueue.postEvent(mEventRead);             
mQueue.stop();
           

【說明】

首先建立一個事件

啟動TimedEventQueue

通過postEvent觸發事件,事件觸發後會執行mEventRead對應的fire函數

最後結束

我們首先建立一個事件類

structdtEvent : publicTimedEventQueue::Event {              
    dtEvent(dtPlayer *player,void(dtPlayer::*method)()):               
      mPlayer(player),              
      mMethod(method)               
    {              
    }              
  
protected:              
    virtual~dtEvent() {}              
  
    virtualvoid fire(TimedEventQueue *queue, int64_t/* now_us */) {              
        (mPlayer->*mMethod)();              
    }              
  
private:              
    dtPlayer *mPlayer;              
    void(dtPlayer::*mMethod)();              
  
    dtEvent(constdtEvent &);              
    dtEvent &operator=(constdtEvent &);              
};
           

這裡處理方法與awesomeplayer等類似,由于Event是抽象類,不能直接使用,是以需要建立一個繼承自Event的子類,并重載fire方法

這裡構造函數中傳入了dtPlayer::*method 參數,作為fire中執行的函數體。這樣就保證了對于每一個事件,通過傳入不同的mMethod方法,便可以做出不同的反應

類似Awesomeplayer,我們需要為TimedEventQueue建立一個載體類dtPlayer

class dtPlayer{           
public:           
  
    dtPlayer();           
  
    virtual ~dtPlayer(){};           
  
private:           
    TimedEventQueue mQueue;           
    bool mQueueStarted;           
  
    sp<TimedEventQueue::Event> mEventRead;             
    sp<TimedEventQueue::Event> mEventWrite;             
    sp<TimedEventQueue::Event> mEventSearch;             
  
    voidonReadEvent();           
    voidonWriteEvent();           
    voidonSearchEvent();           
public:           
    voidstart();           
    voidstop();           
    bool isplaying();           
    voidpostRandomEvent();           
private:           
    dtPlayer(constdtPlayer &);           
    dtPlayer &operator=(constdtPlayer &);           
};
           

 在載體類中,我們有

TimedEventQueue 對象mQueue,

三個Event對象,分别是mEventRead mEventWrite mEventSearch

三個方法,分别是傳入給上面三個事件的參數,是void onReadEvent(); void onWriteEvent(); void onSearchEvent();

狀态機:start stop 等

postRandomEvent 方法:封裝的觸發事件的方法

下面看下具體實作

dtPlayer::dtPlayer()          
{          
  mEventRead =new dtEvent(this,&dtPlayer::onReadEvent);            
  mEventWrite =new dtEvent(this,&dtPlayer::onWriteEvent);            
  mEventSearch =new dtEvent(this,&dtPlayer::onSearchEvent);           
  
  mQueueStarted=false;           
}
           

先看下構造函數,構造函數主要是生成三個事件,并将參數傳遞進去,這裡可以看到調用的是重載的event的構造函數

并設定mQueue狀态

void dtPlayer::onReadEvent()          
{          
  ALOGI("--read event occur-- read something \n"); }          
  
void dtPlayer::onWriteEvent()          
{          
  ALOGI("--write event occur-- write something \n"); }          
  
void dtPlayer::onSearchEvent()          
{          
  ALOGI("--search event occur-- search something \n");             
}
           

 對應的實作,隻加了列印,表示執行過了

void dtPlayer::start()        
{        
  if(mQueueStarted==false)        
    mQueue.start();        
  mQueueStarted=true;        
  printf("dtplayer start  \n");        
}        
  
void dtPlayer::stop()        
{        
  if(mQueueStarted==true)        
    mQueue.stop(true);        
  mQueueStarted=false;        
  
}        
  
bool dtPlayer::isplaying()        
{        
  return(mQueueStarted==true);        
}
           

狀态機代碼,主要是控制mQueue的狀态

void dtPlayer::postRandomEvent()        
{        
  mQueue.postEvent(mEventRead);        
}
           

事件觸發函數,這裡可以看到觸發方法便是調用mQueue的postEvent方法,并且将事件對象作為參數傳入。這樣便可以執行對應事件的fire方法了

extern "C"int dt_test()        
{        
    ALOGE("--post event start \n");        
    dtPlayer *player=newdtPlayer();        
    player->start();        
    player->postRandomEvent();        
    player->stop();        
    ALOGE("--post event end\n");        
    sleep(5);        
    return0;        
}
           

這裡實作了測試code,隻是簡單的觸發一個事件然後傳回

【說明】具體的例子源代碼已經打包放在了附件中

測試方法:

(1)将壓縮包拷貝至framework/av/media/libstagefright目錄下

(2)解壓縮并進入_timeeventqueue_test檔案夾

(3)mm

(4)将可執行檔案拷貝到目标闆執行即可

通過上面的例子可以看到,TimedEventQueue 的使用時比較簡單的,隻要建立一個Event子類,并重載fire方法

之後通過TimedEventQueue 的postEvent等就可以出發執行了

而且這裡一個TimedEventQueue 可以支援多個事件

下面看下TimedEventQueue 具體是如何實作這一機制的

3、TimedEventQueue類介紹

2.1 event事件

TimedEventQueue排程的對象是事件

先看下事件的定義,事件是定義在TimedEventQueue 結構體内部的結構體

typedef int32_t event_id;
    struct Event : public RefBase {
        Event()
            : mEventID(0) {
        }
        virtual ~Event() {}
        event_id eventID() {
            return mEventID;
        }
    protected:
        virtual void fire(TimedEventQueue *queue, int64_t now_us) = 0;
    private:
        friend class TimedEventQueue;
        event_id mEventID;
        void setEventID(event_id id) {
            mEventID = id;
        }
        Event(const Event &);
        Event &operator=(const Event &);
    };
           

說明:

mEventID 事件ID

fire 純虛函數,事件觸發時排程執行的函數

是以Event不能直接使用,需繼承并重載fire方法後使用

 2.2 TimedEventQueue 一些重要方法解析

挑一下比較重要的解釋下

void TimedEventQueue::start() {        
    if(mRunning) {        
        return;        
    }        
    mStopped =false;        
    pthread_attr_t attr;        
    pthread_attr_init(&attr);        
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);        
    pthread_create(&mThread, &attr, ThreadWrapper,this);        
    pthread_attr_destroy(&attr);        
    mRunning =true;        
}
           

這裡調用mQueue->start 後主要是啟動一個線程,看下線程代碼

// static        
void *TimedEventQueue::ThreadWrapper(void *me) {        
  
    androidSetThreadPriority(0, ANDROID_PRIORITY_FOREGROUND);        
  
    static_cast<TimedEventQueue *>(me)->threadEntry();        
  
    returnNULL;        
}
           

 這裡線程實作方法的主要作用是調用mQueue->threadEntry 方法,因為上面傳入的參數是this指針

代碼比較多我們分段來看

void TimedEventQueue::threadEntry() {       
    prctl(PR_SET_NAME, (unsigned long)"TimedEventQueue",0, 0,0);       
  
    for(;;) {       
        int64_t now_us =0;       
        sp<Event> event;       
  
        {       
            Mutex::Autolock autoLock(mLock);       
  
            if(mStopped) {       
                break;       
            }       
  
            while(mQueue.empty()) {       
                mQueueNotEmptyCondition.wait(mLock);       
            }
           

首先判斷mStopped,确定是否要退出

然後判斷mQueue中是否為空,若未空表示沒有要處理的事件,則等待

event_id eventID = 0;      
            for(;;) {      
                if(mQueue.empty()) {      
                    // The only event in the queue could have been cancelled      
                    // while we were waiting for its scheduled time.      
                    break;      
                }      
  
                List<QueueItem>::iterator it = mQueue.begin();      
                eventID = (*it).event->eventID();      
  
                now_us = ALooper::GetNowUs();      
                int64_t when_us = (*it).realtime_us;      
  
                int64_t delay_us;      
                if(when_us < 0 || when_us == INT64_MAX) {      
                    delay_us = 0;      
                }else {      
                    delay_us = when_us - now_us;      
                }      
  
                if(delay_us <= 0) {      
                    break;      
                }      
  
                staticint64_t kMaxTimeoutUs = 10000000ll;  // 10 secs      
                booltimeoutCapped = false;      
                if(delay_us > kMaxTimeoutUs) {      
                    ALOGW("delay_us exceeds max timeout: %lld us", delay_us);      
  
                    // We'll never block for more than 10 secs, instead      
                    // we will split up the full timeout into chunks of      
                    // 10 secs at a time. This will also avoid overflow      
                    // when converting from us to ns.      
                    delay_us = kMaxTimeoutUs;      
                    timeoutCapped =true;      
                }      
  
                status_t err = mQueueHeadChangedCondition.waitRelative(      
                        mLock, delay_us * 1000ll);      
  
                if(!timeoutCapped && err == -ETIMEDOUT) {      
                    // We finally hit the time this event is supposed to      
                    // trigger.      
                    now_us = ALooper::GetNowUs();      
                    break;      
                }      
            }
           

若mQueue事件清單非空,則有要處理的事件,進入for loop進行處理,主要工作有

從mQueue的event清單中擷取一個event

判斷是否執行,如果時間到了(delay_us <= 0),則跳出循環執行事件處理函數,否則若需等待時間低于10s,則mQueueHeadChangedCondition.waitRelative等待。若高于10s,等10s後重新輪詢

當條件滿足時,通過event = removeEventFromQueue_l(eventID);擷取事件對象,并調用event->fire(this, now_us);

這裡有幾個問題

(1)對mQueue,每個event都有一個唯一eventID,mQueue通過eventID來擷取事件對象,eventID如何計算的

此問題的答案在mQueue->postEvent中。看下代碼

TimedEventQueue::event_id TimedEventQueue::postEvent(constsp<Event> &event) {   
    // Reserve an earlier timeslot an INT64_MIN to be able to post   
    // the StopEvent to the absolute head of the queue.   
    returnpostTimedEvent(event, INT64_MIN + 1);   
}
           
TimedEventQueue::event_id TimedEventQueue::postTimedEvent(   
        constsp<Event> &event, int64_t realtime_us) {   
    Mutex::Autolock autoLock(mLock);   
  
    event->setEventID(mNextEventID++);   
  
    List<QueueItem>::iterator it = mQueue.begin();   
    while(it != mQueue.end() && realtime_us >= (*it).realtime_us) {   
        ++it;   
    }   
  
    QueueItem item;   
    item.event = event;   
    item.realtime_us = realtime_us;   
  
    if(it == mQueue.begin()) {   
        mQueueHeadChangedCondition.signal();   
    }   
  
    mQueue.insert(it, item);   
  
    mQueueNotEmptyCondition.signal();   
  
    returnevent->eventID();   
}
           

 從代碼可以看出,postEvent會調用postTimedEvent,而在postTimedEvent中第一步操作就是設定eventID,這也就保證了每個event都有唯一一個eventID辨別

另外,會将事件通過mQueue.insert(it, item); 插入到TimedEventQueue->mQueue中,如下定義

struct QueueItem {   
    sp<Event> event;   
    int64_t realtime_us;   
};
           

mQueue是一個QueueItem清單,存儲了事件對象及要觸生的時間

(2)每次mQueue都從清單中取出第一個事件進行處理,但有個問題,若此事件阻塞的時候,其他事件需要觸發,怎麼處理?

玄機同樣在postTimedEvent中,在插入清單之前,會首先查找要插入到TimedEventQueue->mQueue 中的位置,按照觸發時間排序

2.3 其他方法介紹

TimedEventQueue 中還有一些接口擴充使得其功能更加強大,主要有

event_id postEventToBack(const sp<Event> &event); 插入到事件清單末尾

TimedEventQueue::event_id TimedEventQueue::postEventWithDelay(const sp<Event> &event, int64_t delay_us) ; 注冊事件同時設定delay時間

bool TimedEventQueue::cancelEvent(event_id id);登出某個事件

void TimedEventQueue::cancelEvents(*);登出所有事件