本文已获得作者授权,本文作者:cain_huang原文链接:https://www.jianshu.com/p/9003caa6683f
在开始介绍播放器开发之前,我们首先对posix库进行一定的封装,得到我们想要的 Mutex、 Condition、Thread等类。
至于为何不用 C++11自带的相关类呢?
这是考虑到编译环境的问题,有些公司可能仍旧没升级 NDK 的版本,不支持C++11,这里为了方便,只好利用 Posix 封装一套 Thread 相关的基础类,部分代码参考(copy)自Android 源码中的代码。
至于原理,这里就不介绍了,网上相关资料还是很多的,分析互斥锁、条件锁等原理不是本文章的重点。
Mutex封装
Mutex 的封装可参考 Android 的 libutil 库里面的代码,直接复制过来使用即可,代码里面还封装了 AutoLock。代码如下:
1#ifndef MUTEX_H 2#define MUTEX_H 3#include <stdint.h> 4#include <sys/types.h> 5#include <time.h> 6 7#include <pthread.h> 8 9typedef int32_t status_t;1011class Condition;1213class Mutex {14public:15 enum {16 PRIVATE = 0,17 SHARED = 118 };19 Mutex();20 Mutex(const char* name);21 Mutex(int type, const char* name = NULL);22 ~Mutex();2324 // lock or unlock the mutex25 status_t lock();26 void unlock();2728 // lock if possible; returns 0 on success, error otherwise29 status_t tryLock();3031 // Manages the mutex automatically. It'll be locked when Autolock is32 // constructed and released when Autolock goes out of scope.33 class Autolock {34 public:35 inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }36 inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }37 inline ~Autolock() { mLock.unlock(); }38 private:39 Mutex& mLock;40 };4142private:43 friend class Condition;4445 // A mutex cannot be copied46 Mutex(const Mutex&);47 Mutex& operator = (const Mutex&);4849 pthread_mutex_t mMutex;50};5152inline Mutex::Mutex() {53 pthread_mutex_init(&mMutex, NULL);54}5556inline Mutex::Mutex(const char* name) {57 pthread_mutex_init(&mMutex, NULL);58}5960inline Mutex::Mutex(int type, const char* name) {61 if (type == SHARED) {62 pthread_mutexattr_t attr;63 pthread_mutexattr_init(&attr);64 pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);65 pthread_mutex_init(&mMutex, &attr);66 pthread_mutexattr_destroy(&attr);67 } else {68 pthread_mutex_init(&mMutex, NULL);69 }70}7172inline Mutex::~Mutex() {73 pthread_mutex_destroy(&mMutex);74}7576inline status_t Mutex::lock() {77 return -pthread_mutex_lock(&mMutex);78}7980inline void Mutex::unlock() {81 pthread_mutex_unlock(&mMutex);82}8384inline status_t Mutex::tryLock() {85 return -pthread_mutex_trylock(&mMutex);86}87typedef Mutex::Autolock AutoMutex;8889#endif //MUTEX_H
Condition封装
Condition类的封装跟 Mutex一样,直接从 Android 源码里面复制过来,稍作修改即可。
代码如下:
1#ifndef CONDITION_H 2#define CONDITION_H 3 4#include <stdint.h> 5#include <sys/types.h> 6#include <time.h> 7#include <pthread.h> 8 9#include <Mutex.h>1011typedef int64_t nsecs_t; // nano-seconds1213class Condition {14public:15 enum {16 PRIVATE = 0,17 SHARED = 118 };1920 enum WakeUpType {21 WAKE_UP_ONE = 0,22 WAKE_UP_ALL = 123 };2425 Condition();26 Condition(int type);27 ~Condition();2829 status_t wait(Mutex& mutex);30 status_t waitRelative(Mutex& mutex, nsecs_t reltime);31 void signal();32 void signal(WakeUpType type) {33 if (type == WAKE_UP_ONE) {34 signal();35 } else {36 broadcast();37 }38 }39 void broadcast();40private:41 pthread_cond_t mCond;42};4344inline Condition::Condition() {45 pthread_cond_init(&mCond, NULL);46}4748inline Condition::Condition(int type) {49 if (type == SHARED) {50 pthread_condattr_t attr;51 pthread_condattr_init(&attr);52 pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);53 pthread_cond_init(&mCond, &attr);54 pthread_condattr_destroy(&attr);55 } else {56 pthread_cond_init(&mCond, NULL);57 }58}5960inline Condition::~Condition() {61 pthread_cond_destroy(&mCond);62}6364inline status_t Condition::wait(Mutex &mutex) {65 return -pthread_cond_wait(&mCond, &mutex.mMutex);66}6768inline status_t Condition::waitRelative(Mutex &mutex, nsecs_t reltime) {69 struct timeval t;70 struct timespec ts;71 gettimeofday(&t, NULL);72 ts.tv_sec = t.tv_sec;73 ts.tv_nsec = t.tv_usec*1000;7475 ts.tv_sec += reltime / 1000000000;76 ts.tv_nsec += reltime % 1000000000;77 if (ts.tv_nsec >= 1000000000) {78 ts.tv_nsec -= 1000000000;79 ts.tv_sec += 1;80 }81 return -pthread_cond_timedwait(&mCond, &mutex.mMutex, &ts);82}8384inline void Condition::signal() {85 pthread_cond_signal(&mCond);86}8788inline void Condition::broadcast() {89 pthread_cond_broadcast(&mCond);90}9192#endif //CONDITION_H
Thread封装
为了方便使用线程,我们对 pthread 进行封装。完整的代码如下:
1#include <Mutex.h> 2#include <Condition.h> 3 4typedef enum { 5 Priority_Default = -1, 6 Priority_Low = 0, 7 Priority_Normal = 1, 8 Priority_High = 2 9} ThreadPriority; 10 11class Runnable { 12public: 13 virtual ~Runnable(){} 14 15 virtual void run() = 0; 16}; 17 18/** 19 * Thread can use a custom Runnable, but must delete Runnable constructor yourself 20 */ 21class Thread : public Runnable { 22public: 23 24 Thread(); 25 26 Thread(ThreadPriority priority); 27 28 Thread(Runnable *runnable); 29 30 Thread(Runnable *runnable, ThreadPriority priority); 31 32 virtual ~Thread(); 33 34 void start(); 35 36 void join(); 37 38 void detach(); 39 40 pthread_t getId() const; 41 42 bool isActive() const; 43 44protected: 45 static void *threadEntry(void *arg); 46 47 int schedPriority(ThreadPriority priority); 48 49 virtual void run(); 50 51protected: 52 Mutex mMutex; 53 Condition mCondition; 54 Runnable *mRunnable; 55 ThreadPriority mPriority; // thread priority 56 pthread_t mId; // thread id 57 bool mRunning; // thread running 58 bool mNeedJoin; // if call detach function, then do not call join function 59}; 60 61inline Thread::Thread() { 62 mNeedJoin = true; 63 mRunning = false; 64 mId = -1; 65 mRunnable = NULL; 66 mPriority = Priority_Default; 67} 68 69inline Thread::Thread(ThreadPriority priority) { 70 mNeedJoin = true; 71 mRunning = false; 72 mId = -1; 73 mRunnable = NULL; 74 mPriority = priority; 75} 76 77inline Thread::Thread(Runnable *runnable) { 78 mNeedJoin = false; 79 mRunning = false; 80 mId = -1; 81 mRunnable = runnable; 82 mPriority = Priority_Default; 83} 84 85inline Thread::Thread(Runnable *runnable, ThreadPriority priority) { 86 mNeedJoin = false; 87 mRunning = false; 88 mId = -1; 89 mRunnable = runnable; 90 mPriority = priority; 91} 92 93inline Thread::~Thread() { 94 join(); 95 mRunnable = NULL; 96} 97 98inline void Thread::start() { 99 if (!mRunning) {100 pthread_create(&mId, NULL, threadEntry, this);101 mNeedJoin = true;102 }103 // wait thread to run104 mMutex.lock();105 while (!mRunning) {106 mCondition.wait(mMutex);107 }108 mMutex.unlock();109}110111inline void Thread::join() {112 Mutex::Autolock lock(mMutex);113 if (mId > 0 && mNeedJoin) {114 pthread_join(mId, NULL);115 mNeedJoin = false;116 mId = -1;117 }118}119120inline void Thread::detach() {121 Mutex::Autolock lock(mMutex);122 if (mId >= 0) {123 pthread_detach(mId);124 mNeedJoin = false;125 }126}127128inline pthread_t Thread::getId() const {129 return mId;130}131132inline bool Thread::isActive() const {133 return mRunning;134}135136inline void* Thread::threadEntry(void *arg) {137 Thread *thread = (Thread *) arg;138139 if (thread != NULL) {140 thread->mMutex.lock();141 thread->mRunning = true;142 thread->mCondition.signal();143 thread->mMutex.unlock();144145 thread->schedPriority(thread->mPriority);146147 // when runnable is exit,run runnable else run()148 if (thread->mRunnable) {149 thread->mRunnable->run();150 } else {151 thread->run();152 }153154 thread->mMutex.lock();155 thread->mRunning = false;156 thread->mCondition.signal();157 thread->mMutex.unlock();158 }159160 pthread_exit(NULL);161162 return NULL;163}164165inline int Thread::schedPriority(ThreadPriority priority) {166 if (priority == Priority_Default) {167 return 0;168 }169170 struct sched_param sched;171 int policy;172 pthread_t thread = pthread_self();173 if (pthread_getschedparam(thread, &policy, &sched) < 0) {174 return -1;175 }176 if (priority == Priority_Low) {177 sched.sched_priority = sched_get_priority_min(policy);178 } else if (priority == Priority_High) {179 sched.sched_priority = sched_get_priority_max(policy);180 } else {181 int min_priority = sched_get_priority_min(policy);182 int max_priority = sched_get_priority_max(policy);183 sched.sched_priority = (min_priority + (max_priority - min_priority) / 2);184 }185186 if (pthread_setschedparam(thread, policy, &sched) < 0) {187 return -1;188 }189 return 0;190}191192inline void Thread::run() {193 // do nothing194}
备注:
-
为何不用C++11的线程?
编译器可能不支持C++11。
这里只是做兼容,而且音视频的库基本都是C语言编写的,这里主要是考虑到二进制接口兼容性的问题。在使用带异常的C++时,有可能会导致ffmpeg某些版本出现偶然的内部崩溃问题,这个是我在实际使用过程中发现的。
这个C++二进制接口兼容性问题各个技术大牛有专门讨论过,我并不擅长C++,也讲不出更深入的说法,想要了解的话,建议自行找资料了解,这里就不费口舌了。
-
当继承Thread类时,我们需要重写run方法。
Runnable 是一个抽象基类,用来模仿Java层的Runnable接口。当我们使用Runnable时,必须有外部释放Runnable的内存,这里并没有垃圾回收功能,要做成Java那样能够自动回收内存,这个超出了我的能力范围。我这里只是为了方便使用而简单地将pthread封装起来使用而已。
如果要使用pthread_detach的时候,希望调用Thread的detach方法。这样Thread的线程标志不会混乱。
调用pthread_detach后,如果不调用pthread_exit方法,会导致线程结构有个8K的内存没有释放掉。默认情况下是没有detach的,此时,如果要释放线程的内存,需要在线程执行完成之后,不管是否调用了pthread_exit方法,都调用pthread_join方法阻塞销毁线程占用的那个8K内存。
这也是我为何要将Thread封装起来的原因之一。我们有时候不想detach一个线程,这时候,我们就需要用join来释放,重复调用的话,会导致出现 fatal signal 6的情况。
备注2
关于NDK 常见的出错信息意义:
-
fatal signal 4:常见情况是方法没有返回值,比如一个返回int的方法,到最后没有return ret。
-
fatal signal 6:常见情况是pthread 线程类阻塞了,比如重复调用join方法,或者一个线程detach之后,然后又调用join就会出现这种情况
-
fatal signal 11:空指针出错。在多线程环境下,由于对象在另外一个线程释放调用,而该线程并没有停止,仍然在运行阶段,此时调用到该被释放的对象时,就会出现fatalsignal 11 的错误。
-
其他的出错信息一般比较少见,至少本人接触到的NDK代码,还没遇到过其他出错信息。
好了,我们这里封装完了基础公共类之后,就可以愉快地编写C/C++代码了。
关注微信公众号【 纸上浅谈】,阅读更多Android开发、音视频、Camera、OpenGL、NDK 开发相关文章~~~
