目录
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(*);注销所有事件