作者:cain_huang

在开始介绍播放器开发之前,我们首先对posix库进行一定的封装,得到我们想要的 MutexConditionThread等类。

至于为何不用 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++代码了。