• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include <stdlib.h>
2 
3 #include <X11/Xlib.h>
4 
5 #include <gdk/gdk.h>
6 #if defined (GDK_WINDOWING_X11)
7 #include <gdk/gdkx.h>
8 #else
9 #error "Only X11 is supported so far"
10 #endif
11 
12 #include <gtk/gtk.h>
13 
14 #include <gst/gst.h>
15 #include <gst/app/gstappsink.h>
16 #include <gst/video/video.h>
17 #include <gst/va/gstvadisplay.h>
18 
19 #include <va/va_x11.h>
20 
21 #define GST_MAP_VA (GST_MAP_FLAG_LAST << 1)
22 
23 static gchar **INPUT_FILES = NULL;
24 
25 struct _app
26 {
27   GtkWidget *window;
28   GtkWidget *video;
29   GstElement *pipeline;
30   GstSample *sample;
31   GMutex mutex;
32   VADisplay va_dpy;
33 };
34 
35 static GstBusSyncReply
context_handler(GstBus * bus,GstMessage * msg,gpointer data)36 context_handler (GstBus * bus, GstMessage * msg, gpointer data)
37 {
38   struct _app *app = data;
39   const gchar *context_type;
40 
41   if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_NEED_CONTEXT)
42     return GST_BUS_PASS;
43 
44   gst_message_parse_context_type (msg, &context_type);
45 
46   gst_println ("got need context %s", context_type);
47 
48   if (g_strcmp0 (context_type, GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR) == 0) {
49     GstContext *context;
50     GstStructure *s;
51 
52     context = gst_context_new (GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR, TRUE);
53     s = gst_context_writable_structure (context);
54     gst_structure_set (s, "va-display", G_TYPE_POINTER, app->va_dpy, NULL);
55     gst_element_set_context (GST_ELEMENT (msg->src), context);
56     gst_context_unref (context);
57   }
58 
59   gst_message_unref (msg);
60 
61   return GST_BUS_DROP;
62 }
63 
64 static void
delete_event_cb(GtkWidget * widget,GdkEvent * event,gpointer data)65 delete_event_cb (GtkWidget * widget, GdkEvent * event, gpointer data)
66 {
67   gtk_main_quit ();
68 }
69 
70 static gboolean
draw_unlocked(GtkWidget * widget,struct _app * app)71 draw_unlocked (GtkWidget * widget, struct _app *app)
72 {
73   GstBuffer *buffer;
74   GstCaps *caps;
75   GstMapInfo map_info;
76   GstVideoInfo vinfo;
77   VASurfaceID surface;
78   VAStatus va_status;
79   GstVideoRectangle src, dst, res;
80   gboolean ret = FALSE;
81 
82   buffer = gst_sample_get_buffer (app->sample);
83   caps = gst_sample_get_caps (app->sample);
84 
85   if (!gst_video_info_from_caps (&vinfo, caps))
86     return FALSE;
87 
88   src.x = 0;
89   src.y = 0;
90   src.w = GST_VIDEO_INFO_WIDTH (&vinfo);
91   src.h = GST_VIDEO_INFO_HEIGHT (&vinfo);
92 
93   if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ | GST_MAP_VA))
94     return FALSE;
95 
96   surface = (*(VASurfaceID *) map_info.data);
97   if (surface == VA_INVALID_ID)
98     goto bail;
99 
100   dst.x = 0;
101   dst.y = 0;
102   dst.w = gtk_widget_get_allocated_width (widget);
103   dst.h = gtk_widget_get_allocated_height (widget);
104 
105   gst_video_sink_center_rect (src, dst, &res, TRUE);
106 
107   va_status = vaPutSurface (app->va_dpy, surface,
108       GDK_WINDOW_XID (gtk_widget_get_window (widget)),
109       src.x, src.y, src.w, src.h, res.x, res.y, res.w, res.h, NULL, 0, 0);
110   if (va_status != VA_STATUS_SUCCESS)
111     gst_printerrln ("failed vaPutSurface: %s", vaErrorStr (va_status));
112   else
113     ret = TRUE;
114 
115 bail:
116   gst_buffer_unmap (buffer, &map_info);
117 
118   return ret;
119 }
120 
121 static gboolean
redraw_cb(gpointer data)122 redraw_cb (gpointer data)
123 {
124   GtkWidget *video = data;
125 
126   gtk_widget_queue_draw (video);
127   return G_SOURCE_REMOVE;
128 }
129 
130 static gboolean
draw_cb(GtkWidget * widget,cairo_t * cr,gpointer data)131 draw_cb (GtkWidget * widget, cairo_t * cr, gpointer data)
132 {
133   struct _app *app = data;
134   gboolean ret = TRUE;
135 
136   g_mutex_lock (&app->mutex);
137   if (app->sample)
138     ret = draw_unlocked (widget, app);
139   g_mutex_unlock (&app->mutex);
140 
141   if (!ret)
142     gst_printerrln ("failed to paint frame");
143 
144   return FALSE;
145 }
146 
147 static void
build_ui(struct _app * app)148 build_ui (struct _app *app)
149 {
150   app->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
151   gtk_window_set_title (GTK_WINDOW (app->window), "VA X11 render");
152   g_signal_connect (app->window, "delete-event", G_CALLBACK (delete_event_cb),
153       app);
154 
155   app->video = gtk_drawing_area_new ();
156   g_signal_connect (app->video, "draw", G_CALLBACK (draw_cb), app);
157 
158   gtk_container_add (GTK_CONTAINER (app->window), app->video);
159 
160   gtk_widget_show_all (app->window);
161 }
162 
163 static GstFlowReturn
new_sample_cb(GstAppSink * sink,gpointer data)164 new_sample_cb (GstAppSink * sink, gpointer data)
165 {
166   struct _app *app = data;
167 
168   g_mutex_lock (&app->mutex);
169   if (app->sample)
170     gst_sample_unref (app->sample);
171   app->sample = gst_app_sink_pull_sample (sink);
172   g_mutex_unlock (&app->mutex);
173 
174   g_idle_add (redraw_cb, app->video);
175 
176   return GST_FLOW_OK;
177 }
178 
179 static gboolean
message_handler(GstBus * bus,GstMessage * msg,gpointer data)180 message_handler (GstBus * bus, GstMessage * msg, gpointer data)
181 {
182   switch (GST_MESSAGE_TYPE (msg)) {
183     case GST_MESSAGE_EOS:
184       gtk_main_quit ();
185       break;
186     case GST_MESSAGE_ERROR:{
187       gchar *debug = NULL;
188       GError *err = NULL;
189 
190       gst_message_parse_error (msg, &err, &debug);
191       gst_printerrln ("GStreamer error: %s\n%s", err->message,
192           debug ? debug : "");
193       if (debug)
194         g_free (debug);
195       if (err)
196         g_error_free (err);
197 
198       gtk_main_quit ();
199       break;
200     }
201     default:
202       break;
203   }
204 
205   return TRUE;
206 }
207 
208 static gboolean
build_pipeline(struct _app * app)209 build_pipeline (struct _app *app)
210 {
211   GstElement *src, *sink;
212   GstCaps *caps;
213   GstBus *bus;
214   GstAppSinkCallbacks callbacks = {
215     .new_sample = new_sample_cb,
216   };
217   GError *err = NULL;
218 
219   app->pipeline = gst_parse_launch ("filesrc name=src ! "
220       "parsebin ! vah264dec ! appsink name=sink", &err);
221   if (err) {
222     gst_printerrln ("Couldn't create pipeline: %s", err->message);
223     g_error_free (err);
224     return FALSE;
225   }
226 
227   src = gst_bin_get_by_name (GST_BIN (app->pipeline), "src");
228   g_object_set (src, "location", INPUT_FILES[0], NULL);
229   gst_object_unref (src);
230 
231   sink = gst_bin_get_by_name (GST_BIN (app->pipeline), "sink");
232   caps = gst_caps_from_string ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_VA ")");
233   g_object_set (sink, "caps", caps, NULL);
234   gst_caps_unref (caps);
235   gst_app_sink_set_callbacks (GST_APP_SINK (sink), &callbacks, app, NULL);
236   gst_object_unref (sink);
237 
238   bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
239   gst_bus_set_sync_handler (bus, context_handler, app, NULL);
240   gst_bus_add_watch (bus, message_handler, app);
241   gst_object_unref (bus);
242 
243   return TRUE;
244 }
245 
246 static gboolean
parse_arguments(int * argc,char *** argv)247 parse_arguments (int *argc, char ***argv)
248 {
249   GOptionContext *ctxt;
250   GError *err = NULL;
251   const GOptionEntry options[] = {
252     {G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY,
253         &INPUT_FILES, "H.264 video files to play", NULL},
254     {NULL,},
255   };
256 
257   ctxt = g_option_context_new ("— VA X11 render");
258   g_option_context_add_main_entries (ctxt, options, NULL);
259   g_option_context_add_group (ctxt, gtk_get_option_group (TRUE));
260   g_option_context_add_group (ctxt, gst_init_get_option_group ());
261 
262   if (!g_option_context_parse (ctxt, argc, argv, &err)) {
263     gst_printerrln ("option parsing failed: %s", err->message);
264     g_error_free (err);
265     return FALSE;
266   }
267 
268   g_option_context_free (ctxt);
269   return TRUE;
270 }
271 
272 int
main(int argc,char ** argv)273 main (int argc, char **argv)
274 {
275   GdkDisplay *gdk_dpy;
276   GstBus *bus;
277   VAStatus va_status;
278   struct _app app = { NULL, };
279   int maj, min, ret = EXIT_FAILURE;
280 
281 #if defined (GDK_WINDOWING_X11)
282   XInitThreads ();
283 #endif
284 
285   if (!parse_arguments (&argc, &argv))
286     return EXIT_FAILURE;
287 
288   if (!(INPUT_FILES && INPUT_FILES[0]))
289     goto gtk_failed;
290 
291   gdk_dpy = gdk_display_get_default ();
292   if (!GDK_IS_X11_DISPLAY (gdk_dpy)) {
293     gst_printerrln ("This example is only for native X11");
294     goto gtk_failed;
295   }
296 
297   g_mutex_init (&app.mutex);
298 
299   if (!build_pipeline (&app))
300     goto gst_failed;
301 
302   app.va_dpy = vaGetDisplay (GDK_DISPLAY_XDISPLAY (gdk_dpy));
303   va_status = vaInitialize (app.va_dpy, &maj, &min);
304   if (va_status != VA_STATUS_SUCCESS) {
305     gst_printerrln ("failed to initialize VA: %s", vaErrorStr (va_status));
306     goto va_failed;
307   }
308 
309   build_ui (&app);
310 
311   gst_element_set_state (app.pipeline, GST_STATE_PLAYING);
312 
313   gtk_main ();
314 
315   if (app.sample)
316     gst_sample_unref (app.sample);
317 
318   gst_element_set_state (app.pipeline, GST_STATE_NULL);
319 
320   bus = gst_pipeline_get_bus (GST_PIPELINE (app.pipeline));
321   gst_bus_remove_watch (bus);
322   gst_object_unref (bus);
323 
324   ret = EXIT_SUCCESS;
325 
326 va_failed:
327   gst_object_unref (app.pipeline);
328   vaTerminate (app.va_dpy);
329 gst_failed:
330   g_mutex_clear (&app.mutex);
331 gtk_failed:
332   g_strfreev (INPUT_FILES);
333   gst_deinit ();
334 
335   return ret;
336 }
337