Quellcode durchsuchen

Added support for 0-copy decode and display using VAAPI and EGL

Sam Lantinga vor 1 Jahr
Ursprung
Commit
1bf913b29a
2 geänderte Dateien mit 440 neuen und 179 gelöschten Zeilen
  1. 7 1
      test/CMakeLists.txt
  2. 433 178
      test/testffmpeg.c

+ 7 - 1
test/CMakeLists.txt

@@ -182,11 +182,17 @@ if(HAVE_LIBUDEV_H)
     add_definitions(-DHAVE_LIBUDEV_H)
 endif()
 
-set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE)
 include("${SDL3_SOURCE_DIR}/cmake/FindFFmpeg.cmake")
 if(FFmpeg_FOUND AND FFmpeg_AVCODEC_VERSION VERSION_GREATER_EQUAL "60")
     add_sdl_test_executable(testffmpeg NO_C90 SOURCES testffmpeg.c ${icon_bmp_header})
     target_link_libraries(testffmpeg PRIVATE ${FFMPEG_LIBRARIES})
+    if(HAVE_OPENGLES_V2)
+        #message(STATUS "Enabling EGL support in testffmpeg")
+        target_compile_definitions(testffmpeg PRIVATE HAVE_EGL)
+        if(TARGET OpenGL::EGL)
+            target_link_libraries(testffmpeg PRIVATE OpenGL::EGL)
+        endif()
+    endif()
 else()
     message(STATUS "Can't find ffmpeg 6.0 or newer, skipping testffmpeg")
 endif()

+ 433 - 178
test/testffmpeg.c

@@ -20,10 +20,27 @@
 #include <SDL3/SDL.h>
 #include <SDL3/SDL_main.h>
 
+#ifdef HAVE_EGL
+#include <SDL3/SDL_opengl.h>
+#include <SDL3/SDL_opengles2.h>
+#include <SDL3/SDL_egl.h>
+
+#ifndef fourcc_code
+#define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
+#endif
+#ifndef DRM_FORMAT_R8
+#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ')
+#endif
+#ifndef DRM_FORMAT_GR88
+#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8')
+#endif
+#endif
+
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 #include <libavutil/avutil.h>
-#include <libswscale/swscale.h>
+#include <libavutil/hwcontext_drm.h>
+#include <libavutil/pixdesc.h>
 
 #include "icon.h"
 
@@ -40,13 +57,68 @@ static SDL_Window *window;
 static SDL_Renderer *renderer;
 static SDL_AudioStream *audio;
 static SDL_Texture *video_texture;
-static SDL_PixelFormatEnum video_format;
-static int video_width;
-static int video_height;
 static Uint64 video_start;
-static struct SwsContext *video_conversion_context;
+static SDL_bool has_eglCreateImage;
+#ifdef HAVE_EGL
+static SDL_bool has_EGL_EXT_image_dma_buf_import;
+static PFNGLACTIVETEXTUREARBPROC glActiveTextureARBFunc;
+static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOESFunc;
+#endif
 static int done;
 
