從3月份開始到4月底,開始了多線程解碼的項目。這個項目主要爲了利用當今的CPU多核技術,將H.264解碼單線程變成多線程,以提高解碼速度。剛開始的時候不知道如何下手,不過老大(項目經理)給了我一些提示,讓我先看看ffmpeg中的解碼多線程是如何實現的,然後再根據ffmpeg中的多線程思路來完善我們公司自己的解碼器多線程功能。因此,基於這個項目基本上是按照以下的步驟實現的:
1)由於按照老大的提示需要參照ffmpeg的多線程解碼思路,所以第一步首先就需要實現ffmpeg的單步調試,以理解ffmpeg多線程解碼的運行方式。這裏可選擇兩種方法,第一種適合只安裝了VS2010的windows用戶,第二種則適合於在linux上進行代碼調試的人。第一種方法稍微複雜,主要是利用編譯出的pdb信息進行調試,詳細步驟可參考之前的文章《VS2010中進行ffmpeg編譯與單步調試》。第二種方法則相對比較簡單,由於ffmpeg源碼本身是在linux平臺上進行開發的,因此只要在linux平臺上先編譯源碼,然後安裝相應的IDE進行調試即可,IDE可採用QT creator或者code blocks。不過這裏值得注意的是,利用第一種方法進行ffmpeg的調試相對比較粗糙,因爲現在最新的ffmpeg版本代碼都需要C99的支持,而VS2010則不支持C99標準,所以需要在編譯時下載C99 warper進行包裝後才能編譯通過形成pdb文件,但這樣一來,在調試的時候問題也就來了,由於在編譯的時候採用了C99轉換,故而實際生成的調試信息和源代碼不能精確匹配,可能會給調試人員帶來理解上的困擾。因而,筆者建議,在條件允許的情況下,可以採用VS2012以後的版本進行調試,或者最好採用linux平臺進行代碼調試。但如果你沒有安裝linux平臺怎麼辦呢?筆者按照自己的經驗,建議你可以安裝一臺虛擬機,然後在虛擬機上裝一個linux系統,這樣你就能夠充分享用windows和linux兩者帶來的便利了。
2)ffmpeg的調試工作做好以後,接下來就是理解ffmpeg的多線程解碼思路了。通過調試我們可以發現,在ffmpeg中,可以分別採用幀內多線程解碼和幀間多線程解碼,幀內多線程解碼的的依據主要是幀內各宏塊的參考宏塊可能相同,需要相同參考宏塊進行解碼的宏塊可以同時進行解碼。同理,幀間多線程解碼的依據也是由於各幀圖像需要的參考幀可能相同,需要相同參考幀的幀可以同時進行解碼,最容易理解的就是部分B幀的並行解碼了。由於幀內多線程解碼的效率並不是很高,因此我主要針對的是幀間多線程解碼方法,關於幀間多線程解碼的內容可以參考網站http://blog.csdn.net/bsplover/article/details/7542980和碩士論文《視頻編解碼算法的並行研究》,以及以下三張圖片內容:
相信通過這三個資料的學習,再結合相應的代碼跟蹤調試,應該對於理解ffmpeg幀間多線程解碼思路不會太難。
3)理解了ffmpeg多線程解碼,接下里就是對應公司的H.264解碼器進行修改了。在進行這一步驟之前,需要先熟悉多線程編碼的相關問題。關於多線程編碼,在操作系統進入多任務作業時就已經成爲了大家研究的熱點。隨後,在硬件CPU多核的推動之下,越來越多的系統和應用程序採用了多線程實現的方式,這樣可以充分利用CPU多核技術,使任務得以並行處理,加快了任務處理的時間,提高了程序運行的效率。在不同的平臺下,也出現了不同的多線程編碼方式,如在windows下,以win32多線程編碼爲主,在linux平臺上,則採用基於POSIX標準的pthread進行多線程編碼,兩種平臺上的編碼底層接口不同,也導致了跨平臺移植的困難。所幸,之後出現了win32 pthread版本的接口,可以方便在windows平臺下開發出符合POSIX標準的linux上可運行的多線程代碼。關於win32 pthread的源碼,大家可以上http://www.sourceware.org/pthreads-win32/上下載,這樣你就可以在vs平臺上使用win32 源碼或者DLL嵌入進行調試編碼。
接下來要講一下多線程編碼常用的一些函數了,這裏分爲win32 和POSIX進行對比說明:
1.關於線程創建和消亡的操作。
1.1 創建和撤銷一個POSIX線程
pthread_create(&tid, NULL, start_fn, arg); pthread_exit(status); |
1.2 創建和撤銷一個Win32線程
CreateThread(NULL, NULL, start_fn, arg, NULL, NULL); ExitThread(status); |
2.關於線程的等待(join or wait for)的操作。
在多線程模型下,一個線程有可能必須等待其他的線程結束了才能繼續運行。比如說司機和售票員,司機只有當售票員確定所有的人都上車了,即售票員的行動結束以後才能開車,在這之前司機必須等待。
2.1等待一個POSIX線程
pthread_join(T1); |
2.2等待一個Win32線程
WaitForSingleObject(T1); |
Win32還提供一個調用WaitForMulitpleObject(T[]),可以用來等待多個線程。
3.關於線程的強制撤銷(cancellation or killing)的操作。
3.1 撤銷一個POSIX線程
pthread_cancel(T1); |
3.2 撤銷一個Win32線程
TerminateThread(T1); |
4.1互斥量mutex是最簡單的同步變量,它實現的操作實際上就是一把互斥鎖,如果一個線程擁有了這個mutex,其他線程在申請擁有這個mutex的時候,就會被阻塞,直到等到先那個線程釋放這個mutex。在任何時候,mutex至多隻有一個擁有者,它的操作是完全排他性的。
4.1.1 POSIX的mutex操作
pthread_mutex_init(MUTEX, NULL); pthread_mutex_lock(MUTEX); pthread_mutex_trylock(MUTEX); pthread_mutex_timedlock(MUTEX, ABSTIME); pthread_mutex_unlock(MUTEX); pthread_mutex_destroy(MUTEX); |
4.1.2 Win32的mutex操作
CreateMutex(NULL, FALSE, NULL); WaitForSingleObject(MUTEX); ReleaseMutex(MUTEX); CloseHandle(MUTEX); |
4.1.3 Win32的CriticalSection操作
InitializeCriticalSection(&cs); EnterCriticalSection(&cs); TryEnterCriticalSection(&cs); LeaveCriticalSection(&cs); DeleteCriticalSection(&cs); |
4.2信號量semaphore最初是由E.W.Dijkstra於20世紀60年代引入的。通常,信號量是一個計數器和對於這個計數器的兩個操作(分別稱之爲P,V操作),以及一個等待隊列的總和。一個P操作使得計數器減少一次,如果計數器大於零,則執行P操作的線程繼續執行,如果小於零,那麼該線程就會被放入到等待隊列中;一個V操作使得計數器增加一次,如果等待隊列中由等待的線程,便釋放一個線程。
4.2.1 POSIX的信號量操作
sem_init(SEM, 0, VALUE); sem_wait(SEM); sem_trywait(SEM); sem_destroy(SEM); |
4.2.2 Win32的信號量操作
CreateSemaphore(NULL, 0, MaxVal, NULL); WaitForSingleObject(SEM); ReleaseSemaphore(SEM); CloseHandle(SEM); |
4.3.1 POSIX的條件量的操作
phtread_cond_init(COND, NULL); phtread_cond_wait(COND, MUTEX); phtread_cond_timedwait(COND, MUTEX, TIME); phtread_cond_signal(COND); phtread_cond_broadcast(COND); phtread_cond_destroy(COND); |
其中broadcast是用來喚醒所有等在該條件量上的線程。
Win32中並沒有條件量這個概念,但是它實現了一種叫做Event的同步變量,實質上和條件量是差不多的。
學習完上面的這些常用的多線程接口函數之後,我想大家現在就可以自己編寫一個多線程的程序了,當然爲了支持跨平臺,你可以選用上面提到的win32 pthread。同樣,我也是根據以上的多線程編碼知識實現了多線程解碼項目,最後歡迎大家提出批評和討論意見~~