Несоответствие времени выполнения DXVA аппаратное декодирование видео

В настоящее время я работаю над проектом, в котором используется DXVA API и инфраструктура FFmpeg для реализации аппаратного ускорения декодирования файлов видеопотока H264.

Я провел некоторые исследования по декодированию графических процессоров и построил свой код на основе реализации аппаратного ускорения в VLC. Насколько я понимаю, использование DXVA в FFmpeg включает инициализацию DirectXVideoDecoder и реализацию нескольких функций обратного вызова в AVCodecContext. Процесс декодирования выполняется с помощью функции FFmpeg avcodec_decode_video2(), и каждый кадр анализируется с помощью av_read_frame (). Декодированный кадр сохраняется в графической памяти и отображается с использованием Direct3D.

Я попытался рассчитать каждый процесс с помощью функции :GetTickCount() и заметил, что время выполнения программы для видео с 1550 кадрами составляет 35000 мс, причем функция отображения занимает 90% времени, а функция декодирования - 6% времени.

Однако, когда я попытался закомментировать процесс отображения и выполнить код, декодирующий только каждый кадр, общее время декодирования неожиданно увеличилось до 25 000 мс для того же видео, что заняло 94% от общего времени. Вот код для функции декодирования:

//record start time 
DWORD start_time = ::GetTickCount();
//media file to be loaded
const char *filename = "123.mkv";
//time recording parameters
unsigned frame_read_time_total = 0;
unsigned decode_frame_time_total = 0;
unsigned display_time_total = 0;
unsigned setup_time_total = 0;

/*********************Setup and Initialization Code*******************************/
unsigned setup_time_start = ::GetTickCount();
av_register_all();
av_log_set_level(AV_LOG_DEBUG);
int res;
AVFormatContext *file = NULL;
res = avformat_open_input(&file, filename, NULL, NULL);//´ò¿ªÎļþ
if (res < 0) {
    printf("error %x in avformat_open_input\n", res);
    return 1;
}
res = avformat_find_stream_info(file, NULL);//È¡³öÁ÷ÐÅÏ¢
if (res < 0)
{
    printf("error %x in avformat_find_stream_info\n", res);
    return 1;
}
av_dump_format(file, 0, filename, 0);//ÁгöÊäÈëÎļþµÄÏà¹ØÁ÷ÐÅÏ¢
int i;
int videoindex = -1;
int audioindex = -1;
for (i = 0; i < file->nb_streams; i++){
    if (file->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
        videoindex = i;
    }
    if (file->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
        audioindex = i;
    }
}
if (videoindex == -1){
    av_log(NULL, AV_LOG_DEBUG, "can't find video stream\n");
    return 0;

}
AVCodec *codec = avcodec_find_decoder(file->streams[videoindex]->codec->codec_id);//¸ù¾ÝÁ÷ÐÅÏ¢ÕÒµ½½âÂëÆ÷
if (!codec){
    printf("decoder not found\n");
    return 1;
}
AVCodecContext *codecctx = file->streams[videoindex]->codec;
screen_width = codecctx->width;
screen_height = codecctx->height;
//Initialize Win API Window 
WNDCLASSEX window;
ZeroMemory(&window, sizeof(window));
window.cbSize = sizeof(window);
window.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
window.lpfnWndProc = (WNDPROC)WindowProcess;
window.lpszClassName = L"D3D";
window.style = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx(&window);
HWND hwnd_temp = CreateWindow(L"D3D", L"Player", WS_OVERLAPPEDWINDOW,
    0, 0, screen_width, screen_height, NULL, NULL, NULL, NULL);