+static SDL_bool CreateWindow(Uint32 window_flags, SDL_bool useEGL)
+{
+    SDL_RendererInfo info;
+
+    if (useEGL) {
+        SDL_SetHint( SDL_HINT_VIDEO_FORCE_EGL, "1" );
+        SDL_SetHint( SDL_HINT_RENDER_DRIVER, "opengles2" );
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+    } else {
+        SDL_SetHint( SDL_HINT_VIDEO_FORCE_EGL, "0" );
+        SDL_SetHint( SDL_HINT_RENDER_DRIVER, NULL );
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+    }
+    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
+    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
+    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
+
+    if (SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN, &window, &renderer) < 0) {
+        return SDL_FALSE;
+    }
+
+    if (SDL_GetRendererInfo(renderer, &info) == 0) {
+        SDL_Log("Created renderer %s\n", info.name);
+    }
+
+#ifdef HAVE_EGL
+    if (useEGL) {
+        const char *extensions = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS);
+        if (SDL_strstr(extensions, "EGL_EXT_image_dma_buf_import") != NULL) {
+            has_EGL_EXT_image_dma_buf_import = SDL_TRUE;
+        }
+
+        if (SDL_GL_ExtensionSupported("GL_OES_EGL_image")) {
+            glEGLImageTargetTexture2DOESFunc = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+        }
+
+        glActiveTextureARBFunc = (PFNGLACTIVETEXTUREARBPROC)SDL_GL_GetProcAddress("glActiveTextureARB");
+
+        if (has_EGL_EXT_image_dma_buf_import &&
+            glEGLImageTargetTexture2DOESFunc &&
+            glActiveTextureARBFunc) {
+            has_eglCreateImage = SDL_TRUE;
+        }
+    }
+#endif /* HAVE_EGL */
+
+    return SDL_TRUE;
+}
+
 static SDL_Texture *CreateTexture(SDL_Renderer *r, unsigned char *data, unsigned int len, int *w, int *h) {
     SDL_Texture *texture = NULL;
     SDL_Surface *surface;
@@ -98,33 +170,140 @@ static void MoveSprite(void)
     }
 }
 
