Как я могу преобразовать FFmpeg AVFrame с пиксельным форматом AV_PIX_FMT_CUDA в новый AVFrame с пиксельным форматом AV_PIX_FMT_RGB

У меня есть простое приложение C++, которое использует FFmpeg 3.2 для получения потока RTP H264. Чтобы сохранить процессор, я делаю часть декодирования с помощью кодека h264_cuvid. Мой FFmpeg 3.2 скомпилирован с ускорением hw. На самом деле, если я сделаю команду:

ffmpeg -hwaccels

я получил

cuvid

Это означает, что в моей настройке FFmpeg все в порядке, чтобы "говорить" с моей картой NVIDIA. Кадры, которые функция 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? (кусочки кода будут очень признательны)

2 ответа

Это мое понимание аппаратного декодирования на последней версии FFMPeg 4.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: программный, который хранится в памяти "CPU" (или RAM), и аппаратный, который хранится в памяти графической карты.

Получение AVFrame с аппаратного обеспечения

Извлечь аппаратный фрейм и превратить его в читаемый, конвертируемый (с помощью swscaler) AVFrame, av_hwframe_transfer_data () необходимо использовать для извлечения данных из графической карты. Затем посмотрите на пиксельный формат полученного кадра, обычно это формат NV12 при использовании декодирования nVidia.

// 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(), но вам нужно декодировать хотя бы один кадр.

Надеюсь, это поможет!

Я не эксперт ffmpeg, но у меня была похожая проблема, и мне удалось ее решить. Я получал AV_PIX_FMT_NV12 от cuvid (декодер mjpeg_cuvid) и хотел 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;

Я уверен, что есть лучший способ сделать это, но затем я использовал этот список для сопоставления идентификаторов целочисленных пиксельных форматов с читаемыми человеком форматами пикселей.

Если это не сработает, вы можете сделать cudaMemcpy для переноса ваших пикселей с устройства на хост:

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

Преобразование из YUV в RGB/RGBA может быть сделано многими способами. В этом примере это делается с помощью libavdevice API.

Вы должны использовать vf_scale_npp сделать это. Вы можете использовать либо nppscale_deinterleave или же nppscale_resize зависит от ваших потребностей.

Оба имеют одинаковые входные параметры, которые являются AVFilterContext, которые должны быть инициализированы с nppscale_init, NPPScaleStageContext, который принимает ваш пиксельный формат ввода / вывода, и два AVFrame, которые, конечно, являются вашими входными и выходными кадрами.

Для получения дополнительной информации вы можете увидеть определение npplib\nppscale, которое будет выполнять ускоренное преобразование и масштабирование формата CUDA начиная с ffmpeg 3.1.

В любом случае, я рекомендую использовать NVIDIA Video Codec SDK напрямую для этой цели.

Другие вопросы по тегам