ffmpeg 多線程編解碼


http://blog.csdn.net/shuiniu1224/article/details/24932869

 

在ffmpeg中,可以分別采用幀內多線程解碼和幀間多線程解碼,幀內多線程解碼的的依據主要是幀內各宏塊的參考宏塊可能相同,需要相同參考宏塊進行解碼的宏塊可以同時進行解碼。同理,幀間多線程解碼的依據也是由於各幀圖像需要的參考幀可能相同,需要相同參考幀的幀可以同時進行解碼,最容易理解的就是部分B幀的並行解碼了。由於幀內多線程解碼的效率並不是很高,因此我主要針對的是幀間多線程解碼方法。

關於幀間多線程解碼的內容可以參考網站http://blog.csdn.NET/bsplover/article/details/7542980和碩士論文《視頻編解碼算法的並行研究》,以及以下三張圖片內容:

 

 

 

 

 

 

 

 

 

=================================

http://blog.csdn.net/xietao_live_cn/article/details/6452030

 

FFMPEG多線程解碼

 

FFMPEG多線程解碼器分為Frame級和Slice級兩種,Slice級多線程同時解碼一幀中不同的部分。Frame級多線程同時接受多幀碼流,實現並行解碼,當前幀處於顯示狀態時,未來的幾幀已經在其他線程中被解碼。

  1. 1.         Slice Threading

         FFmpeg中,dvvideo_decoder, ffv1_decoder, h264_decoder, mpeg2_video_decoder和mpeg_video_decoder均支持了Slice Threading。

實現方法是:首先為codecContext注冊注冊多線程處理函數excute(),Codec解碼過程中處理Slice時調用avctx->excute()。excute()啟動Slice解碼工作線程開始多線程解碼,同時快速返回開始下一Slice的解析和解碼。

Frame Threading主線程和解碼線程的同步如圖1所示。

 

 

  1. 2.         Frame Threading

X264編碼的片子,同樣1080P的尺寸的,才不到10G,而H264編碼的就要20多G

x264是一個基於h.264的免費開源的視頻Codec,屬於后起之秀

H不開源,是商業的
X開源,是自由的

x264是一種免費的、具有更優秀算法的H.264/MPEG-4 AVC視頻壓縮編碼格式。它同xvid一樣都是開源項目,但x264是采用H.264標准的,而xvid是采用MPEG-4早期標准的。由於H.264是2003年正式發布的最新的視頻編碼標准,因此,在通常情況下,x264壓縮出的視頻文件在相同質量下要比xvid壓縮出的文件要小,或者也可以說,在相同體積下比xvid壓縮出的文件質量要好。它符合GPL許可證。

H.264
    H.264是由國際電信聯盟(ITU-T)所制定的新一代的視頻壓縮格式。H.264最具價值的部分無疑是更高的數據壓縮比。在同等的圖像質量條件下,H.264的數據壓縮比能比當前DVD系統中使用的 MPEG-2高2-3倍,比MPEG-4高1.5-2倍。正因為如此,經過H.264壓縮的視頻數據,在網絡傳輸過程中所需要的帶寬更少,也更加經濟。在 MPEG-2需要6Mbps的傳輸速率匹配時,H.264只需要1Mbps-2Mbps的傳輸速率。
    與MPEG-4一樣,經過H.264壓縮的視頻文件一般也是采用.avi 作為其后綴名,同樣不容易辨認,只能通過解碼器來自己識別。

 

         目前為止支持Frame Threading的解碼器有h264_decoder, huffyuv_decoder, ffvhuff_decoder, mdec_decoder, mimic_decoder, mpeg4_decoder, theora_decoder, vp3_decoder和vp8_decoder。

Frame Threading有如下限制:用戶函數draw_horiz_band()必須是線程安全的;為了提升性能,用戶應該為codec提供線程安全的get_buffer()回調函數;用戶必須能處理多線程帶來的延時。

另外,支持Frame Threading的codec要求每個包包含一個完整幀。Buffer內容在ff_thread_await_progress()調用之前不能讀,同樣,包括加邊draw_edges()在內的處理,在ff_thread_report_progress()調用之后,Buffer內容不能寫。

        

每個線程都有以下四個狀態。如圖2所示,為了保證線程安全,若Codec未實現update_thread_context()和線程安全的get_buffer(),則必須在解碼完成后才能將狀態轉換為STATUS_SETUP_FINISHED,意味着下一個線程只能在當前線程解碼完成后才能開始解碼。

而如圖3所示,如果Codec實現update_thread_context()和線程安全的get_buffer(),線程狀態可以在解碼開始之前轉換為STATUS_SETUP_FINISHED,這樣,下一個線程就可能與當前線程並行。

 

 

         

解碼主線程通過調用submit_packet()將碼流交給對應的解碼線程。主線程和解碼線程的同步如圖4所示。

 

 

 

======================

http://jmvc.blog.sohu.com/145356341.html

3. 多線程代碼分析

(1)文檔解讀

