播放一个视频开始时要调用prepare , 在prepare 里调用 TimedEventQueue::start() ,即mQueue.start() ,会启动一个线程,然后在TimedEventQueue::threadEntry() 会收事件,处理事件。
TimedEventQueue 是一个按照事件约定时间来执行事件携带动作的类。事件的约定时间存在 QueueItem::realtime_us 里,往TimedEventQueue发事件使用 TimedEventQueue::postTimedEvent ,该方法除this以外的第一个参数是事件,第二个参数是该事件约定的执行时间,它是按顺序往列表里填事件的,请看这段代码:
TimedEventQueue::event_id TimedEventQueue::postTimedEvent() {
....
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);
....
}
在TimedEventQueue::threadEntry()按顺序查事件:
List<QueueItem>::iterator it = mQueue.begin();
eventID = (*it).event->eventID();
按该事件约定时间延时后再取事件:event = removeEventFromQueue_l(eventID);
再执行事件携带的动作:event->fire(this, now_us);
事件携带的动作(Event::fire) 是在AwesomeEvent类里实现的
struct AwesomeEvent : public TimedEventQueue::Event {
AwesomeEvent(
AwesomePlayer *player,
void (AwesomePlayer::*method)())
: mPlayer(player),
mMethod(method) {
}
protected:
....
virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
(mPlayer->*mMethod)();
}
private:
AwesomePlayer *mPlayer;
void (AwesomePlayer::*mMethod)();
...
};
看下面两行代码:
virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
(mPlayer->*mMethod)();
}
mPlayer和mMethod 都是AwesomeEvent 的成员变量,(*mMethod)() 表示调用该指针指向的函数, mPlayer-> 表示把mPlayer 指针作为(*mMethod)()的第一个参数:this。
其中有关视频解压和显示的事件是 AwesomePlayer::mVideoEvent。
我们看看该事件是如何创建的:
AwesomePlayer::AwesomePlayer(){
.....
mVideoEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoEvent);
}
可以看出 mVideoEvent 事件创建之后的 mPlayer是AwesomePlayer*, 而动作是AwesomePlayer::onVideoEvent。
我把AwesomePlayer::onVideoEvent()的代码简化和适当注释了一下放在了最后一段。开始通过调用mVideoSource->read(&mVideoBuffer, &options);来解压, 然后mVideoRenderer->render(mVideoBuffer);显示。中间有超时判断。不管哪个分支都有postVideoEvent_l()的调用,也就是说是每10ms就要触发一次解压和显示事件,然后根据视频帧的时戳决定是否延时。
在AwesomePlayer::onVideoEvent 里调用的 postVideoEvent_l(),原型是AwesomePlayer::postVideoEvent_l(int64_t delayUs=-1),声明缺省参数值是-1,所以在AwesomePlayer::onVideoEvent()不带参数调用的话,是以当时后延10ms的时间作为事件约定时间是来调用TimedEventQueue::postTimedEvent方法的。
通过 TimeSource *ts = ((mFlags & AUDIO_AT_EOS) || !(mFlags & AUDIOPLAYER_STARTED)) ? &mSystemTimeSource : mTimeSource;
这一句可以看出视频是用音频的时间作为同步参考时钟的。
void AwesomePlayer::onVideoEvent() {
Mutex::Autolock autoLock(mLock);
if (mSeeking != NO_SEEK) {
if (mVideoBuffer) {
mVideoBuffer->release();
mVideoBuffer = NULL;
}
}
for (;;) {
/// 这里做解压的动作,mVideoSource是一个基于OpenMax的decoder,在里面会通过连接的 DataSource 去读取原始的压缩数据,解压出可以显示的点阵数据。
status_t err = mVideoSource->read(&mVideoBuffer, &options); // mVideoSource is a video decoder
options.clearSeekTo();
break;
}
int64_t timeUs;
CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
mLastVideoTimeUs = timeUs;
TimeSource *ts =
((mFlags & AUDIO_AT_EOS) || !(mFlags & AUDIOPLAYER_STARTED))
? &mSystemTimeSource : mTimeSource; // mTimeSource = mAudioPlayer; so, timeSource is from audio.
if (mFlags & FIRST_FRAME) {
modifyFlags(FIRST_FRAME, CLEAR);
mSinceLastDropped = 0;
mTimeSourceDeltaUs = ts->getRealTimeUs() - timeUs;
}
int64_t realTimeUs, mediaTimeUs;
if (!(mFlags & AUDIO_AT_EOS) && mAudioPlayer != NULL
&& mAudioPlayer->getMediaTimeMapping(&realTimeUs, &mediaTimeUs)) {
mTimeSourceDeltaUs = realTimeUs - mediaTimeUs;
}
if (wasSeeking == NO_SEEK) { //忽略这一帧的显示
// Let's display the first frame after seeking right away.
int64_t nowUs = ts->getRealTimeUs() - mTimeSourceDeltaUs;
int64_t latenessUs = nowUs - timeUs;
if (latenessUs > 500000ll ) {
mVideoBuffer->release(); // delete this mediabuffer
mVideoBuffer = NULL;
mSeeking = SEEK_VIDEO_ONLY;
mSeekTimeUs = mediaTimeUs;
///给自己发一个10ms之后执行的事件。
postVideoEvent_l();
return;
}
if (latenessUs > 40000) { //忽略这一帧的显示
// We're more than 40ms late.
mVideoBuffer->release(); // delete this mediabuffer
mVideoBuffer = NULL;
++mStats.mNumVideoFramesDropped;
///给自己发一个10ms之后执行的事件。
postVideoEvent_l();
return;
}
if (latenessUs < -10000) {
// We're more than 10ms early.
///给自己发一个10ms之后执行的事件。
postVideoEvent_l(10000); // to display after a while
return;
}
}
if ((mNativeWindow != NULL)
&& (mVideoRendererIsPreview || mVideoRenderer == NULL)) {
mVideoRendererIsPreview = false;
initRenderer_l();
}
if (mVideoRenderer != NULL) {
mSinceLastDropped++;
//// 通过渲染库把点阵数据显示出来
mVideoRenderer->render(mVideoBuffer); // display it
}
mVideoBuffer->release();
mVideoBuffer = NULL;
if (wasSeeking != NO_SEEK && (mFlags & SEEK_PREVIEW)) {
modifyFlags(SEEK_PREVIEW, CLEAR);
return;
}
///给自己发一个10ms之后执行的事件。
postVideoEvent_l();
}
#environment
SHELL=/bin/bash
MAKE=make
#compiler
CC=gcc
AR=ar
CR=cr
#directory
MAKE_DIR=$(PWD)
SRC_DIR=$(MAKE_DIR)/src/
OBJ_DIR=$(MAKE_DIR)/obj/
LIB_DIR=$(MAKE_DIR)/lib/
INCLUDE_DIR=$(MAKE_DIR)/includes
DEBUG_DIR=$(MAKE_DIR)/debug/
RELEASE_DIR=$(MAKE_DIR)/release/
#vpath
vpath %.cpp $(SRC_DIR)
vpath %.o $(OBJ_DIR)
vpath %.d $(OBJ_DIR)
#VPATH = $(SRC_DIR) $(OBJ_DIR)
#include
INCLUDE=-I$(INCLUDE_DIR)
#src file
SRC_FILES:=$(wildcard $(SRC_DIR)*.cpp)
#obj file
OBJ_FILES:=$(notdir $(patsubst %.cpp, %.o, $(SRC_FILES)))
#depend file, which generated by g++ -MMD xx.cpp $(INCLUDE)
DEPEND_FILES:=$(notdir $(patsubst %.cpp, %.d, $(SRC_FILES)))
#lib, which used for link.
LIBS:=
LIB=-L$(LIB_DIR) $(LIBS)
#LOCAL_MODULE_ELF, which means the final executable file for this module
LOCAL_MODULE_ELF:=calc
LOCAL_MODULE_STATIC_LIB:=libcalc.a
LOCAL_MODULE_DYNAMIC_LIB:=libcalc.so
#flag
FLAG_DEBUG=-g
FLAG_COMPLE=-c
FLAG_LINK=
#debug
DEBUG=1
ifeq ($(DEBUG),1)
OUTPUT_DIR:=$(DEBUG_DIR)
FLAG_COMPLE:=$(FLAG_DEBUG) $(FLAG_COMPLE)
FLAG_LINK:=
else
OUTPUT_DIR:=$(RELEASE_DIR)
FLAG_COMPLE:=$(FLAG_COMPLE)
FLAG_LINK:=
endif
#output
OUTPUT_ELF=$(OUTPUT_DIR)$(LOCAL_MODULE_ELF)
OUTPUT_STATIC_LIB=$(OUTPUT_DIR)$(LOCAL_MODULE_STATIC_LIB)
OUTPUT_DYNAMIC_LIB=$(OUTPUT_DIR)$(LOCAL_MODULE_DYNAMIC_LIB)
.PHONY: all help dir elf staticlib dynamiclib obj move clean cleanall
all:dir obj
#放在任何命令之前
include $(DEPEND_FILES)
help:
@echo "A simple makefile!"
dir:
mkdir -p $(OBJ_DIR)
mkdir -p $(LIB_DIR)
mkdir -p $(DEBUG_DIR)
mkdir -p $(RELEASE_DIR)
obj:$(OBJ_FILES)
move:
mv -f *.o $(OBJ_DIR)
mv -f *.d $(OBJ_DIR)
%.d:$(SRC_DIR)%.cpp
@echo "~~~~~~~~~~~~~~~~~~"
$(CC) -MM -MD $^ $(INCLUDE)
%.o:
@echo "------------------"
$(CC) -fPIC -c $< -o $@ $(INCLUDE)
elf:$(OBJ_FILES)
@echo "building elf..."
@echo "OBJ_FILES: $(OBJ_FILES)"
@echo "LIB: $(LIB)"
@echo "OUTPUT_ELF: $(OUTPUT_ELF)"
$(CC) $(FLAG_LINK) -o $(OUTPUT_ELF) $(OBJ_FILES) $(LIB)
staticlib:$(OBJ_FILES)
@echo "building static lib..."
@echo "OUTPUT_LIB: $(OUTPUT_STATIC_LIB)"
$(AR) $(CR) $(OUTPUT_STATIC_LIB) $(OBJ_FILES)
dynamiclib:$(OBJ_FILES)
@echo "building dynamic lib..."
@echo "OUTPUT_LIB: $(OUTPUT_DYNAMIC_LIB)"
$(CC) -shared -o $(OUTPUT_DYNAMIC_LIB) $(OBJ_FILES)
clean:
@rm -f $(OBJ_DIR)* *.d *.o
@rm -f $(OUT)
@clear
cleanall:
@rm -f $(OBJ_DIR)*
@rm -f $(RELEASE_DIR)*
@rm -f $(DEBUG_DIR)*
方法一,hook已有公开头文件的类:
首先写一个Utility函数:
#import <objc/runtime.h> inline void exchangeMethod(Class aClass, SEL oldSEL, SEL newSEL) { Method oldMethod = class_getInstanceMethod(aClass, oldSEL); assert(oldMethod); Method newMethod = class_getInstanceMethod(aClass, newSEL); assert(newMethod); method_exchangeImplementations(oldMethod, newMethod); }
现在,目标是hook UIWebView没公开的函数
- (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
因为已知类的声明,所以可以使用category:
@interface UIWebView (Hook) + (void)hook; - (void)hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2; @end @implementation UIWebView (Hook) + (void)hook { // hook UIWebView中表示一个HTML的frame加载完毕的函数 exchangeMethod([UIWebViewclass], @selector(webView:didFinishLoadForFrame:), @selector(hook_webView:didFinishLoadForFrame:)); } - (void)hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2 { // 因为交换了selector和implementation的映射,原样调一下本函数实际会调用被hook的函数。 [self hook_webView:arg1 didFinishLoadForFrame:arg2]; NSLog(@"webView:didFinishLoadForFrame:"); }在程序启动的时候调用一下 [UIWebView hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。
方法二,hook没有公开头文件的类,需要另建一个类作为新函数载体,然后先为被hook的类增加函数,再替换:
UIWebView体系中有一个类叫UIWebBrowserView,它是真正显示网页的UIView,并有部分函数是作为WebCore向外发送回调信息的接收者。
现我们去hook UIWebBrowserView的这个函数:
- (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2;嗯,是的,这个函数和UIWebView的一样,实际上就是UIWebBrowserView会再调用通知到UIWebView的同名函数。
创建一个类,不要与被hook的类同名,例如加了个Hook后缀:
@interface UIWebBrowserViewHook : NSObject + (void)hook; - (void)hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2; @end其中以hook_为前缀的新增函数的实现与方法一中相同,差别在类函数中:
@implementation UIWebBrowserViewHook + (void)hook { Class aClass = objc_getClass("UIWebBrowserView"); SEL sel = @selector(hook_webView:didFinishLoadForFrame:); // 为UIWebBrowserView增加函数 class_addMethod(aClass, sel, class_getMethodImplementation([self class], sel), "v@:@@"); // 交换实现 exchangeMethod(aClass, @selector(webView:didFinishLoadForFrame:), sel); }在程序启动的时候调用一下 [UIWebBrowserViewHook hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。
方法三,hook没有公开头文件的类,另建一个类作为新函数载体,用新函数替换旧函数,并把旧函数保存到静态变量里:
继续以UIWebBrowserView为例子。注意新函数可以与被hook的函数同名
@interface UIWebBrowserViewHook : NSObject + (void)hook; - (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2; @end需要用到另一个Utility函数:
inline void replaceImplementation(Class newClass, Class hookedClass, SEL sel, IMP& oldImp) { Method old = class_getInstanceMethod(hookedClass, sel); IMP newImp = class_getMethodImplementation(newClass, sel); oldImp = method_setImplementation(old, newImp); }
当两个selector不同名时,以上函数再增加一个参数即可。
下面是实现:
@implementation UIWebBrowserViewHook static IMP webView_didFinishLoadForFrame = NULL; + (void)hook { Class hookedClass = objc_getClass("UIWebBrowserView"); SEL sel = @selector(webView:didFinishLoadForFrame:); replaceImplementation([self class], hookedClass, sel, webView_didFinishLoadForFrame); } - (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2 { // 需要这样来调用被替换掉的原实现 webView_didFinishLoadForFrame(self, @selector(webView:didFinishLoadForFrame:), arg1, arg2); NSLog(@"webView:didFinishLoadForFrame:"); } @end在程序启动的时候调用一下 [UIWebBrowserViewHook hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。
三种方法的比较:
最方便的当然是第一种,但需要是hook有公开头文件的类。
方法二和方法三都新建了一个类,方法二需要描述selector的types,这个比较麻烦,如例子中的"v@:@@"表示返回值为void,第一和第二个参数都是id。方法三不用types,但要增加全局变量。
Objective-C的runtime参考资料:
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048