目錄
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(*);登出所有事件