作者:cain_huang
在开始介绍播放器开发之前,我们首先对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;
10
11class Condition;
12
13class Mutex {
14public:
15 enum {
16 PRIVATE = 0,
17 SHARED = 1
18 };
19 Mutex();
20 Mutex(const char* name);
21 Mutex(int type, const char* name = NULL);
22 ~Mutex();
23
24 // lock or unlock the mutex
25 status_t lock();
26 void unlock();
27
28 // lock if possible; returns 0 on success, error otherwise
29 status_t tryLock();
30
31 // Manages the mutex automatically. It'll be locked when Autolock is
32 // 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 };
41
42private:
43 friend class Condition;
44
45 // A mutex cannot be copied
46 Mutex(const Mutex&);
47 Mutex& operator = (const Mutex&);
48
49 pthread_mutex_t mMutex;
50};
51
52inline Mutex::Mutex() {
53 pthread_mutex_init(&mMutex, NULL);
54}
55
56inline Mutex::Mutex(const char* name) {
57 pthread_mutex_init(&mMutex, NULL);
58}
59
60inline 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}
71
72inline Mutex::~Mutex() {
73 pthread_mutex_destroy(&mMutex);
74}
75
76inline status_t Mutex::lock() {
77 return -pthread_mutex_lock(&mMutex);
78}
79
80inline void Mutex::unlock() {
81 pthread_mutex_unlock(&mMutex);
82}
83
84inline status_t Mutex::tryLock() {
85 return -pthread_mutex_trylock(&mMutex);
86}
87typedef Mutex::Autolock AutoMutex;
88
89#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>
10
11typedef int64_t nsecs_t; // nano-seconds
12
13class Condition {
14public:
15 enum {
16 PRIVATE = 0,
17 SHARED = 1
18 };
19
20 enum WakeUpType {
21 WAKE_UP_ONE = 0,
22 WAKE_UP_ALL = 1
23 };
24
25 Condition();
26 Condition(int type);
27 ~Condition();
28
29 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};
43
44inline Condition::Condition() {
45 pthread_cond_init(&mCond, NULL);
46}
47
48inline 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}
59
60inline Condition::~Condition() {
61 pthread_cond_destroy(&mCond);
62}
63
64inline status_t Condition::wait(Mutex &mutex) {
65 return -pthread_cond_wait(&mCond, &mutex.mMutex);
66}
67
68inline 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;
74
75 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}
83
84inline void Condition::signal() {
85 pthread_cond_signal(&mCond);
86}
87
88inline void Condition::broadcast() {
89 pthread_cond_broadcast(&mCond);
90}
91
92#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 run
104 mMutex.lock();
105 while (!mRunning) {
106 mCondition.wait(mMutex);
107 }
108 mMutex.unlock();
109}
110
111inline 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}
119
120inline void Thread::detach() {
121 Mutex::Autolock lock(mMutex);
122 if (mId >= 0) {
123 pthread_detach(mId);
124 mNeedJoin = false;
125 }
126}
127
128inline pthread_t Thread::getId() const {
129 return mId;
130}
131
132inline bool Thread::isActive() const {
133 return mRunning;
134}
135
136inline void* Thread::threadEntry(void *arg) {
137 Thread *thread = (Thread *) arg;
138
139 if (thread != NULL) {
140 thread->mMutex.lock();
141 thread->mRunning = true;
142 thread->mCondition.signal();
143 thread->mMutex.unlock();
144
145 thread->schedPriority(thread->mPriority);
146
147 // when runnable is exit,run runnable else run()
148 if (thread->mRunnable) {
149 thread->mRunnable->run();
150 } else {
151 thread->run();
152 }
153
154 thread->mMutex.lock();
155 thread->mRunning = false;
156 thread->mCondition.signal();
157 thread->mMutex.unlock();
158 }
159
160 pthread_exit(NULL);
161
162 return NULL;
163}
164
165inline int Thread::schedPriority(ThreadPriority priority) {
166 if (priority == Priority_Default) {
167 return 0;
168 }
169
170 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 }
185
186 if (pthread_setschedparam(thread, policy, &sched) < 0) {
187 return -1;
188 }
189 return 0;
190}
191
192inline void Thread::run() {
193 // do nothing
194}
备注:
- 为何不用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:空指针出错。在多线程环境下,由于对象在另外一个线程释放调用,而该线程并没有停止,仍然在运行阶段,此时调用到该被释放的对象时,就会出现fatal
- 其他的出错信息一般比较少见,至少本人接触到的NDK代码,还没遇到过其他出错信息。
好了,我们这里封装完了基础公共类之后,就可以愉快地编写C/C++代码了。