-static AVCodecContext *OpenStream(AVFormatContext *ic, int stream)
+static Uint32 GetTextureFormat(enum AVPixelFormat format)
+{
+    switch (format) {
+    case AV_PIX_FMT_RGB8:
+        return SDL_PIXELFORMAT_RGB332;
+    case AV_PIX_FMT_RGB444:
+        return SDL_PIXELFORMAT_RGB444;
+    case AV_PIX_FMT_RGB555:
+        return SDL_PIXELFORMAT_RGB555;
+    case AV_PIX_FMT_BGR555:
+        return SDL_PIXELFORMAT_BGR555;
+    case AV_PIX_FMT_RGB565:
+        return SDL_PIXELFORMAT_RGB565;
+    case AV_PIX_FMT_BGR565:
+        return SDL_PIXELFORMAT_BGR565;
+    case AV_PIX_FMT_RGB24:
+        return SDL_PIXELFORMAT_RGB24;
+    case AV_PIX_FMT_BGR24:
+        return SDL_PIXELFORMAT_BGR24;
+    case AV_PIX_FMT_0RGB32:
+        return SDL_PIXELFORMAT_XRGB8888;
+    case AV_PIX_FMT_0BGR32:
+        return SDL_PIXELFORMAT_XBGR8888;
+    case AV_PIX_FMT_NE(RGB0, 0BGR):
+        return SDL_PIXELFORMAT_RGBX8888;
+    case AV_PIX_FMT_NE(BGR0, 0RGB):
+        return SDL_PIXELFORMAT_BGRX8888;
+    case AV_PIX_FMT_RGB32:
+        return SDL_PIXELFORMAT_ARGB8888;
+    case AV_PIX_FMT_RGB32_1:
+        return SDL_PIXELFORMAT_RGBA8888;
+    case AV_PIX_FMT_BGR32:
+        return SDL_PIXELFORMAT_ABGR8888;
+    case AV_PIX_FMT_BGR32_1:
+        return SDL_PIXELFORMAT_BGRA8888;
+    case AV_PIX_FMT_YUV420P:
+        return SDL_PIXELFORMAT_IYUV;
+    case AV_PIX_FMT_YUYV422:
+        return SDL_PIXELFORMAT_YUY2;
+    case AV_PIX_FMT_UYVY422:
+        return SDL_PIXELFORMAT_UYVY;
+    default:
+        return SDL_PIXELFORMAT_UNKNOWN;
+    }
+}
+
+static SDL_bool SupportedPixelFormat(enum AVPixelFormat format)
+{
+    if (has_eglCreateImage &&
+        (format == AV_PIX_FMT_VAAPI || format == AV_PIX_FMT_DRM_PRIME)) {
+        return SDL_TRUE;
+    }
+
+    if (GetTextureFormat(format) != SDL_PIXELFORMAT_UNKNOWN) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
+static enum AVPixelFormat GetPixelFormat(AVCodecContext *s, const enum AVPixelFormat *pix_fmts)
+{
+    const enum AVPixelFormat *p;
+
+    for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
+        if (SupportedPixelFormat(*p)) {
+            /* We support this format */
+            break;
+        }
+    }
+
+    if (*p == AV_PIX_FMT_NONE) {
+        SDL_Log("Couldn't find a supported pixel format:\n");
+        for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
+            SDL_Log("    %s\n", av_get_pix_fmt_name(*p));
+        }
+    }
+
+    return *p;
+}
+
+static AVCodecContext *OpenVideoStream(AVFormatContext *ic, int stream, const AVCodec *codec)
 {
+    AVStream *st = ic->streams[stream];
+    AVCodecParameters *codecpar = st->codecpar;
     AVCodecContext *context;
-    const AVCodec *codec;
+    const AVCodecHWConfig *config;
+    enum AVHWDeviceType type;
+    int i;
     int result;
 
+    SDL_Log("Video stream: %s %dx%d\n", avcodec_get_name(codec->id), codecpar->width, codecpar->height);
+
     context = avcodec_alloc_context3(NULL);
     if (!context) {
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_alloc_context3 failed");
         return NULL;
     }
 
-    if (avcodec_parameters_to_context(context, ic->streams[stream]->codecpar) < 0) {
-        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_parameters_to_context failed");
+    result = avcodec_parameters_to_context(context, ic->streams[stream]->codecpar);
+    if (result < 0) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_parameters_to_context failed: %s\n", av_err2str(result));
         avcodec_free_context(&context);
         return NULL;
     }
     context->pkt_timebase = ic->streams[stream]->time_base;
 
-    codec = avcodec_find_decoder(context->codec_id);
-    if (!codec) {
-        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find codec %s", avcodec_get_name(context->codec_id));
-        avcodec_free_context(&context);
-        return NULL;
+    /* Look for supported hardware accelerated configurations */
+    i = 0;
+    while (!context->hw_device_ctx &&
+           (config = avcodec_get_hw_config(codec, i++)) != NULL) {
+        if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) ||
+            !SupportedPixelFormat(config->pix_fmt)) {
+            continue;
+        }
+
+        type = AV_HWDEVICE_TYPE_NONE;
+        while (!context->hw_device_ctx && 
+               (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
+            if (type != config->device_type) {
+                continue;
+            }
+
+            result = av_hwdevice_ctx_create(&context->hw_device_ctx, type, NULL, NULL, 0);
+            if (result < 0) {
+                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create hardware device context: %s", av_err2str(result));
+            } else {
+                SDL_Log("Using %s hardware acceleration with pixel format %s\n", av_hwdevice_get_type_name(config->device_type), av_get_pix_fmt_name(config->pix_fmt));
+            }
+        }
     }
 
-    context->codec_id = codec->id;
+    /* Allow supported hardware accelerated pixel formats */
+    context->get_format = GetPixelFormat;
+
     result = avcodec_open2(context, codec, NULL);
     if (result < 0) {
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open codec %s: %s", avcodec_get_name(context->codec_id), av_err2str(result));
@@ -132,25 +311,232 @@ static AVCodecContext *OpenStream(AVFormatContext *ic, int stream)
         return NULL;
     }
 
+    SDL_SetWindowSize(window, codecpar->width, codecpar->height);
+
     return context;
 }
 