分析完X264的基本架構,來看看多線程發揮力量的地方。X264自帶的多線程介紹文檔是本課題的必讀文檔,它存放在X264的DOC文件夾下。本文描述的大意是:當前的X264多線程模式已經放棄基於slice的並行編碼,轉而采用幀級和宏塊級的並行,原因是slice並行需要采用slice group,會引入而外冗余降低編碼效率。摘抄一段原文如下:

New threading method: frame-based
application calls x264
x264 runs B-adapt and ratecontrol (serial to the application, but parallel to the other x264 threads)
spawn a thread for this frame
thread runs encode in 1 slice, deblock, hpel filter
meanwhile x264 waits for the oldest thread to finish
return to application, but the rest of the threads continue running in the background
No additional threads are needed to decode the input, unless decoding+B-adapt is slower than slice+deblock+hpel, in which case an additional input thread would allow decoding in parallel to B-adapt.【3】

以上的說明意味着,X264采用B幀在編碼時不作為參考幀,所以適宜對其進行並行。

(2)運行狀況分析

先來看看x264_pthread_create被調用的地方,只有這些地方才實實在在的創建了線程。

 x264_pthread_create( &h->thread_handle, NULL, (void*)x264_slices_write, h )

 x264_pthread_create( &look_h->thread_handle, NULL, (void *)x264_lookahead_thread, look_h )

 x264_pthread_create( &h->tid, NULL, (void*)read_frame_thread_int, h->next_args )

 

x264_pthread_create( pool->thread_handle+i, NULL, (void*)x264_threadpool_thread, pool )    //新增的

 

 由上圖的運行可以看出,在開啟了--threads 4后。x264_slices_write()可以開啟4個線程同時編碼,而同時存在一個主線程和一個x264_lookahead_thread()線程。x264_slices_write()的優先級為低,原因是調用了

     if( h->param.i_sync_lookahead )
        x264_lower_thread_priority( 10 );

調低本線程的優先級。read_frame_thread_int()是讀磁盤上的流數據信息,因為I/O和內存的不同步,所以應該分開線程處理。

 

在x264_encoder_open()中可以找到一下代碼,可以看到對於x264_slices_write()和x264_lookahead_thread()都有被分配了專有的上下文變量,供單一線程使用。

    for( i = 1; i < h->param.i_threads + !!h->param.i_sync_lookahead; i++ )
        CHECKED_MALLOC( h->thread[i], sizeof(x264_t) );

 

(3)如何確保按指定線程數來開啟線程編碼?

按打印實驗可以看到,假設使用--threads 4的參數選項,代碼會同時開啟4個x264_slices_write()線程,然后每編完一個幀(前面的一個線程返回后),一個新的被產生出來,使得x264_slices_write()線程總數保持在4個,這一過程的相關代碼如下:

 

int     x264_encoder_encode( x264_t *h,x264_nal_t **pp_nal, int *pi_nal,x264_picture_t *pic_in,
                             x264_picture_t *pic_out )
{
...
    if( h->param.i_threads > 1)
    {
        int i = ++h->i_thread_phase;
        int t = h->param.i_threads;
        thread_current = h->thread[ i%t ];
        thread_prev    = h->thread[ (i-1)%t ];
        thread_oldest  = h->thread[ (i+1)%t ];
        x264_thread_sync_context( thread_current, thread_prev );
        x264_thread_sync_ratecontrol( thread_current, thread_prev, thread_oldest );
        h = thread_current;
    }

...

    /* Write frame */
    if( h->param.i_threads > 1 )
    {
        printf("x264_pthread_create\n");
        if( x264_pthread_create( &h->thread_handle, NULL, (void*)x264_slices_write, h ) )
            return -1;
        h->b_thread_active = 1;
    }
    else
        if( (intptr_t)x264_slices_write( h ) )
            return -1;

    return x264_encoder_frame_end( thread_oldest, thread_current, pp_nal, pi_nal, pic_out );

...

}

 

static int x264_encoder_frame_end( x264_t *h, x264_t *thread_current,x264_nal_t **pp_nal, int *pi_nal, x264_picture_t *pic_out )
{
...
    if( h->b_thread_active )
    {
        void *ret = NULL;
        x264_pthread_join( h->thread_handle, &ret );
        if( (intptr_t)ret )
            return (intptr_t)ret;
        h->b_thread_active = 0;
    }

...

}

 

從以上兩個函數的代碼段可以看到,h上下文中保持的線程不會多於4個, x264_pthread_create()根據主線程的調用,創建出x264_slices_write線程,然后thread_oldest被指定並被率控函數判斷重設,當前的線程數還不足4的時候,thread_oldest指向新線程,h->b_thread_active為0,不能進入x264_encoder_frame_end()的相關代碼,主線程繼續循環創建x264_slices_write線程,當線程總數為4,這時thread_oldest指向4個線程中被判斷最快返回的那個,這時h->b_thread_active=1將進入x264_pthread_join(),那樣,該線程就將主線至於阻塞狀態,直至thread_oldest完成,才能重現創建新線程,以此機制,保持指定數碼的編碼線程數。

 

(4)x264_lookahead_thread()線程的作用

