From 962ebebe066169592837af1227234e23191f286b Mon Sep 17 00:00:00 2001 From: =?utf8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Mon, 3 Aug 2020 13:50:23 +0200 Subject: [PATCH] tests: examples: add va-x11-render example This a GTK+ example will share, through GstContext, a custom X11 VADisplay to a pipeline using vah264dec and appsink. When the frames are processed for rendering, the VASurfaceID is fetched from the buffer and it is rendered using vaPutSurface in a X11 widget. --- tests/examples/meson.build | 1 + tests/examples/va/main.c | 321 ++++++++++++++++++++++++++++++++++++++++++ tests/examples/va/meson.build | 14 ++ 3 files changed, 336 insertions(+) create mode 100644 tests/examples/va/main.c create mode 100644 tests/examples/va/meson.build diff --git a/tests/examples/meson.build b/tests/examples/meson.build index bc7fdb7..2eba1d4 100644 --- a/tests/examples/meson.build +++ b/tests/examples/meson.build @@ -11,6 +11,7 @@ subdir('mxf') subdir('nvcodec') subdir('opencv', if_found: opencv_dep) subdir('uvch264') +subdir('va') subdir('waylandsink') subdir('webrtc') diff --git a/tests/examples/va/main.c b/tests/examples/va/main.c new file mode 100644 index 0000000..2779139 --- /dev/null +++ b/tests/examples/va/main.c @@ -0,0 +1,321 @@ +#include + +#include + +#include +#if defined (GDK_WINDOWING_X11) +#include +#else +#error "Only X11 is supported so far" +#endif + +#include + +#include +#include +#include + +#include + +#define GST_MAP_VA (GST_MAP_FLAG_LAST << 1) + +static gchar **INPUT_FILES = NULL; + +struct _app +{ + GtkWidget *window; + GtkWidget *video; + GstElement *pipeline; + GstSample *sample; + GMutex mutex; + VADisplay va_dpy; +}; + +static GstBusSyncReply +context_handler (GstBus * bus, GstMessage * msg, gpointer data) +{ + struct _app *app = data; + const gchar *context_type; + + if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_NEED_CONTEXT) + return GST_BUS_DROP; + + gst_message_parse_context_type (msg, &context_type); + gst_println ("got need context %s", context_type); + + if (g_strcmp0 (context_type, "gst.va.display.handle") == 0) { + GstContext *context; + GstStructure *s; + + context = gst_context_new ("gst.va.display.handle", TRUE); + s = gst_context_writable_structure (context); + gst_structure_set (s, "va-display", G_TYPE_POINTER, app->va_dpy, NULL); + gst_element_set_context (GST_ELEMENT (msg->src), context); + } + + return GST_BUS_DROP; +} + +static void +delete_event_cb (GtkWidget * widget, GdkEvent * event, gpointer data) +{ + struct _app *app = data; + + gst_element_set_state (app->pipeline, GST_STATE_NULL); + + gtk_main_quit (); +} + +static gboolean +draw_unlocked (GtkWidget * widget, struct _app *app) +{ + GstBuffer *buffer; + GstCaps *caps; + GstMapInfo map_info; + GstVideoInfo vinfo; + VASurfaceID surface; + VAStatus va_status; + GstVideoRectangle src, dst, res; + gboolean ret = FALSE; + + buffer = gst_sample_get_buffer (app->sample); + caps = gst_sample_get_caps (app->sample); + + if (!gst_video_info_from_caps (&vinfo, caps)) + return FALSE; + + src.x = 0; + src.y = 0; + src.w = GST_VIDEO_INFO_WIDTH (&vinfo); + src.h = GST_VIDEO_INFO_HEIGHT (&vinfo); + + if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ | GST_MAP_VA)) + return FALSE; + + surface = (*(VASurfaceID *) map_info.data); + if (surface == VA_INVALID_ID) + goto bail; + + dst.x = 0; + dst.y = 0; + dst.w = gtk_widget_get_allocated_width (widget); + dst.h = gtk_widget_get_allocated_height (widget); + + gst_video_sink_center_rect (src, dst, &res, TRUE); + + va_status = vaPutSurface (app->va_dpy, surface, + GDK_WINDOW_XID (gtk_widget_get_window (widget)), + src.x, src.y, src.w, src.h, res.x, res.y, res.w, res.h, NULL, 0, 0); + if (va_status != VA_STATUS_SUCCESS) + gst_printerrln ("failed vaPutSurface: %s", vaErrorStr (va_status)); + else + ret = TRUE; + +bail: + gst_buffer_unmap (buffer, &map_info); + + return ret; +} + +static gboolean +redraw_cb (gpointer data) +{ + GtkWidget *video = data; + + gtk_widget_queue_draw (video); + return G_SOURCE_REMOVE; +} + +static gboolean +draw_cb (GtkWidget * widget, cairo_t * cr, gpointer data) +{ + struct _app *app = data; + gboolean ret = TRUE; + + g_mutex_lock (&app->mutex); + if (app->sample) + ret = draw_unlocked (widget, app); + g_mutex_unlock (&app->mutex); + + if (!ret) + gst_printerrln ("failed to paint frame"); + + return FALSE; +} + +static void +build_ui (struct _app *app) +{ + app->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (app->window), "VA X11 render"); + g_signal_connect (app->window, "delete-event", G_CALLBACK (delete_event_cb), + app); + + app->video = gtk_drawing_area_new (); + g_signal_connect (app->video, "draw", G_CALLBACK (draw_cb), app); + + gtk_container_add (GTK_CONTAINER (app->window), app->video); + + gtk_widget_show_all (app->window); +} + +static GstFlowReturn +new_sample_cb (GstAppSink * sink, gpointer data) +{ + struct _app *app = data; + + g_mutex_lock (&app->mutex); + if (app->sample) + gst_sample_unref (app->sample); + app->sample = gst_app_sink_pull_sample (sink); + g_mutex_unlock (&app->mutex); + + g_idle_add (redraw_cb, app->video); + + return GST_FLOW_OK; +} + +static void +end_stream_cb (GstBus * bus, GstMessage * msg, gpointer data) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR:{ + gchar *debug = NULL; + GError *err = NULL; + + gst_message_parse_error (msg, &err, &debug); + gst_printerrln ("GStreamer error: %s\n%s", err->message, + debug ? debug : ""); + if (debug) + g_free (debug); + if (err) + g_error_free (err); + break; + } + default: + break; + } + + gtk_main_quit (); +} + +static gboolean +build_pipeline (struct _app *app) +{ + GstElement *src, *sink; + GstCaps *caps; + GstBus *bus; + GstAppSinkCallbacks callbacks = { + .new_sample = new_sample_cb, + }; + GError *err = NULL; + + app->pipeline = gst_parse_launch ("filesrc name=src ! " + "parsebin ! vah264dec ! appsink name=sink", &err); + if (err) { + gst_printerrln ("Couldn't create pipeline: %s", err->message); + g_error_free (err); + return FALSE; + } + + src = gst_bin_get_by_name (GST_BIN (app->pipeline), "src"); + g_object_set (src, "location", INPUT_FILES[0], NULL); + gst_object_unref (src); + + sink = gst_bin_get_by_name (GST_BIN (app->pipeline), "sink"); + caps = gst_caps_from_string ("video/x-raw (memory:VAMemory)"); + g_object_set (sink, "caps", caps, NULL); + gst_caps_unref (caps); + gst_app_sink_set_callbacks (GST_APP_SINK (sink), &callbacks, app, NULL); + gst_object_unref (src); + + bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message::error", G_CALLBACK (end_stream_cb), app); + gst_bus_set_sync_handler (bus, context_handler, app, NULL); + gst_object_unref (bus); + + return TRUE; +} + +static gboolean +parse_arguments (int *argc, char ***argv) +{ + GOptionContext *ctxt; + GError *err = NULL; + const GOptionEntry options[] = { + {G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, + &INPUT_FILES, "H.264 video files to play", NULL}, + {NULL,}, + }; + + ctxt = g_option_context_new ("— VA X11 render"); + g_option_context_add_main_entries (ctxt, options, NULL); + g_option_context_add_group (ctxt, gtk_get_option_group (TRUE)); + g_option_context_add_group (ctxt, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctxt, argc, argv, &err)) { + gst_printerrln ("option parsing failed: %s", err->message); + g_error_free (err); + return FALSE; + } + + g_option_context_free (ctxt); + return TRUE; +} + +int +main (int argc, char **argv) +{ + GdkDisplay *gdk_dpy; + VAStatus va_status; + struct _app app = { NULL, }; + int maj, min, ret = EXIT_FAILURE; + +#if defined (GDK_WINDOWING_X11) + XInitThreads (); +#endif + + if (!parse_arguments (&argc, &argv)) + return EXIT_FAILURE; + + if (!(INPUT_FILES && INPUT_FILES[0])) + goto gtk_failed; + + gdk_dpy = gdk_display_get_default (); + if (!GDK_IS_X11_DISPLAY (gdk_dpy)) { + gst_printerrln ("This example is only for native X11"); + goto gtk_failed; + } + + g_mutex_init (&app.mutex); + + if (!build_pipeline (&app)) + goto gst_failed; + + app.va_dpy = vaGetDisplay (GDK_DISPLAY_XDISPLAY (gdk_dpy)); + va_status = vaInitialize (app.va_dpy, &maj, &min); + if (va_status != VA_STATUS_SUCCESS) { + gst_printerrln ("failed to initialize VA: %s", vaErrorStr (va_status)); + goto va_failed; + } + + build_ui (&app); + + gst_element_set_state (app.pipeline, GST_STATE_PLAYING); + + gtk_main (); + + ret = EXIT_SUCCESS; + +va_failed: + vaTerminate (app.va_dpy); + gst_object_unref (app.pipeline); +gst_failed: + g_mutex_clear (&app.mutex); +gtk_failed: + g_strfreev (INPUT_FILES); + gst_deinit (); + + return ret; +} diff --git a/tests/examples/va/meson.build b/tests/examples/va/meson.build new file mode 100644 index 0000000..c20d184 --- /dev/null +++ b/tests/examples/va/meson.build @@ -0,0 +1,14 @@ +gtk_dep = dependency('gtk+-3.0', required : get_option('examples')) +gtk_x11_dep = dependency('gtk+-x11-3.0', required : get_option('examples')) +x11_dep = dependency('x11', required : get_option('examples')) +libva_x11_dep = dependency('libva-x11', version: libva_req, required: get_option('examples')) + +if have_va and gtk_dep.found() and gtk_x11_dep.found() and x11_dep.found() and libva_x11_dep.found() + executable('va-x11-render', + 'main.c', + install: false, + include_directories : [configinc], + dependencies : [gtk_dep, gtk_x11_dep, x11_dep, gst_dep, gstapp_dep, gstvideo_dep, libva_dep, libva_x11_dep], + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + ) +endif -- 2.7.4