-static AVCodecContext *OpenAudioStream(AVFormatContext *ic, int stream)
+static void SetYUVConversionMode(AVFrame *frame)
 {
-    AVStream *st = ic->streams[stream];
-    AVCodecParameters *codecpar = st->codecpar;
-    AVCodecContext *context = OpenStream(ic, stream);
+    SDL_YUV_CONVERSION_MODE mode = SDL_YUV_CONVERSION_AUTOMATIC;
+    if (frame && (frame->format == AV_PIX_FMT_YUV420P || frame->format == AV_PIX_FMT_YUYV422 || frame->format == AV_PIX_FMT_UYVY422)) {
+        if (frame->color_range == AVCOL_RANGE_JPEG)
+            mode = SDL_YUV_CONVERSION_JPEG;
+        else if (frame->colorspace == AVCOL_SPC_BT709)
+            mode = SDL_YUV_CONVERSION_BT709;
+        else if (frame->colorspace == AVCOL_SPC_BT470BG || frame->colorspace == AVCOL_SPC_SMPTE170M)
+            mode = SDL_YUV_CONVERSION_BT601;
+    }
+    SDL_SetYUVConversionMode(mode); /* FIXME: no support for linear transfer */
+}
 
-    if (context) {
-        SDL_Log("Audio stream: %s %d channels, %d Hz\n", avcodec_get_name(context->codec_id), codecpar->ch_layout.nb_channels, codecpar->sample_rate);
+static SDL_bool GetTextureForMemoryFrame(AVFrame *frame, SDL_Texture **texture)
+{
+    int texture_width = 0, texture_height = 0;
+    Uint32 texture_format = SDL_PIXELFORMAT_UNKNOWN;
+    Uint32 frame_format = GetTextureFormat(frame->format);
+
+    if (*texture) {
+        SDL_QueryTexture(*texture, &texture_format, NULL, &texture_width, &texture_height);
+    }
+    if (!*texture || frame_format != texture_format || frame->width != texture_width || frame->height != texture_height) {
+        if (*texture) {
+            SDL_DestroyTexture(*texture);
+        }
+
+        *texture = SDL_CreateTexture(renderer, frame_format, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height);
+        if (!*texture) {
+            return SDL_FALSE;
+        }
+    }
 
-        SDL_AudioSpec spec = { SDL_AUDIO_F32, codecpar->ch_layout.nb_channels, codecpar->sample_rate };
-        audio = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, NULL, NULL);
-        if (audio) {
-            SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(audio));
+    switch (frame_format) {
+    case SDL_PIXELFORMAT_IYUV:
+        if (frame->linesize[0] > 0 && frame->linesize[1] > 0 && frame->linesize[2] > 0) {
+            SDL_UpdateYUVTexture(*texture, NULL, frame->data[0], frame->linesize[0],
+                                                   frame->data[1], frame->linesize[1],
+                                                   frame->data[2], frame->linesize[2]);
+        } else if (frame->linesize[0] < 0 && frame->linesize[1] < 0 && frame->linesize[2] < 0) {
+            SDL_UpdateYUVTexture(*texture, NULL, frame->data[0] + frame->linesize[0] * (frame->height                    - 1), -frame->linesize[0],
+                                                   frame->data[1] + frame->linesize[1] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[1],
+                                                   frame->data[2] + frame->linesize[2] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[2]);
+        }
+        SetYUVConversionMode(frame);
+        break;
+    default:
+        if (frame->linesize[0] < 0) {
+            SDL_UpdateTexture(*texture, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0]);
+        } else {
+            SDL_UpdateTexture(*texture, NULL, frame->data[0], frame->linesize[0]);
+        }
+        break;
+    }
+    return SDL_TRUE;
+}
+
+static SDL_bool GetTextureForDRMFrame(AVFrame *frame, SDL_Texture **texture)
+{
+#ifdef HAVE_EGL
+    const AVDRMFrameDescriptor *desc = (const AVDRMFrameDescriptor *)frame->data[0];
+    int i, j, image_index, num_planes;
+    EGLDisplay display = eglGetCurrentDisplay();
+
+    /* FIXME: Assuming NV12 data format */
+    num_planes = 0;
+    for (i = 0; i < desc->nb_layers; ++i) {
+        num_planes += desc->layers[i].nb_planes;
+    }
+    if (num_planes != 2) {
+        SDL_SetError("Expected NV12 frames with 2 planes, instead got %d planes", num_planes);
+        return SDL_FALSE;
+    }
+
+    if (*texture) {
+        /* Free the previous texture now that we're about to render a new one */
+        SDL_DestroyTexture(*texture);
+    } else {
+        /* First time set up for NV12 textures */
+        SDL_SetHint("SDL_RENDER_OPENGL_NV12_RG_SHADER", "1");
+
+        SetYUVConversionMode(frame);
+    }
+
+    *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_NV12, SDL_TEXTUREACCESS_STATIC, frame->width, frame->height);
+    if (!*texture) {
+        return SDL_FALSE;
+    }
+
+    /* Bind the texture for importing */
+    SDL_GL_BindTexture(*texture, NULL, NULL);
+
+    /* import the frame into OpenGL */
+    image_index = 0;
+    for (i = 0; i < desc->nb_layers; ++i) {
+        const AVDRMLayerDescriptor *layer = &desc->layers[i];
+        for (j = 0; j < layer->nb_planes; ++j) {
+            static const uint32_t formats[ 2 ] = { DRM_FORMAT_R8, DRM_FORMAT_GR88 };
+            const AVDRMPlaneDescriptor *plane = &layer->planes[j];
+            const AVDRMObjectDescriptor *object = &desc->objects[plane->object_index];
+            EGLAttrib img_attr[] = {
+                EGL_LINUX_DRM_FOURCC_EXT,      formats[i],
+                EGL_WIDTH,                     frame->width  / ( image_index + 1 ),  /* half size for chroma */
+                EGL_HEIGHT,                    frame->height / ( image_index + 1 ),
+                EGL_DMA_BUF_PLANE0_FD_EXT,     object->fd,
+                EGL_DMA_BUF_PLANE0_OFFSET_EXT, plane->offset,
+                EGL_DMA_BUF_PLANE0_PITCH_EXT,  plane->pitch,
+                EGL_NONE
+            };
+            EGLImage pImage = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
+
+            glActiveTextureARBFunc(GL_TEXTURE0_ARB + image_index);
+            glEGLImageTargetTexture2DOESFunc(GL_TEXTURE_2D, pImage);
+            ++image_index;
+        }
+    }
+
+    SDL_GL_UnbindTexture(*texture);
+
+    return SDL_TRUE;
+#else
+    return SDL_FALSE;
+#endif
+}
+
+static SDL_bool GetTextureForVAAPIFrame(AVFrame *frame, SDL_Texture **texture)
+{
+    AVFrame *drm_frame;
+    SDL_bool result = SDL_FALSE;
+
+    drm_frame = av_frame_alloc();
+    if (drm_frame) {
+        drm_frame->format = AV_PIX_FMT_DRM_PRIME;
+        if (av_hwframe_map(drm_frame, frame, 0) == 0) {
+            result = GetTextureForDRMFrame(drm_frame, texture);
         } else {
-            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s", SDL_GetError());
+            SDL_SetError("Couldn't map hardware frame");
         }
+        av_frame_free(&drm_frame);
+    } else {
+        SDL_OutOfMemory();
+    }
+    return result;
+}
+
+static SDL_bool GetTextureForFrame(AVFrame *frame, SDL_Texture **texture)
+{
+    switch (frame->format) {
+    case AV_PIX_FMT_VAAPI:
+        return GetTextureForVAAPIFrame(frame, texture);
+    case AV_PIX_FMT_DRM_PRIME:
+        return GetTextureForDRMFrame(frame, texture);
+    default:
+        return GetTextureForMemoryFrame(frame, texture);
+    }
+}
+
+static void HandleVideoFrame(AVFrame *frame, double pts)
+{
+    /* Update the video texture */
+    GetTextureForFrame(frame, &video_texture);
+
+    /* Quick and dirty PTS handling */
+    if (!video_start) {
+        video_start = SDL_GetTicks();
+    }
+    double now = (double)(SDL_GetTicks() - video_start) / 1000.0;
+    while (now < pts - 0.001) {
+        SDL_Delay(1);
+        now = (double)(SDL_GetTicks() - video_start) / 1000.0;
+    }
+
+    if (frame->linesize[0] < 0) {
+        SDL_RenderTextureRotated(renderer, video_texture, NULL, NULL, 0.0, NULL, SDL_FLIP_VERTICAL);
+    } else {
+        SDL_RenderTexture(renderer, video_texture, NULL, NULL);
+    }
+
+    /* Render any bouncing balls */
+    MoveSprite();
+
+    SDL_RenderPresent(renderer);
+}
+
+static AVCodecContext *OpenAudioStream(AVFormatContext *ic, int stream, const AVCodec *codec)
+{
+    AVStream *st = ic->streams[stream];
+    AVCodecParameters *codecpar = st->codecpar;
+    AVCodecContext *context;
+    int result;
+
+    SDL_Log("Audio stream: %s %d channels, %d Hz\n", avcodec_get_name(codec->id), codecpar->ch_layout.nb_channels, codecpar->sample_rate);
+
+    context = avcodec_alloc_context3(NULL);
+    if (!context) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_alloc_context3 failed\n");
+        return NULL;
+    }
+
+    result = avcodec_parameters_to_context(context, ic->streams[stream]->codecpar);
+    if (result < 0) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "avcodec_parameters_to_context failed: %s\n", av_err2str(result));
+        avcodec_free_context(&context);
+        return NULL;
+    }
+    context->pkt_timebase = ic->streams[stream]->time_base;
+
+    result = avcodec_open2(context, codec, NULL);
+    if (result < 0) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open codec %s: %s", avcodec_get_name(context->codec_id), av_err2str(result));
+        avcodec_free_context(&context);
+        return NULL;
+    }
+
+    SDL_AudioSpec spec = { SDL_AUDIO_F32, codecpar->ch_layout.nb_channels, codecpar->sample_rate };
+    audio = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, NULL, NULL);
+    if (audio) {
+        SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(audio));
+    } else {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s", SDL_GetError());
     }
     return context;
 }
