转自:http://blog.csdn.net/shuiniu1224/article/details/24932869
从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。同样,我也是根据以上的多线程编码知识实现了多线程解码项目,最后欢迎大家提出批评和讨论意见~~