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 AVFrame
をAV_PIX_FMT_CUDA
からAV_PIX_FMT_RGB
に変換する方法を知っていますか? (コードの一部をいただければ幸いです)
私は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を使用して実行します。
これは、最新の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)に格納され、ハードウェアグラフィックカードのメモリに保存されているもの。
ハードウェアフレームを取得して、読み取り可能で変換可能な(swscalerを使用)にするには、AVFrame
、 av_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つのフレームをデコードする必要があります。
これがお役に立てば幸いです。
これを行うには、vf_scale_npp
を使用する必要があります。必要に応じて、nppscale_deinterleave
またはnppscale_resize
のいずれかを使用できます。
どちらにも同じ入力パラメータがあります。 AVFilterContextnppscale_init
で初期化する必要があります。 NPPScaleStageContext これは入出力ピクセル形式と2つを取ります AVFrame sはもちろん、入力フレームと出力フレームです。
詳細については、 npplib\nppscale ffmpeg3.1以降のCUDA加速形式の変換とスケーリングを行う定義を参照してください。
とにかく、私はこの目的のために NVIDIA Video Codec SDK を直接使用することをお勧めします。