在開始介紹播放器開發(fā)之前,我們首先對posix庫進(jìn)行一定的封裝,得到我們想要的Mutex、Condition、Thread等類。至于為何不用C++11自帶的相關(guān)類呢?這是考慮到編譯環(huán)境的問題,有些公司可能仍舊沒升級NDK的版本,不支持C++11,這里為了方便,只好利用Posix封裝一套Thread相關(guān)的基礎(chǔ)類,部分代碼參考(copy)自Android 源碼中的代碼。至于原理,這里就不介紹了,網(wǎng)上相關(guān)資料還是很多的,分析互斥鎖、條件鎖等原理不是本文章的重點。
Mutex封裝
Mutex的封裝可參考Android 的libutil庫里面的代碼,直接復(fù)制過來使用即可,代碼里面還封裝了AutoLock。代碼如下:
#ifndef MUTEX_H
#define MUTEX_H
#include <stdint.h>
#include <sys/types.h>
#include <time.h>
#include <pthread.h>
typedef int32_t status_t;
class Condition;
class Mutex {
public:
enum {
PRIVATE = 0,
SHARED = 1
};
Mutex();
Mutex(const char* name);
Mutex(int type, const char* name = NULL);
~Mutex();
// lock or unlock the mutex
status_t lock();
void unlock();
// lock if possible; returns 0 on success, error otherwise
status_t tryLock();
// Manages the mutex automatically. It'll be locked when Autolock is
// constructed and released when Autolock goes out of scope.
class Autolock {
public:
inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }
inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
inline ~Autolock() { mLock.unlock(); }
private:
Mutex& mLock;
};
private:
friend class Condition;
// A mutex cannot be copied
Mutex(const Mutex&);
Mutex& operator = (const Mutex&);
pthread_mutex_t mMutex;
};
inline Mutex::Mutex() {
pthread_mutex_init(&mMutex, NULL);
}
inline Mutex::Mutex(const char* name) {
pthread_mutex_init(&mMutex, NULL);
}
inline Mutex::Mutex(int type, const char* name) {
if (type == SHARED) {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&mMutex, &attr);
pthread_mutexattr_destroy(&attr);
} else {
pthread_mutex_init(&mMutex, NULL);
}
}
inline Mutex::~Mutex() {
pthread_mutex_destroy(&mMutex);
}
inline status_t Mutex::lock() {
return -pthread_mutex_lock(&mMutex);
}
inline void Mutex::unlock() {
pthread_mutex_unlock(&mMutex);
}
inline status_t Mutex::tryLock() {
return -pthread_mutex_trylock(&mMutex);
}
typedef Mutex::Autolock AutoMutex;
#endif //MUTEX_H
Condition封裝
Condition類的封裝跟Mutex一樣,直接從Android源碼里面復(fù)制過來,稍作修改即可。代碼如下:
#ifndef CONDITION_H
#define CONDITION_H
#include <stdint.h>
#include <sys/types.h>
#include <time.h>
#include <pthread.h>
#include <Mutex.h>
typedef int64_t nsecs_t; // nano-seconds
class Condition {
public:
enum {
PRIVATE = 0,
SHARED = 1
};
enum WakeUpType {
WAKE_UP_ONE = 0,
WAKE_UP_ALL = 1
};
Condition();
Condition(int type);
~Condition();
status_t wait(Mutex& mutex);
status_t waitRelative(Mutex& mutex, nsecs_t reltime);
void signal();
void signal(WakeUpType type) {
if (type == WAKE_UP_ONE) {
signal();
} else {
broadcast();
}
}
void broadcast();
private:
pthread_cond_t mCond;
};
inline Condition::Condition() {
pthread_cond_init(&mCond, NULL);
}
inline Condition::Condition(int type) {
if (type == SHARED) {
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&mCond, &attr);
pthread_condattr_destroy(&attr);
} else {
pthread_cond_init(&mCond, NULL);
}
}
inline Condition::~Condition() {
pthread_cond_destroy(&mCond);
}
inline status_t Condition::wait(Mutex &mutex) {
return -pthread_cond_wait(&mCond, &mutex.mMutex);
}
inline status_t Condition::waitRelative(Mutex &mutex, nsecs_t reltime) {
struct timeval t;
struct timespec ts;
gettimeofday(&t, NULL);
ts.tv_sec = t.tv_sec;
ts.tv_nsec = t.tv_usec*1000;
ts.tv_sec += reltime / 1000000000;
ts.tv_nsec += reltime % 1000000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_nsec -= 1000000000;
ts.tv_sec += 1;
}
return -pthread_cond_timedwait(&mCond, &mutex.mMutex, &ts);
}
inline void Condition::signal() {
pthread_cond_signal(&mCond);
}
inline void Condition::broadcast() {
pthread_cond_broadcast(&mCond);
}
#endif //CONDITION_H
Thread封裝
為了方便使用線程,我們對pthread進(jìn)行封裝。完整的代碼如下:
#include <Mutex.h>
#include <Condition.h>
typedef enum {
Priority_Default = -1,
Priority_Low = 0,
Priority_Normal = 1,
Priority_High = 2
} ThreadPriority;
class Runnable {
public:
virtual ~Runnable(){}
virtual void run() = 0;
};
/**
* Thread can use a custom Runnable, but must delete Runnable constructor yourself
*/
class Thread : public Runnable {
public:
Thread();
Thread(ThreadPriority priority);
Thread(Runnable *runnable);
Thread(Runnable *runnable, ThreadPriority priority);
virtual ~Thread();
void start();
void join();
void detach();
pthread_t getId() const;
bool isActive() const;
protected:
static void *threadEntry(void *arg);
int schedPriority(ThreadPriority priority);
virtual void run();
protected:
Mutex mMutex;
Condition mCondition;
Runnable *mRunnable;
ThreadPriority mPriority; // thread priority
pthread_t mId; // thread id
bool mRunning; // thread running
bool mNeedJoin; // if call detach function, then do not call join function
};
inline Thread::Thread() {
mNeedJoin = true;
mRunning = false;
mId = -1;
mRunnable = NULL;
mPriority = Priority_Default;
}
inline Thread::Thread(ThreadPriority priority) {
mNeedJoin = true;
mRunning = false;
mId = -1;
mRunnable = NULL;
mPriority = priority;
}
inline Thread::Thread(Runnable *runnable) {
mNeedJoin = false;
mRunning = false;
mId = -1;
mRunnable = runnable;
mPriority = Priority_Default;
}
inline Thread::Thread(Runnable *runnable, ThreadPriority priority) {
mNeedJoin = false;
mRunning = false;
mId = -1;
mRunnable = runnable;
mPriority = priority;
}
inline Thread::~Thread() {
join();
mRunnable = NULL;
}
inline void Thread::start() {
if (!mRunning) {
pthread_create(&mId, NULL, threadEntry, this);
mNeedJoin = true;
}
// wait thread to run
mMutex.lock();
while (!mRunning) {
mCondition.wait(mMutex);
}
mMutex.unlock();
}
inline void Thread::join() {
Mutex::Autolock lock(mMutex);
if (mId > 0 && mNeedJoin) {
pthread_join(mId, NULL);
mNeedJoin = false;
mId = -1;
}
}
inline void Thread::detach() {
Mutex::Autolock lock(mMutex);
if (mId >= 0) {
pthread_detach(mId);
mNeedJoin = false;
}
}
inline pthread_t Thread::getId() const {
return mId;
}
inline bool Thread::isActive() const {
return mRunning;
}
inline void* Thread::threadEntry(void *arg) {
Thread *thread = (Thread *) arg;
if (thread != NULL) {
thread->mMutex.lock();
thread->mRunning = true;
thread->mCondition.signal();
thread->mMutex.unlock();
thread->schedPriority(thread->mPriority);
// when runnable is exit,run runnable else run()
if (thread->mRunnable) {
thread->mRunnable->run();
} else {
thread->run();
}
thread->mMutex.lock();
thread->mRunning = false;
thread->mCondition.signal();
thread->mMutex.unlock();
}
pthread_exit(NULL);
return NULL;
}
inline int Thread::schedPriority(ThreadPriority priority) {
if (priority == Priority_Default) {
return 0;
}
struct sched_param sched;
int policy;
pthread_t thread = pthread_self();
if (pthread_getschedparam(thread, &policy, &sched) < 0) {
return -1;
}
if (priority == Priority_Low) {
sched.sched_priority = sched_get_priority_min(policy);
} else if (priority == Priority_High) {
sched.sched_priority = sched_get_priority_max(policy);
} else {
int min_priority = sched_get_priority_min(policy);
int max_priority = sched_get_priority_max(policy);
sched.sched_priority = (min_priority + (max_priority - min_priority) / 2);
}
if (pthread_setschedparam(thread, policy, &sched) < 0) {
return -1;
}
return 0;
}
inline void Thread::run() {
// do nothing
}
備注:
為何不用C++11的線程?編譯器可能不支持C++11。這里只是做兼容,而且音視頻的庫基本都是C語言編寫的,這里主要是考慮到二進(jìn)制接口兼容性的問題。在使用帶異常的C++時,有可能會導(dǎo)致ffmpeg某些版本出現(xiàn)偶然的內(nèi)部崩潰問題,這個是我在實際使用過程中發(fā)現(xiàn)的。這個C++二進(jìn)制接口兼容性問題各個技術(shù)大牛有專門討論過,我并不擅長C++,也講不出更深入的說法,想要了解的話,建議自行找資料了解,這里就不費口舌了。
當(dāng)繼承Thread類時,我們需要重寫run方法。
Runnable 是一個抽象基類,用來模仿Java層的Runnable接口。當(dāng)我們使用Runnable時,必須有外部釋放Runnable的內(nèi)存,這里并沒有垃圾回收功能,要做成Java那樣能夠自動回收內(nèi)存,這個超出了我的能力范圍。我這里只是為了方便使用而簡單地將pthread封裝起來使用而已。
如果要使用pthread_detach的時候,希望調(diào)用Thread的detach方法。這樣Thread的線程標(biāo)志不會混亂。調(diào)用pthread_detach后,如果不調(diào)用pthread_exit方法,會導(dǎo)致線程結(jié)構(gòu)有個8K的內(nèi)存沒有釋放掉。默認(rèn)情況下是沒有detach的,此時,如果要釋放線程的內(nèi)存,需要在線程執(zhí)行完成之后,不管是否調(diào)用了pthread_exit方法,都調(diào)用pthread_join方法阻塞銷毀線程占用的那個8K內(nèi)存。這也是我為何要將Thread封裝起來的原因之一。我們有時候不想detach一個線程,這時候,我們就需要用join來釋放,重復(fù)調(diào)用的話,會導(dǎo)致出現(xiàn) fatal signal 6 的情況。
備注2:
關(guān)于NDK 常見的出錯信息意義:
fatal signal 4: 常見情況是方法沒有返回值,比如一個返回int的方法,到最后沒有return ret。
fatal signal 6:常見情況是pthread 線程類阻塞了,比如重復(fù)調(diào)用join方法,或者一個線程detach之后,然后又調(diào)用join就會出現(xiàn)這種情況
fatal signal 11:空指針出錯。在多線程環(huán)境下,由于對象在另外一個線程釋放調(diào)用,而該線程并沒有停止,仍然在運行階段,此時調(diào)用到該被釋放的對象時,就會出現(xiàn)fatal signal 11 的錯誤。
其他的出錯信息一般比較少見,至少本人接觸到的NDK代碼,還沒遇到過其他出錯信息。
好了,我們這里封裝完了基礎(chǔ)公共類之后,就可以愉快地編寫C/C++代碼了。
完整代碼請參考本人的播放器項目:CainPlayer