2 * simple-decoder.c - Simple Decoder Application
4 * Copyright (C) 2013-2014 Intel Corporation
5 * Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public License
9 * as published by the Free Software Foundation; either version 2.1
10 * of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301 USA
24 * This is a really simple decoder application that only accepts raw
25 * bitstreams. So, it may be needed to suggest what codec to use to
29 #include "gst/vaapi/sysdeps.h"
31 #include <gst/vaapi/gstvaapidecoder.h>
32 #include <gst/vaapi/gstvaapidecoder_h264.h>
33 #include <gst/vaapi/gstvaapidecoder_jpeg.h>
34 #include <gst/vaapi/gstvaapidecoder_mpeg2.h>
35 #include <gst/vaapi/gstvaapidecoder_mpeg4.h>
36 #include <gst/vaapi/gstvaapidecoder_vc1.h>
37 #include <gst/vaapi/gstvaapiwindow.h>
38 #include <gst/vaapi/gstvaapipixmap.h>
42 static gchar *g_codec_str;
43 static gboolean g_use_pixmap;
44 static gboolean g_benchmark;
46 static GOptionEntry g_options[] = {
49 G_OPTION_ARG_STRING, &g_codec_str,
50 "suggested codec", NULL },
53 G_OPTION_ARG_NONE, &g_use_pixmap,
54 "use render-to-pixmap", NULL },
57 G_OPTION_ARG_NONE, &g_benchmark,
58 "benchmark mode", NULL },
75 GstVaapiSurfaceProxy *proxy;
77 GstClockTime duration;
87 GstVaapiDisplay *display;
88 GstVaapiDecoder *decoder;
89 GThread *decoder_thread;
90 volatile gboolean decoder_thread_cancel;
92 GAsyncQueue *decoder_queue;
96 guint32 frame_duration;
99 GstVaapiPixmap *pixmaps[2];
103 GstVaapiWindow *window;
106 GThread *render_thread;
107 volatile gboolean render_thread_cancel;
109 RenderFrame *last_frame;
117 static inline RenderFrame *
118 render_frame_new(void)
120 return g_slice_new(RenderFrame);
124 render_frame_free(RenderFrame *rfp)
126 if (G_UNLIKELY(!rfp))
128 gst_vaapi_surface_proxy_replace(&rfp->proxy, NULL);
129 g_slice_free(RenderFrame, rfp);
133 render_frame_replace(RenderFrame **rfp_ptr, RenderFrame *new_rfp)
136 render_frame_free(*rfp_ptr);
140 #define APP_ERROR app_error_quark()
142 app_error_quark(void)
144 static gsize g_quark;
146 if (g_once_init_enter(&g_quark)) {
147 gsize quark = (gsize)g_quark_from_static_string("AppError");
148 g_once_init_leave(&g_quark, quark);
154 app_send_error(App *app, GError *error)
156 g_mutex_lock(&app->mutex);
158 app->event = APP_GOT_ERROR;
159 g_cond_signal(&app->event_cond);
160 g_mutex_unlock(&app->mutex);
164 app_send_eos(App *app)
166 g_mutex_lock(&app->mutex);
167 app->event = APP_GOT_EOS;
168 g_cond_signal(&app->event_cond);
169 g_mutex_unlock(&app->mutex);
173 get_decoder_status_string(GstVaapiDecoderStatus status)
177 #define DEFINE_STATUS(status, status_string) \
178 case GST_VAAPI_DECODER_STATUS_##status: \
179 str = status_string; \
183 DEFINE_STATUS(SUCCESS, "<success>");
184 DEFINE_STATUS(END_OF_STREAM, "<EOS>");
185 DEFINE_STATUS(ERROR_ALLOCATION_FAILED, "allocation failed");
186 DEFINE_STATUS(ERROR_INIT_FAILED, "initialization failed");
187 DEFINE_STATUS(ERROR_UNSUPPORTED_CODEC, "unsupported codec");
188 DEFINE_STATUS(ERROR_NO_DATA, "not enough data");
189 DEFINE_STATUS(ERROR_NO_SURFACE, "no surface vailable");
190 DEFINE_STATUS(ERROR_INVALID_SURFACE, "invalid surface");
191 DEFINE_STATUS(ERROR_BITSTREAM_PARSER, "bitstream parser error");
192 DEFINE_STATUS(ERROR_UNSUPPORTED_PROFILE,
193 "unsupported profile");
194 DEFINE_STATUS(ERROR_UNSUPPORTED_CHROMA_FORMAT,
195 "unsupported chroma-format");
196 DEFINE_STATUS(ERROR_INVALID_PARAMETER, "invalid parameter");
207 get_error_string(AppError error)
211 #define DEFINE_ERROR(error, error_string) \
212 case APP_ERROR_##error: \
213 str = error_string; \
217 DEFINE_ERROR(NONE, "<none>");
218 DEFINE_ERROR(DECODER, "decoder");
219 DEFINE_ERROR(RENDERER, "renderer");
230 decoder_release(App *app)
232 g_mutex_lock(&app->mutex);
233 g_cond_signal(&app->decoder_ready);
234 g_mutex_unlock(&app->mutex);
238 decoder_thread(gpointer data)
240 App * const app = data;
241 GError *error = NULL;
242 GstVaapiDecoderStatus status;
243 GstVaapiSurfaceProxy *proxy;
247 gboolean got_surface, got_eos = FALSE;
251 g_print("Decoder thread started\n");
253 #define SEND_ERROR(...) \
255 error = g_error_new(APP_ERROR, APP_ERROR_DECODER, __VA_ARGS__); \
259 pts = g_get_monotonic_time();
261 while (!app->decoder_thread_cancel) {
262 if (G_UNLIKELY(ofs == app->file_size))
265 const gsize size = MIN(4096, app->file_size - ofs);
266 buffer = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY,
267 app->file_data, app->file_size, ofs, size, NULL, NULL);
269 SEND_ERROR("failed to allocate new buffer");
272 if (!gst_vaapi_decoder_put_buffer(app->decoder, buffer))
273 SEND_ERROR("failed to push buffer to decoder");
274 gst_buffer_replace(&buffer, NULL);
277 status = gst_vaapi_decoder_get_surface(app->decoder, &proxy);
279 case GST_VAAPI_DECODER_STATUS_SUCCESS:
280 gst_vaapi_surface_proxy_set_destroy_notify(proxy,
281 (GDestroyNotify)decoder_release, app);
282 rfp = render_frame_new();
284 SEND_ERROR("failed to allocate render frame");
287 rfp->duration = app->frame_duration;
288 pts += app->frame_duration;
289 g_async_queue_push(app->decoder_queue, rfp);
291 case GST_VAAPI_DECODER_STATUS_ERROR_NO_DATA:
292 /* nothing to do, just continue to the next iteration */
294 case GST_VAAPI_DECODER_STATUS_END_OF_STREAM:
295 gst_vaapi_decoder_flush(app->decoder);
300 case GST_VAAPI_DECODER_STATUS_ERROR_NO_SURFACE:
301 end_time = g_get_monotonic_time() + G_TIME_SPAN_SECOND;
302 g_mutex_lock(&app->mutex);
303 got_surface = g_cond_wait_until(&app->decoder_ready, &app->mutex,
305 g_mutex_unlock(&app->mutex);
308 SEND_ERROR("failed to acquire a surface within one second");
311 SEND_ERROR("%s", get_decoder_status_string(status));
324 app_send_error(app, error);
329 app_set_framerate(App *app, guint fps_n, guint fps_d)
331 if (!fps_n || !fps_d)
334 g_mutex_lock(&app->mutex);
335 if (fps_n != app->fps_n || fps_d != app->fps_d) {
338 app->frame_duration = gst_util_uint64_scale(
339 GST_TIME_AS_USECONDS(GST_SECOND), fps_d, fps_n);
341 g_mutex_unlock(&app->mutex);
345 handle_decoder_state_changes(GstVaapiDecoder *decoder,
346 const GstVideoCodecState *codec_state, gpointer user_data)
348 App * const app = user_data;
350 g_assert(app->decoder == decoder);
351 app_set_framerate(app, codec_state->info.fps_n, codec_state->info.fps_d);
355 start_decoder(App *app)
359 app->file = g_mapped_file_new(app->file_name, FALSE, NULL);
363 app->file_size = g_mapped_file_get_length(app->file);
364 app->file_data = (guint8 *)g_mapped_file_get_contents(app->file);
368 caps = caps_from_codec(app->codec);
369 switch (app->codec) {
370 case GST_VAAPI_CODEC_H264:
371 app->decoder = gst_vaapi_decoder_h264_new(app->display, caps);
374 case GST_VAAPI_CODEC_JPEG:
375 app->decoder = gst_vaapi_decoder_jpeg_new(app->display, caps);
378 case GST_VAAPI_CODEC_MPEG2:
379 app->decoder = gst_vaapi_decoder_mpeg2_new(app->display, caps);
381 case GST_VAAPI_CODEC_MPEG4:
382 app->decoder = gst_vaapi_decoder_mpeg4_new(app->display, caps);
384 case GST_VAAPI_CODEC_VC1:
385 app->decoder = gst_vaapi_decoder_vc1_new(app->display, caps);
394 gst_vaapi_decoder_set_codec_state_changed_func(app->decoder,
395 handle_decoder_state_changes, app);
397 g_timer_start(app->timer);
399 app->decoder_thread = g_thread_try_new("Decoder Thread", decoder_thread,
401 if (!app->decoder_thread)
407 stop_decoder(App *app)
409 g_timer_stop(app->timer);
411 app->decoder_thread_cancel = TRUE;
412 g_thread_join(app->decoder_thread);
413 g_print("Decoder thread stopped\n");
418 ensure_window_size(App *app, GstVaapiSurface *surface)
422 if (gst_vaapi_window_get_fullscreen(app->window))
425 gst_vaapi_surface_get_size(surface, &width, &height);
426 if (app->surface_width == width && app->surface_height == height)
428 app->surface_width = width;
429 app->surface_height = height;
431 gst_vaapi_window_set_size(app->window, width, height);
432 gst_vaapi_window_get_size(app->window,
433 &app->window_width, &app->window_height);
437 ensure_pixmaps(App *app, GstVaapiSurface *surface,
438 const GstVaapiRectangle *crop_rect)
440 GstVaapiPixmap *pixmaps[G_N_ELEMENTS(app->pixmaps)];
441 guint num_pixmaps, i, width, height;
442 gboolean success = FALSE;
445 width = crop_rect->width;
446 height = crop_rect->height;
449 gst_vaapi_surface_get_size(surface, &width, &height);
450 if (app->pixmap_width == width && app->pixmap_height == height)
453 for (i = 0, num_pixmaps = 0; i < G_N_ELEMENTS(pixmaps); i++) {
454 GstVaapiPixmap * const pixmap =
455 video_output_create_pixmap(app->display, GST_VIDEO_FORMAT_xRGB,
459 pixmaps[num_pixmaps++] = pixmap;
462 for (i = 0; i < num_pixmaps; i++)
463 gst_vaapi_pixmap_replace(&app->pixmaps[i], pixmaps[i]);
464 app->pixmap_width = width;
465 app->pixmap_height = height;
469 for (i = 0; i < num_pixmaps; i++)
470 gst_vaapi_pixmap_replace(&pixmaps[i], NULL);
475 renderer_wait_until(App *app, GstClockTime pts)
477 g_mutex_lock(&app->mutex);
479 } while (g_cond_wait_until(&app->render_ready, &app->mutex, pts));
480 g_mutex_unlock(&app->mutex);
484 renderer_process(App *app, RenderFrame *rfp)
486 GError *error = NULL;
487 GstVaapiSurface *surface;
488 const GstVaapiRectangle *crop_rect;
490 #define SEND_ERROR(...) \
492 error = g_error_new(APP_ERROR, APP_ERROR_RENDERER, __VA_ARGS__); \
496 surface = gst_vaapi_surface_proxy_get_surface(rfp->proxy);
498 SEND_ERROR("failed to get decoded surface from render frame");
500 ensure_window_size(app, surface);
502 crop_rect = gst_vaapi_surface_proxy_get_crop_rect(rfp->proxy);
503 if (g_use_pixmap && !ensure_pixmaps(app, surface, crop_rect))
504 SEND_ERROR("failed to create intermediate pixmaps");
506 if (!gst_vaapi_surface_sync(surface))
507 SEND_ERROR("failed to sync decoded surface");
509 if (G_LIKELY(!g_benchmark))
510 renderer_wait_until(app, rfp->pts);
512 if (G_UNLIKELY(g_use_pixmap)) {
513 GstVaapiPixmap * const pixmap = app->pixmaps[app->pixmap_id];
515 if (!gst_vaapi_pixmap_put_surface(pixmap, surface, crop_rect,
516 GST_VAAPI_PICTURE_STRUCTURE_FRAME))
517 SEND_ERROR("failed to render to pixmap");
519 if (!gst_vaapi_window_put_pixmap(app->window, pixmap, NULL, NULL))
520 SEND_ERROR("failed to render surface %" GST_VAAPI_ID_FORMAT,
521 GST_VAAPI_ID_ARGS(pixmap));
523 app->pixmap_id = (app->pixmap_id + 1) % G_N_ELEMENTS(app->pixmaps);
525 else if (!gst_vaapi_window_put_surface(app->window, surface,
526 crop_rect, NULL, GST_VAAPI_PICTURE_STRUCTURE_FRAME))
527 SEND_ERROR("failed to render surface %" GST_VAAPI_ID_FORMAT,
528 GST_VAAPI_ID_ARGS(gst_vaapi_surface_get_id(surface)));
532 render_frame_replace(&app->last_frame, rfp);
538 app_send_error(app, error);
543 renderer_thread(gpointer data)
545 App * const app = data;
548 g_print("Render thread started\n");
550 while (!app->render_thread_cancel) {
551 rfp = g_async_queue_timeout_pop(app->decoder_queue, 1000000);
552 if (rfp && !renderer_process(app, rfp))
559 flush_decoder_queue(App *app)
563 /* Flush pending surfaces */
565 rfp = g_async_queue_try_pop(app->decoder_queue);
568 } while (renderer_process(app, rfp));
573 start_renderer(App *app)
575 app->render_thread = g_thread_try_new("Renderer Thread", renderer_thread,
577 if (!app->render_thread)
583 stop_renderer(App *app)
585 app->render_thread_cancel = TRUE;
586 g_thread_join(app->render_thread);
588 g_print("Render thread stopped\n");
590 flush_decoder_queue(app);
591 render_frame_replace(&app->last_frame, NULL);
604 g_mapped_file_unref(app->file);
607 g_free(app->file_name);
609 for (i = 0; i < G_N_ELEMENTS(app->pixmaps); i++)
610 gst_vaapi_pixmap_replace(&app->pixmaps[i], NULL);
611 gst_vaapi_decoder_replace(&app->decoder, NULL);
612 gst_vaapi_window_replace(&app->window, NULL);
613 gst_vaapi_display_replace(&app->display, NULL);
615 if (app->decoder_queue) {
616 g_async_queue_unref(app->decoder_queue);
617 app->decoder_queue = NULL;
619 g_cond_clear(&app->decoder_ready);
622 g_timer_destroy(app->timer);
626 g_cond_clear(&app->render_ready);
627 g_cond_clear(&app->event_cond);
628 g_mutex_clear(&app->mutex);
629 g_slice_free(App, app);
637 app = g_slice_new0(App);
641 g_mutex_init(&app->mutex);
642 g_cond_init(&app->event_cond);
643 g_cond_init(&app->decoder_ready);
644 g_cond_init(&app->render_ready);
646 app_set_framerate(app, 60, 1);
647 app->window_width = 640;
648 app->window_height = 480;
650 app->decoder_queue = g_async_queue_new_full(
651 (GDestroyNotify)render_frame_free);
652 if (!app->decoder_queue)
655 app->timer = g_timer_new();
666 app_check_events(App *app)
668 GError *error = NULL;
669 gboolean stop = FALSE;
672 g_mutex_lock(&app->mutex);
673 while (app->event == APP_RUNNING)
674 g_cond_wait(&app->event_cond, &app->mutex);
676 switch (app->event) {
687 g_mutex_unlock(&app->mutex);
693 g_message("%s error: %s", get_error_string(error->code), error->message);
699 app_run(App *app, int argc, char *argv[])
702 g_message("no bitstream file specified");
705 app->file_name = g_strdup(argv[1]);
707 if (!g_file_test(app->file_name, G_FILE_TEST_IS_REGULAR)) {
708 g_message("failed to find file '%s'", app->file_name);
712 app->codec = identify_codec(app->file_name);
714 app->codec = identify_codec_from_string(g_codec_str);
716 g_message("failed to identify codec for '%s'", app->file_name);
721 g_print("Simple decoder (%s bitstream)\n", string_from_codec(app->codec));
723 app->display = video_output_create_display(NULL);
725 g_message("failed to create VA display");
729 app->window = video_output_create_window(app->display,
730 app->window_width, app->window_height);
732 g_message("failed to create window");
736 gst_vaapi_window_show(app->window);
738 if (!start_decoder(app)) {
739 g_message("failed to start decoder thread");
743 if (!start_renderer(app)) {
744 g_message("failed to start renderer thread");
748 app_check_events(app);
753 g_print("Decoded %u frames", app->num_frames);
755 const gdouble elapsed = g_timer_elapsed(app->timer, NULL);
756 g_print(" in %.2f sec (%.1f fps)\n",
757 elapsed, (gdouble)app->num_frames / elapsed);
764 main(int argc, char *argv[])
769 if (!video_output_init(&argc, argv, g_options))
770 g_error("failed to initialize video output subsystem");
774 g_error("failed to create application context");
776 ret = !app_run(app, argc, argv);