4

ffmpeg-mt がマージされた ffmpeg の最新ビルドを使い始めました。

ただし、私のアプリケーションは TBB (Intel Threading Building Blocks) を使用しているため、新しいスレッドの作成と同期を伴う ffmpeg-mt の実装は、デコード関数を実行する TBB タスクを潜在的にブロックする可能性があるため、あまり適していません。また、キャッシュを不必要に破棄します。

ffmpeg がマルチスレッドを有効にするために使用するインターフェイスを実装しているように見える pthread.c を見回していました。

私の質問は、同じ関数を実装するが、明示的なスレッドの代わりに tbb タスクを使用する tbb.c を作成できるかどうかです。

私は C の経験がありませんが、tbb (C++) を簡単に ffmpeg にコンパイルすることはできないと思います。したがって、実行時にffmpeg関数ポインタを何らかの方法で上書きするのがよいでしょうか?

TBB を ffmpeg スレッド API に実装することに関する提案やコメントをいただければ幸いです。

4

2 に答える 2

7

それで、ffmpegコードを読んでそれを行う方法を見つけました。

基本的には、以下のコードを含めて、tbb_avcodec_open/tbb_avcodec_closeffmpegs の代わりに使用するだけavcodec_open/avcodec_closeです。

これは、TBB タスクを使用してデコードを並行して実行します。

 // Author Robert Nagy

#include "tbb_avcodec.h"

#include <tbb/task.h>
#include <tbb/atomic.h>

extern "C" 
{
    #define __STDC_CONSTANT_MACROS
    #define __STDC_LIMIT_MACROS
    #include <libavformat/avformat.h>
}

int task_execute(AVCodecContext* s, std::function<int(void* arg, int arg_size, int jobnr, int threadnr)>&& func, void* arg, int* ret, int count, int size)
{   
    tbb::atomic<int> counter;
    counter = 0;

    // Execute s->thread_count number of tasks in parallel.
    tbb::parallel_for(0, s->thread_count, 1, [&](int threadnr) 
    {
        while(true)
        {
            int jobnr = counter++;
            if(jobnr >= count)
                break;

            int r = func(arg, size, jobnr, threadnr);
            if (ret)
                ret[jobnr] = r;
        }
    });

    return 0;
}

int thread_execute(AVCodecContext* s, int (*func)(AVCodecContext *c2, void *arg2), void* arg, int* ret, int count, int size)
{
    return task_execute(s, [&](void* arg, int arg_size, int jobnr, int threadnr) -> int
    {
        return func(s, reinterpret_cast<uint8_t*>(arg) + jobnr*size);
    }, arg, ret, count, size);
}

int thread_execute2(AVCodecContext* s, int (*func)(AVCodecContext* c2, void* arg2, int, int), void* arg, int* ret, int count)
{
    return task_execute(s, [&](void* arg, int arg_size, int jobnr, int threadnr) -> int
    {
        return func(s, arg, jobnr, threadnr);
    }, arg, ret, count, 0);
}

void thread_init(AVCodecContext* s)
{
    static const size_t MAX_THREADS = 16; // See mpegvideo.h
    static int dummy_opaque;

    s->active_thread_type = FF_THREAD_SLICE;
    s->thread_opaque      = &dummy_opaque; 
    s->execute            = thread_execute;
    s->execute2           = thread_execute2;
    s->thread_count       = MAX_THREADS; // We are using a task-scheduler, so use as many "threads/tasks" as possible.
}

void thread_free(AVCodecContext* s)
{
    s->thread_opaque = nullptr;
}

int tbb_avcodec_open(AVCodecContext* avctx, AVCodec* codec)
{
    avctx->thread_count = 1;
    if((codec->capabilities & CODEC_CAP_SLICE_THREADS) && (avctx->thread_type & FF_THREAD_SLICE))
        thread_init(avctx);
// ff_thread_init will not be executed since thread_opaque != nullptr || thread_count == 1.
    return avcodec_open(avctx, codec); 
}

int tbb_avcodec_close(AVCodecContext* avctx)
{
    thread_free(avctx);
    // ff_thread_free will not be executed since thread_opaque == nullptr.
    return avcodec_close(avctx); 
}
于 2011-05-19T13:35:35.720 に答える
2

SO の誰にでも興味を持ってもらえるように、TBB フォーラムでの私の回答をここに再投稿します。

上記の回答のコードは私には良さそうです。ネイティブ スレッドを念頭に置いて設計されたコンテキストで TBB を使用する賢い方法です。というか、もっとTBBっぽいものにできないかな。時間と希望があれば試してみることができるいくつかのアイデアがあります。

次の 2 つの項目は、スレッド数を制御する必要がある場合に役立ちます。

  • thread_init で、ヒープ割り当てtbb::task_scheduler_init(TSI) オブジェクトを作成し、必要な数のスレッドで初期化します (MAX_THREADS は必要ありません)。s->thread_opaque可能/許可されている場合は、このオブジェクトのアドレスを保持します。AVCodecContext*そうでない場合、考えられる解決策は、対応する のアドレスにマップするグローバル マップですtask_scheduler_init
  • これに対応して、thread_free で、TSI オブジェクトを取得して削除します。

上記とは別に、別の潜在的な変更は、 を呼び出す方法tbb::parallel_forです。単に十分な数のスレッドを作成するために使用するのではなく、以下のように直接的な目的で使用することはできませんか?

int task_execute(AVCodecContext* s,
                 std::function<int(void*, int, int, int)>&& f,
                 void* arg, int* ret, int count, int size)   
{      
    tbb::atomic<int> counter;   
    counter = 0;   

    // Execute 'count' number of tasks in parallel.   
    tbb::parallel_for(tbb::blocked_range<int>(0, count, 2),
                      [&](const tbb::blocked_range<int> &r)    
    {   
        int threadnr = counter++;   
        for(int jobnr=r.begin(); jobnr!=r.end(); ++jobnr)
        {   
            int r = func(arg, size, jobnr, threadnr);   
            if (ret)   
                ret[jobnr] = r;   
        }
        --counter;
    });   

    return 0;   
}

これは、 がcountよりも大幅に大きい場合にパフォーマンスが向上する可能性があります。これはthread_count、a) 並列スラックが多いほど、TBB がより効率的に動作することを意味し (これは明らかです)、b) 集中型アトミック カウンターのオーバーヘッドがより多くの反復に分散されるためです。に 2 の粒度を選択したことに注意してくださいblocked_rangecount>=2*thread_countこれは、カウンタがループ本体内でインクリメントおよびデクリメントされるためです。そのため、バリアントを「一致させる」には、タスクごとに少なくとも 2 回の反復 (および対応する) が必要です。

于 2011-05-24T11:28:48.720 に答える