在分析這個線程之前,來看看兩個重要的線程控制函數:

//喚醒等待該條件變量的所有線程。如果沒有等待的線程,則什么也不做。

#define x264_pthread_cond_broadcast  pthread_cond_broadcast

//自動解鎖互斥量(如同執行了 pthread_unlock_mutex),並等待條件變量觸發。這時線程掛起,不占用 CPU 

時間,直到條件變量被觸發。在調用 pthread_cond_wait 之前,應用程序必須加鎖互斥量。pthread_cond_wait 函數返回前,自動重新對互斥量加鎖(如同執行了 pthread_lock_mutex)。
#define x264_pthread_cond_wait       pthread_cond_wait

 

以下的代碼是X264中x264_lookahead_thread代碼經常阻塞的地方,

**************************代碼段A********************************************

        if( h->lookahead->next.i_size <= h->lookahead->i_slicetype_length )
        {
            while( !h->lookahead->ifbuf.i_size && !h->lookahead->b_exit_thread )
                x264_pthread_cond_wait( &h->lookahead->ifbuf.cv_fill, &h->lookahead->ifbuf.mutex );
            x264_pthread_mutex_unlock( &h->lookahead->ifbuf.mutex );
        }
        else
        {
            x264_pthread_mutex_unlock( &h->lookahead->ifbuf.mutex );
            x264_lookahead_slicetype_decide( h );
        }

這里是等待滿足!h->lookahead->ifbuf.i_size && !h->lookahead->b_exit_thread 的條件,后一條件在正常編碼過程是TRUE,因為不會無故退出線程。那么這里等待的其實是ifbuf.i_size為非0.查找相關代碼,

 

這里的 ifbuf.i_size條件是在x264_synch_frame_list_push()得到滿足的,這里在得到一個輸入的新編碼幀后將發出信號。

    slist->list[ slist->i_size++ ] = frame;
    x264_pthread_cond_broadcast( &slist->cv_fill );

在代碼段A中,if( h->lookahead->next.i_size <= h->lookahead->i_slicetype_length )條件中,i_slicetype_length表示為了進行slice type的判斷而緩存的幀,它的值有取決於h->frames.i_delay,由代碼的初始化設定值決定(默認為40)。也就是說預存40幀的數值,進行slice type決定用。暫時不詳細分析slice type判斷的具體實現,它的大概思想是根據碼率,GOP和失真狀況的權衡,來進行幀類型選擇,在類似實時通信場合,不允許B幀的使用,也不可能預存那么多幀,這樣的處理沒有意義。

 

回頭看這里的處理意義,是阻塞線程,等待后續的輸入幀,然后利用處理規則來決定其slice type,為slice編碼准備幀。

 

(5)宏塊級別的並行

在數據結構x264_frame_t中,有變量x264_pthread_cond_t  cv; 該變量分別在下面的兩個函數里被封裝了阻塞和喚醒:

void          x264_frame_cond_broadcast( x264_frame_t *frame, int i_lines_completed );
void          x264_frame_cond_wait( x264_frame_t *frame, int i_lines_completed );

考查它們被調用的地方,

************代碼B****************from x264_macroblock_analyse( )->x264_mb_analyse_init()

int thresh = pix_y + h->param.analyse.i_mv_range_thread;
for( i = (h->sh.i_type == SLICE_TYPE_B); i >= 0; i-- )
{
    x264_frame_t **fref = i ? h->fref1 : h->fref0;
    int i_ref = i ? h->i_ref1 : h->i_ref0;
    for( j=0; j<i_ref; j++ )
    {
        x264_frame_cond_wait( fref[j], thresh );
        thread_mvy_range = X264_MIN( thread_mvy_range, fref[j]->i_lines_completed - pix_y );
    }
}

**************************代碼C************************************from x264_fdec_filter_row()

if( h->param.i_threads > 1 && h->fdec->b_kept_as_ref )

    x264_frame_cond_broadcast( h->fdec, mb_y*16 + (b_end ? 10000 : -(X264_THREAD_HEIGHT <<h->sh.b_mbaff)) );
}

從上面的代碼段可以看到沒完成圖像一行的編碼,便會使用mb_y*16 -X264_THREAD_HEIGH的值來嘗試喚醒x264_pthread_cond_wait( &frame->cv, &frame->mutex ),要判斷的條件是 

mb_y*16 -X264_THREAD_HEIGH < thresh = pix_y + h->param.analyse.i_mv_range_thread;

后者作為一個設想的閾值,用於確保依賴於本幀的后續幀在編碼時,本幀已經編碼出若干行宏塊,以后續編碼幀的基礎,那樣可以設想的情形如下圖,不過X264是以編碼完整行為單位的。

本文的分析道這里告一段落,對於幀間多線程分析和宏塊的並行優化,或按自己的應用做代碼裁剪,可以通過改正上面的(4)(5)代碼段來實現,在當前(四核CPU)的X264測試中,已有代碼確實能夠很好的利用多核資源,並行編碼的話題會隨硬件的升級而不斷探索下去。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM