web-dev-qa-db-ja.com

ピクセルフォーマットAV_PIX_FMT_CUDAのFFmpegAVFrameをピクセルフォーマットAV_PIX_FMT_RGBの新しいAVFrameに変換するにはどうすればよいですか?

FFmpeg3.2を使用してH264 RTPストリームを受信する単純なC++アプリケーションがあります。CPUを節約するために、コーデックh264_cuvidでデコード部分を実行しています。FFmpeg3.2は次のようにコンパイルされています。 hwアクセラレーションが有効になっています。実際、次のコマンドを実行すると、次のようになります。

ffmpeg -hwaccels

私は得る

cuvid

これは、私のFFmpegセットアップで、NVIDIAカードと「話す」ことがすべてOKであることを意味します。関数avcodec_decode_video2が提供するフレームは、ピクセル形式AV_PIX_FMT_CUDAです。これらのフレームをAV_PIX_FMT_RGBで新しいフレームに変換する必要があります。残念ながら、ピクセル形式sws_getContextがサポートされていないため、よく知られている関数sws_scaleおよびAV_PIX_FMT_CUDAを使用して変換を行うことはできません。 swscaleを試してみると、次のエラーが発生します。

「cudaは入力ピクセルフォーマットとしてサポートされていません」

FFmpeg AVFrameAV_PIX_FMT_CUDAからAV_PIX_FMT_RGBに変換する方法を知っていますか? (コードの一部をいただければ幸いです)

8
costef

私はffmpegの専門家ではありませんが、同様の問題があり、なんとか解決できました。 cuvid(mjpeg_cuvidデコーダー)からAV_PIX_FMT_NV12を取得していて、cuda処理用にAV_PIX_FMT_CUDAが必要でした。

フレームをデコードする直前にピクセルフォーマットを設定するとうまくいくことがわかりました。

    pCodecCtx->pix_fmt = AV_PIX_FMT_CUDA; // change format here
    avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
    // do something with pFrame->data[0] (Y) and pFrame->data[1] (UV)

Pix_fmtsを使用して、デコーダーでサポートされているピクセル形式を確認できます。

    AVCodec *pCodec = avcodec_find_decoder_by_name("mjpeg_cuvid");
    for (int i = 0; pCodec->pix_fmts[i] != AV_PIX_FMT_NONE; i++)
            std::cout << pCodec->pix_fmts[i] << std::endl;

これを行うためのより良い方法があると確信していますが、次に this listを使用して、整数のピクセル形式IDを人間が読める形式のピクセル形式にマップしました。

それが機能しない場合は、cudaMemcpyを実行して、ピクセルをデバイスからホストに転送できます。

    cudaMemcpy(pLocalBuf pFrame->data[0], size, cudaMemcpyDeviceToHost);

YUVからRGB/RGBAへの変換はさまざまな方法で実行できます。 この例 libavdeviceAPIを使用して実行します。

2
Jean B.

これは、最新のFFMPeg4.1バージョンでのハードウェアデコードについての私の理解です。以下は、ソースコードを調べた後の私の結論です。

まず、hw_decodeの例からインスピレーションを得ることをお勧めします。

https://github.com/FFmpeg/FFmpeg/blob/release/4.1/doc/examples/hw_decode.c

新しいAPIでは、 avcodec_send_packet() を使用してエンコーダーにパケットを送信するときに、 avcodec_receive_frame() を使用してデコードされたフレームを取得します。

AVFrameには2つの異なる種類があります:software1つは「CPU」メモリ(別名RAM)に格納され、ハードウェアグラフィックカードのメモリに保存されているもの。

ハードウェアからAVFrameを取得する

ハードウェアフレームを取得して、読み取り可能で変換可能な(swscalerを使用)にするには、AVFrameav_hwframe_transfer_data() を使用してグラフィックカードからデータを取得する必要があります。次に、取得したフレームのピクセル形式を確認します。nVidiaデコードを使用する場合は通常NV12形式です。

// According to the API, if the format of the AVFrame is set before calling 
// av_hwframe_transfer_data(), the graphic card will try to automatically convert
// to the desired format. (with some limitation, see below)
m_swFrame->format = AV_PIX_FMT_NV12;

// retrieve data from GPU to CPU
err = av_hwframe_transfer_data(
     m_swFrame, // The frame that will contain the usable data.
     m_decodedFrame, // Frame returned by avcodec_receive_frame()
     0);

const char* gpu_pixfmt = av_get_pix_fmt_name((AVPixelFormat)m_decodedFrame->format);
const char* cpu_pixfmt = av_get_pix_fmt_name((AVPixelFormat)m_swFrame->format);

サポートされている「ソフトウェア」ピクセル形式のリスト

ピクセルフォーマットを選択したい場合は、ここで注意してください。すべてのAVPixelFormatがサポートされているわけではありません。 AVHWFramesConstraints ここにあなたの友達がいます:

AVHWDeviceType type = AV_HWDEVICE_TYPE_CUDA;
int err = av_hwdevice_ctx_create(&hwDeviceCtx, type, nullptr, nullptr, 0);
if (err < 0) {
    // Err
}

AVHWFramesConstraints* hw_frames_const = av_hwdevice_get_hwframe_constraints(hwDeviceCtx, nullptr);
if (hw_frames_const == nullptr) {
    // Err
}

// Check if we can convert the pixel format to a readable format.
AVPixelFormat found = AV_PIX_FMT_NONE;
for (AVPixelFormat* p = hw_frames_const->valid_sw_formats; 
    *p != AV_PIX_FMT_NONE; p++)
{
    // Check if we can convert to the desired format.
    if (sws_isSupportedInput(*p))
    {
        // Ok! This format can be used with swscale!
        found = *p;
        break;
    }
}

// Don't forget to free the constraint object.
av_hwframe_constraints_free(&hw_frames_const);

// Attach your hw device to your codec context if you want to use hw decoding.
// Check AVCodecContext.hw_device_ctx!

最後に、より迅速な方法はおそらく av_hwframe_transfer_get_formats() 関数ですが、少なくとも1つのフレームをデコードする必要があります。

これがお役に立てば幸いです。

2
lp35

これを行うには、vf_scale_nppを使用する必要があります。必要に応じて、nppscale_deinterleaveまたはnppscale_resizeのいずれかを使用できます。

どちらにも同じ入力パラメータがあります。 AVFilterContextnppscale_initで初期化する必要があります。 NPPScaleStageContext これは入出力ピクセル形式と2つを取ります AVFrame sはもちろん、入力フレームと出力フレームです。

詳細については、 npplib\nppscale ffmpeg3.1以降のCUDA加速形式の変換とスケーリングを行う定義を参照してください。

とにかく、私はこの目的のために NVIDIA Video Codec SDK を直接使用することをお勧めします。

1
HMD