@@ -228,159 +614,14 @@ static void HandleAudioFrame(AVFrame *frame)
     }
 }
 
-static AVCodecContext *OpenVideoStream(AVFormatContext *ic, int stream)
-{
-    AVStream *st = ic->streams[stream];
-    AVCodecParameters *codecpar = st->codecpar;
-    AVCodecContext *context = OpenStream(ic, stream);
-
-    if (context) {
-        SDL_Log("Video stream: %s %dx%d\n", avcodec_get_name(context->codec_id), codecpar->width, codecpar->height);
-        SDL_SetWindowSize(window, codecpar->width, codecpar->height);
-    }
-    return context;
-}
-
-static SDL_PixelFormatEnum GetVideoFormat(enum AVPixelFormat format)
-{
-    switch (format) {
-    case AV_PIX_FMT_RGB8:
-        return SDL_PIXELFORMAT_RGB332;
-    case AV_PIX_FMT_RGB444:
-        return SDL_PIXELFORMAT_RGB444;
-    case AV_PIX_FMT_RGB555:
-        return SDL_PIXELFORMAT_RGB555;
-    case AV_PIX_FMT_BGR555:
-        return SDL_PIXELFORMAT_BGR555;
-    case AV_PIX_FMT_RGB565:
-        return SDL_PIXELFORMAT_RGB565;
-    case AV_PIX_FMT_BGR565:
-        return SDL_PIXELFORMAT_BGR565;
-    case AV_PIX_FMT_RGB24:
-        return SDL_PIXELFORMAT_RGB24;
-    case AV_PIX_FMT_BGR24:
-        return SDL_PIXELFORMAT_BGR24;
-    case AV_PIX_FMT_0RGB32:
-        return SDL_PIXELFORMAT_XRGB8888;
-    case AV_PIX_FMT_0BGR32:
-        return SDL_PIXELFORMAT_XBGR8888;
-    case AV_PIX_FMT_NE(RGB0, 0BGR):
-        return SDL_PIXELFORMAT_RGBX8888;
-    case AV_PIX_FMT_NE(BGR0, 0RGB):
-        return SDL_PIXELFORMAT_BGRX8888;
-    case AV_PIX_FMT_RGB32:
-        return SDL_PIXELFORMAT_ARGB8888;
-    case AV_PIX_FMT_RGB32_1:
-        return SDL_PIXELFORMAT_RGBA8888;
-    case AV_PIX_FMT_BGR32:
-        return SDL_PIXELFORMAT_ABGR8888;
-    case AV_PIX_FMT_BGR32_1:
-        return SDL_PIXELFORMAT_BGRA8888;
-    case AV_PIX_FMT_YUV420P:
-        return SDL_PIXELFORMAT_IYUV;
-    case AV_PIX_FMT_YUYV422:
-        return SDL_PIXELFORMAT_YUY2;
-    case AV_PIX_FMT_UYVY422:
-        return SDL_PIXELFORMAT_UYVY;
-    default:
-        return SDL_PIXELFORMAT_UNKNOWN;
-    }
-}
-
-static void SetYUVConversionMode(AVFrame *frame)
-{
-    SDL_YUV_CONVERSION_MODE mode = SDL_YUV_CONVERSION_AUTOMATIC;
-    if (frame && (frame->format == AV_PIX_FMT_YUV420P || frame->format == AV_PIX_FMT_YUYV422 || frame->format == AV_PIX_FMT_UYVY422)) {
-        if (frame->color_range == AVCOL_RANGE_JPEG)
-            mode = SDL_YUV_CONVERSION_JPEG;
-        else if (frame->colorspace == AVCOL_SPC_BT709)
-            mode = SDL_YUV_CONVERSION_BT709;
-        else if (frame->colorspace == AVCOL_SPC_BT470BG || frame->colorspace == AVCOL_SPC_SMPTE170M)
-            mode = SDL_YUV_CONVERSION_BT601;
-    }
-    SDL_SetYUVConversionMode(mode); /* FIXME: no support for linear transfer */
-}
-
-static void HandleVideoFrame(AVFrame *frame, double pts)
-{
-    SDL_RendererFlip flip = SDL_FLIP_NONE;
-
-    /* Update the video texture */
-    SDL_PixelFormatEnum format = GetVideoFormat(frame->format);
-    if (!video_texture || format != video_format || frame->width != video_width || frame->height != video_height) {
-        if (video_texture) {
-            SDL_DestroyTexture(video_texture);
-        }
-
-        if (format == SDL_PIXELFORMAT_UNKNOWN) {
-            video_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height);
-        } else {
-            video_texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height);
-        }
-        video_format = format;
-        video_width = frame->width;
-        video_height = frame->height;
-    }
-
-    switch (format) {
-    case SDL_PIXELFORMAT_UNKNOWN:
-        video_conversion_context = sws_getCachedContext(video_conversion_context,
-            frame->width, frame->height, frame->format, frame->width, frame->height,
-            AV_PIX_FMT_BGRA, SWS_POINT, NULL, NULL, NULL);
-        if (video_conversion_context != NULL) {
-            uint8_t *pixels[4];
-            int pitch[4];
-            if (SDL_LockTexture(video_texture, NULL, (void **)pixels, pitch) == 0) {
-                sws_scale(video_conversion_context, (const uint8_t * const *)frame->data, frame->linesize,
-                          0, frame->height, pixels, pitch);
-                SDL_UnlockTexture(video_texture);
-            }
-        }
-        break;
-    case SDL_PIXELFORMAT_IYUV:
-        if (frame->linesize[0] > 0 && frame->linesize[1] > 0 && frame->linesize[2] > 0) {
-            SDL_UpdateYUVTexture(video_texture, NULL, frame->data[0], frame->linesize[0],
-                                                   frame->data[1], frame->linesize[1],
-                                                   frame->data[2], frame->linesize[2]);
-        } else if (frame->linesize[0] < 0 && frame->linesize[1] < 0 && frame->linesize[2] < 0) {
-            SDL_UpdateYUVTexture(video_texture, NULL, frame->data[0] + frame->linesize[0] * (frame->height                    - 1), -frame->linesize[0],
-                                                   frame->data[1] + frame->linesize[1] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[1],
-                                                   frame->data[2] + frame->linesize[2] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[2]);
-            flip = SDL_FLIP_VERTICAL;
-        }
-        SetYUVConversionMode(frame);
-        break;
-    default:
-        if (frame->linesize[0] < 0) {
-            SDL_UpdateTexture(video_texture, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0]);
-            flip = SDL_FLIP_VERTICAL;
-        } else {
-            SDL_UpdateTexture(video_texture, NULL, frame->data[0], frame->linesize[0]);
-        }
-        break;
-    }
-
-    /* Quick and dirty PTS handling */
-    if (!video_start) {
-        video_start = SDL_GetTicks();
-    }
-    double now = (double)(SDL_GetTicks() - video_start) / 1000.0;
-    while (now < pts - 0.001) {
-        SDL_Delay(1);
-        now = (double)(SDL_GetTicks() - video_start) / 1000.0;
-    }
-
-    SDL_RenderTextureRotated(renderer, video_texture, NULL, NULL, 0.0, NULL, flip);
-    MoveSprite();
-    SDL_RenderPresent(renderer);
-}
-
 int main(int argc, char *argv[])
 {
     const char *file = NULL;
     AVFormatContext *ic = NULL;
     int audio_stream = -1;
     int video_stream = -1;
+    const AVCodec *audio_decoder = NULL;
+    const AVCodec *video_decoder = NULL;
     AVCodecContext *audio_context = NULL;
     AVCodecContext *video_context = NULL;
     AVPacket *pkt = NULL;
@@ -389,6 +630,7 @@ int main(int argc, char *argv[])
     int i;
     int result;
     int return_code = -1;
+    Uint32 window_flags;
     SDL_bool flushing = SDL_FALSE;
     SDL_bool decoded = SDL_FALSE;
 
@@ -416,7 +658,17 @@ int main(int argc, char *argv[])
         goto quit;
     }
 