if (hwnd_temp == NULL){
    av_log(NULL, AV_LOG_ERROR, "Error: Cannot create window\n");
    system("pause");
}
hwnd.push_back(hwnd_temp);
vlc_va_dxva2_t *dxva = vlc_va_NewDxva2(codecctx->codec_id);
if (NULL == dxva){
    return 0;
}
res = Setup(dxva, &codecctx->hwaccel_context, &codecctx->pix_fmt, screen_width, screen_height);
if (res < 0) {
    printf("error DXVA setup\n", res);
    return 1;
}
//Assign callback function 
codecctx->opaque = dxva;
codecctx->get_format = ffmpeg_GetFormat;
codecctx->get_buffer = ffmpeg_GetFrameBuf;
codecctx->reget_buffer = ffmpeg_ReGetFrameBuf;
codecctx->release_buffer = ffmpeg_ReleaseFrameBuf;
codecctx->thread_count = 1;
res = avcodec_open2(codecctx, codec, NULL);
if (res < 0) {
    printf("error %x in avcodec_open2\n", res);
    return 1;
}
//Initialize Packet
AVPacket pkt = { 0 };
AVFrame *picture = avcodec_alloc_frame();
DWORD wait_for_keyframe = 60;
//initialize frame count
int count = 0;
ShowWindow(hwnd.at(0), SW_SHOWNORMAL);
UpdateWindow(hwnd.at(0));
RECT screen_size;
screen_size.top = 0;
screen_size.bottom = screen_height;
screen_size.left = 0;
screen_size.right = screen_width;

unsigned setup_time_end = ::GetTickCount();
setup_time_total = setup_time_end - setup_time_start;

MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(msg.message!=WM_QUIT)
{
    if (PeekMessage(&msg, NULL, 0,0, PM_REMOVE)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        continue;
    }
        int read_status;
        unsigned read_frame_start = ::GetTickCount();
        read_status = av_read_frame(file, &pkt);
        if (read_status < 0)
        {
            av_free_packet(&pkt);
            goto done;
        }

        unsigned read_frame_end = ::GetTickCount();
        frame_read_time_total += (read_frame_end - read_frame_start);
        int got_picture = 0;
        unsigned decode_start = ::GetTickCount();
        int bytes_used = avcodec_decode_video2(codecctx, picture, &got_picture, &pkt);
        unsigned decode_end = ::GetTickCount();
        decode_frame_time_total += (decode_end - decode_start);
        if (got_picture)
        {
            count++;
            unsigned display_start = ::GetTickCount();
            //display_frame((vlc_va_dxva2_t *)codecctx->opaque, picture, screen_size,0);
            unsigned display_end = ::GetTickCount();
            display_time_total += (display_end - display_start);
        }
        av_free_packet(&pkt);
}
done:
UnregisterClass(L"D3D",0);
printf("Frames = %d\n",count);
unsigned stop_time = ::GetTickCount();
unsigned total_time = stop_time - start_time;
printf("total frame = %d\n", count);
printf("time cost = %d\n", total_time);
printf("total setup time = %d, %f %% total execution time\n", setup_time_total,(float) setup_time_total / total_time * 100);
printf("total frame read time = %d, %f %% total execution time\n", frame_read_time_total, (float)frame_read_time_total / total_time*100);
printf("total frame decode time = %d, %f %% total execution time\n", decode_frame_time_total, (float)decode_frame_time_total / total_time*100);
printf("total display time = %d, %f %% of total execution time\n", display_time_total, (float)display_time_total / total_time*100);

av_free(picture);
av_close_input_file(file);
system("pause");
return 0;

Что может быть причиной этого странного поведения? Я предполагаю, что это может быть неправильное использование:GetTickCount() или это может быть связано с процессом аппаратного ускорения декодирования DXVA. Простите за длинный пост. Любой вклад и предложение приветствуется. Заранее спасибо.

1 ответ

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

Если процесс отображения очень длинный, декодер декодирует кадры, в то время как процесс отображения выполняется. Поэтому, когда вы запрашиваете рендеринг, некоторые кадры уже декодируются, и это быстро.

Если вы избегаете процесса отображения, процесс декодирования занимает все процессорное время. Обычно процесс отображения использует какую-то временную метку, которая позволяет достаточно времени процессу декодирования.

PS: из того, что я знаю о Ffmpeg и Dxva2, вам также нужно предоставить текстуру directx.

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