此处是接着上一篇文章, c++ 以 Void * 释放对象会怎样? 最后留下问题的最终解决方案声明: 源码来自 Google NKD!
Looper 目录结构位置
如果直接拿 Google 源码来是用, 请注意, 里面有 bug,head 没有初始化为 NULL, 是个野指针!!!!
Google 源码我就不贴出来了, 有兴趣直接查看 NDK 直接贴我改造后的代码
消息对象基类(post 的消息必须继承该基类):
- //post 消息对象基类
- class LoopMsgObj
- {
- public:
- LoopMsgObj(){}
- ~LoopMsgObj(){}
- };
是的, LoopMsgObj 什么都没做
Looper 类声明:
- class looper {
- public:
- looper();
- virtual ~looper();
- //flush 是否清空消息队列
- void post(int what, LoopMsgObj *data, bool flush = false);
- void quit();
- virtual void handle(int what, LoopMsgObj *data);
- private:
- virtual void addmsg(loopermessage *msg, bool flush);
- static void* trampoline(void* p);
- void loop();
- protected:
- std::deque< loopermessage * > _msgQueue;
- pthread_t worker;
- sem_t headwriteprotect;
- sem_t headdataavailable;
- bool running;
- };
Looper 实现:
- void* looper::trampoline(void* p) {
- LOGV("at looper trampoline");
- ((looper*)p)->loop();
- return NULL;
- }
- looper::looper() {
- LOGV("at looper create");
- // head = NULL;
- sem_init(&headdataavailable, 0, 0);
- sem_init(&headwriteprotect, 0, 1);
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_create(&worker, &attr, trampoline, this);
- running = true;
- }
- looper::~looper() {
- if (running) {
- LOGV("Looper deleted while still running. Some messages will not be processed");
- quit();
- }
- }
- //
- void looper::post(int what, LoopMsgObj *data, bool flush) {
- loopermessage *msg = new loopermessage();
- msg->what = what;
- msg->obj = data;
- msg->quit = false;
- addmsg(msg, flush);
- }
- void looper::addmsg(loopermessage *msg, bool flush) {
- sem_wait(&headwriteprotect);
- if (flush) {
- _msgQueue.clear();
- }
- _msgQueue.push_back(msg);
- LOGV("post msg %d", msg->what);
- sem_post(&headwriteprotect);
- sem_post(&headdataavailable);
- }
- void looper::loop() {
- LOGV("at loop");
- while(true)
- {
- // wait for available message
- sem_wait(&headdataavailable);
- LOGV("headdataavailable");
- // get next available message
- sem_wait(&headwriteprotect);
- LOGV("headwriteprotect");
- if(_msgQueue.size() > 0)
- {
- loopermessage *msg = _msgQueue.front();
- _msgQueue.pop_front();
- //quit 退出
- if (msg->quit)
- {
- delete msg->obj;
- delete msg;
- while(_msgQueue.size() > 0)
- {
- msg = _msgQueue.front();
- _msgQueue.pop_front();
- delete msg->obj;
- delete msg;
- }
- sem_post(&headwriteprotect);
- return;
- }
- sem_post(&headwriteprotect);
- LOGV("processing msg %d", msg->what);
- handle(msg->what, msg->obj);
- delete msg;
- }
- else
- {
- LOGV("no msg");
- sem_post(&headwriteprotect);
- continue;
- }
- }
- }
- void looper::quit() {
- LOGV("quit");
- loopermessage *msg = new loopermessage();
- msg->what = 0;
- msg->obj = NULL;
- msg->quit = true;
- addmsg(msg, true);
- void *retval;
- pthread_join(worker, &retval);
- sem_destroy(&headdataavailable);
- sem_destroy(&headwriteprotect);
- running = false;
- }
- void looper::handle(int what, LoopMsgObj* obj) {
- LOGV("dropping msg %d %p", what, obj);
- }
这里主要用 sem_post 和 sem_wait 信号量机制来保证 loop 线程在没有消息的时候处于睡眠状态, 不会空转, 关于信号量详细介绍可以看这里
里面用 STL 的 deque 容器来装消息, 当主动 post 一个 LoopMsgObj, 就 push_back 到_msgQueue 容器中 loop 不停的从_msgQueue 容器中 pop_front 一个消息, 丢给 handle 去处理改造后 Looper 源码地址
上面的 Looper 能满足大多数情况, 但是在某些情况 handle 消息速度跟不上 post 消息的时候就需要动态丢数据以满足需要
固定消息长度的 FixedLoop 类, 继承 Looper, 实现代码:
- namespace WeiYu
- {
- FixedLoop::FixedLoop(int MaxMsgLen):_MaxMsgLen(MaxMsgLen),looper()
- {
- }
- void FixedLoop::addmsg(loopermessage *msg, bool flush)
- {
- sem_wait(&headwriteprotect);
- if (flush) {
- _msgQueue.clear();
- }
- if(_msgQueue.size() >= _MaxMsgLen) // 移除一个消息
- {
- loopermessage *tempMsg = _msgQueue.front();
- _msgQueue.pop_front();
- delete tempMsg->obj;
- delete tempMsg;
- }
- _msgQueue.push_back(msg);
- sem_post(&headwriteprotect);
- sem_post(&headdataavailable);
- }
- }
代码很简单, 在 addmsg 的时候判断容器_msgQueue 大小是否达到_MaxMsgLen, 如果达到_MaxMsgLen, 就主动丢掉_msgQueue 的 front 消息, 然后再把新消息 push_back 进容器
我拿 FixedLoop 这个类做视频编码类, 摄像头采集图像数据 post 进去, 然后在 handle 里面编码 h264 我在 handle 里面尝试做 cpu 美颜, 编码速度在低端手机大概一秒 8 帧左右, 摄像头采集一秒 15 帧, 严重跟不上采集速度, 动态丢帧效果非常明显使用 FixedLoop 安卓视频编码 h264 源码地址
本来我也用 FixedLoop 来把编码后的视频信息推流到 RTMP 服务器, 无奈在 4G 网络不好的情况下, 播放推流的视频经常出现马赛克研究了一段时间原因, 才发现是关键帧被丢掉了, 关键帧后面的 P 帧却没有丢掉导致的无奈, 只要再封装一个 NaluLoop 类, 实现代码:
- namespace WeiYu
- {
- NaluLoop::NaluLoop(int QueueNaluLen):_MaxNalu(QueueNaluLen),looper()
- {}
- // 队列里面既有音频, 也有视频
- void NaluLoop::addmsg(loopermessage *msg, bool flush)
- {
- sem_wait(&headwriteprotect);
- if (flush) {
- _msgQueue.clear();
- }
- if(_msgQueue.size() >= _MaxNalu) // 移除消息, 直到下一个 I 帧, 或者队列为空
- {
- loopermessage *tempMsg = _msgQueue.front();
- _msgQueue.pop_front();
- delete (NaluStruct*)tempMsg->obj;
- delete tempMsg;
- while(_msgQueue.size() > 0)
- {
- tempMsg = _msgQueue.front();
- if(((NaluStruct*)tempMsg->obj)->type == 5)
- {
- break;
- }
- _msgQueue.pop_front();
- delete tempMsg->obj;
- delete tempMsg;
- }
- }
- _msgQueue.push_back(msg);
- sem_post(&headwriteprotect);
- sem_post(&headdataavailable);
- }
- }
代码也非常简单, 在 addmsg 时打到限制条件, 就把_msgQueue 中第一个关键帧前边所有的视频 (P 帧) 和音频数据都丢弃是用 NaluLoop 封装的 RTMP 推流器类源码
用手机连接电脑 wifi, 然后限制网速, 推流速度更不上编码速度, 测试效果还不错
来源: http://www.jianshu.com/p/efa15f77fbb5