-    if (SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer) < 0) {
+    window_flags = SDL_WINDOW_HIDDEN;
+#ifdef __APPLE__
+    window_flags |= SDL_WINDOW_METAL;
+#elif !defined(__WIN32__)
+    window_flags |= SDL_WINDOW_OPENGL;
+#endif
+#ifdef HAVE_EGL
+    /* Try to create an EGL compatible window for DRM hardware frame support */
+    CreateWindow(window_flags, SDL_TRUE);
+#endif
+    if (!window && !CreateWindow(window_flags, SDL_FALSE)) {
         return_code = 2;
         goto quit;
     }
@@ -432,17 +684,17 @@ int main(int argc, char *argv[])
         return_code = 4;
         goto quit;
     }
-    video_stream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
+    video_stream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &video_decoder, 0);
     if (video_stream >= 0) {
-        video_context = OpenVideoStream(ic, video_stream);
+        video_context = OpenVideoStream(ic, video_stream, video_decoder);
         if (!video_context) {
             return_code = 4;
             goto quit;
         }
     }
-    audio_stream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, video_stream, NULL, 0);
+    audio_stream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, video_stream, &audio_decoder, 0);
     if (audio_stream >= 0) {
-        audio_context = OpenAudioStream(ic, audio_stream);
+        audio_context = OpenAudioStream(ic, audio_stream, audio_decoder);
         if (!audio_context) {
             return_code = 4;
             goto quit;
@@ -496,6 +748,9 @@ int main(int argc, char *argv[])
         }
     }
 
+    /* We're ready to go! */
+    SDL_ShowWindow(window);
+
     /* Main render loop */
     done = 0;