/* * simple-decoder.c - Simple Decoder Application * * Copyright (C) 2013-2014 Intel Corporation * Author: Gwenole Beauchesne * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ /* * This is a really simple decoder application that only accepts raw * bitstreams. So, it may be needed to suggest what codec to use to * the application. */ #include "gst/vaapi/sysdeps.h" #include #include #include #include #include #include #include #include #include "codec.h" #include "output.h" static gchar *g_codec_str; static gboolean g_benchmark; static GOptionEntry g_options[] = { {"codec", 'c', 0, G_OPTION_ARG_STRING, &g_codec_str, "suggested codec", NULL}, {"benchmark", 0, 0, G_OPTION_ARG_NONE, &g_benchmark, "benchmark mode", NULL}, {NULL,} }; typedef enum { APP_RUNNING, APP_GOT_EOS, APP_GOT_ERROR, } AppEvent; typedef enum { APP_ERROR_NONE, APP_ERROR_DECODER, APP_ERROR_RENDERER, } AppError; typedef struct { GstVaapiSurfaceProxy *proxy; GstClockTime pts; GstClockTime duration; } RenderFrame; typedef struct { GMutex mutex; GMappedFile *file; gchar *file_name; guint file_offset; guint file_size; guchar *file_data; GstVaapiDisplay *display; GstVaapiDecoder *decoder; GThread *decoder_thread; volatile gboolean decoder_thread_cancel; GAsyncQueue *decoder_queue; GstVaapiCodec codec; guint fps_n; guint fps_d; guint32 frame_duration; guint surface_width; guint surface_height; GstVaapiWindow *window; guint window_width; guint window_height; GThread *render_thread; volatile gboolean render_thread_cancel; GCond render_ready; RenderFrame *last_frame; GError *error; AppEvent event; GCond event_cond; GTimer *timer; guint32 num_frames; } App; static inline RenderFrame * render_frame_new (void) { return g_slice_new (RenderFrame); } static void render_frame_free (RenderFrame * rfp) { if (G_UNLIKELY (!rfp)) return; gst_vaapi_surface_proxy_replace (&rfp->proxy, NULL); g_slice_free (RenderFrame, rfp); } static inline void render_frame_replace (RenderFrame ** rfp_ptr, RenderFrame * new_rfp) { if (*rfp_ptr) render_frame_free (*rfp_ptr); *rfp_ptr = new_rfp; } #define APP_ERROR app_error_quark() static GQuark app_error_quark (void) { static gsize g_quark; if (g_once_init_enter (&g_quark)) { gsize quark = (gsize) g_quark_from_static_string ("AppError"); g_once_init_leave (&g_quark, quark); } return g_quark; } static void app_send_error (App * app, GError * error) { g_mutex_lock (&app->mutex); app->error = error; app->event = APP_GOT_ERROR; g_cond_signal (&app->event_cond); g_mutex_unlock (&app->mutex); } static void app_send_eos (App * app) { g_mutex_lock (&app->mutex); app->event = APP_GOT_EOS; g_cond_signal (&app->event_cond); g_mutex_unlock (&app->mutex); } static const gchar * get_decoder_status_string (GstVaapiDecoderStatus status) { const gchar *str; #define DEFINE_STATUS(status, status_string) \ case GST_VAAPI_DECODER_STATUS_##status: \ str = status_string; \ break switch (status) { DEFINE_STATUS (SUCCESS, ""); DEFINE_STATUS (END_OF_STREAM, ""); DEFINE_STATUS (ERROR_ALLOCATION_FAILED, "allocation failed"); DEFINE_STATUS (ERROR_INIT_FAILED, "initialization failed"); DEFINE_STATUS (ERROR_UNSUPPORTED_CODEC, "unsupported codec"); DEFINE_STATUS (ERROR_NO_DATA, "not enough data"); DEFINE_STATUS (ERROR_NO_SURFACE, "no surface vailable"); DEFINE_STATUS (ERROR_INVALID_SURFACE, "invalid surface"); DEFINE_STATUS (ERROR_BITSTREAM_PARSER, "bitstream parser error"); DEFINE_STATUS (ERROR_UNSUPPORTED_PROFILE, "unsupported profile"); DEFINE_STATUS (ERROR_UNSUPPORTED_CHROMA_FORMAT, "unsupported chroma-format"); DEFINE_STATUS (ERROR_INVALID_PARAMETER, "invalid parameter"); default: str = ""; break; } #undef DEFINE_STATUS return str; } static const gchar * get_error_string (AppError error) { const gchar *str; #define DEFINE_ERROR(error, error_string) \ case APP_ERROR_##error: \ str = error_string; \ break switch (error) { DEFINE_ERROR (NONE, ""); DEFINE_ERROR (DECODER, "decoder"); DEFINE_ERROR (RENDERER, "renderer"); default: str = "unknown"; break; } #undef DEFINE_ERROR return str; } static gpointer decoder_thread (gpointer data) { App *const app = data; GError *error = NULL; GstVaapiDecoderStatus status; GstVaapiSurfaceProxy *proxy; RenderFrame *rfp; GstBuffer *buffer; GstClockTime pts; gboolean got_eos = FALSE; guint ofs; g_print ("Decoder thread started\n"); #define SEND_ERROR(...) \ do { \ error = g_error_new(APP_ERROR, APP_ERROR_DECODER, __VA_ARGS__); \ goto send_error; \ } while (0) pts = g_get_monotonic_time (); ofs = 0; while (!app->decoder_thread_cancel) { if (G_UNLIKELY (ofs == app->file_size)) buffer = NULL; else { const gsize size = MIN (4096, app->file_size - ofs); buffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, app->file_data, app->file_size, ofs, size, NULL, NULL); if (!buffer) SEND_ERROR ("failed to allocate new buffer"); ofs += size; } if (!gst_vaapi_decoder_put_buffer (app->decoder, buffer)) SEND_ERROR ("failed to push buffer to decoder"); gst_buffer_replace (&buffer, NULL); status = gst_vaapi_decoder_get_surface (app->decoder, &proxy); switch (status) { case GST_VAAPI_DECODER_STATUS_SUCCESS: rfp = render_frame_new (); if (!rfp) SEND_ERROR ("failed to allocate render frame"); rfp->proxy = proxy; rfp->pts = pts; rfp->duration = app->frame_duration; pts += app->frame_duration; g_async_queue_push (app->decoder_queue, rfp); break; case GST_VAAPI_DECODER_STATUS_ERROR_NO_DATA: /* nothing to do, just continue to the next iteration */ break; case GST_VAAPI_DECODER_STATUS_END_OF_STREAM: gst_vaapi_decoder_flush (app->decoder); if (got_eos) goto send_eos; got_eos = TRUE; break; default: SEND_ERROR ("%s", get_decoder_status_string (status)); break; } } return NULL; #undef SEND_ERROR send_eos: app_send_eos (app); return NULL; send_error: app_send_error (app, error); return NULL; } static void app_set_framerate (App * app, guint fps_n, guint fps_d) { if (!fps_n || !fps_d) return; g_mutex_lock (&app->mutex); if (fps_n != app->fps_n || fps_d != app->fps_d) { app->fps_n = fps_n; app->fps_d = fps_d; app->frame_duration = gst_util_uint64_scale (GST_TIME_AS_USECONDS (GST_SECOND), fps_d, fps_n); } g_mutex_unlock (&app->mutex); } static void handle_decoder_state_changes (GstVaapiDecoder * decoder, const GstVideoCodecState * codec_state, gpointer user_data) { App *const app = user_data; g_assert (app->decoder == decoder); app_set_framerate (app, codec_state->info.fps_n, codec_state->info.fps_d); } static gboolean start_decoder (App * app) { GstCaps *caps; app->file = g_mapped_file_new (app->file_name, FALSE, NULL); if (!app->file) return FALSE; app->file_size = g_mapped_file_get_length (app->file); app->file_data = (guint8 *) g_mapped_file_get_contents (app->file); if (!app->file_data) return FALSE; caps = caps_from_codec (app->codec); switch (app->codec) { case GST_VAAPI_CODEC_H264: app->decoder = gst_vaapi_decoder_h264_new (app->display, caps); break; case GST_VAAPI_CODEC_JPEG: app->decoder = gst_vaapi_decoder_jpeg_new (app->display, caps); break; case GST_VAAPI_CODEC_MPEG2: app->decoder = gst_vaapi_decoder_mpeg2_new (app->display, caps); break; case GST_VAAPI_CODEC_MPEG4: app->decoder = gst_vaapi_decoder_mpeg4_new (app->display, caps); break; case GST_VAAPI_CODEC_VC1: app->decoder = gst_vaapi_decoder_vc1_new (app->display, caps); break; default: app->decoder = NULL; break; } if (!app->decoder) return FALSE; gst_vaapi_decoder_set_codec_state_changed_func (app->decoder, handle_decoder_state_changes, app); g_timer_start (app->timer); app->decoder_thread = g_thread_try_new ("Decoder Thread", decoder_thread, app, NULL); if (!app->decoder_thread) return FALSE; return TRUE; } static gboolean stop_decoder (App * app) { g_timer_stop (app->timer); app->decoder_thread_cancel = TRUE; g_thread_join (app->decoder_thread); g_print ("Decoder thread stopped\n"); return TRUE; } static void ensure_window_size (App * app, GstVaapiSurface * surface) { guint width, height; if (gst_vaapi_window_get_fullscreen (app->window)) return; gst_vaapi_surface_get_size (surface, &width, &height); if (app->surface_width == width && app->surface_height == height) return; app->surface_width = width; app->surface_height = height; gst_vaapi_window_set_size (app->window, width, height); gst_vaapi_window_get_size (app->window, &app->window_width, &app->window_height); } static inline void renderer_wait_until (App * app, GstClockTime pts) { g_mutex_lock (&app->mutex); do { } while (g_cond_wait_until (&app->render_ready, &app->mutex, pts)); g_mutex_unlock (&app->mutex); } static gboolean renderer_process (App * app, RenderFrame * rfp) { GError *error = NULL; GstVaapiSurface *surface; const GstVaapiRectangle *crop_rect; #define SEND_ERROR(...) \ do { \ error = g_error_new(APP_ERROR, APP_ERROR_RENDERER, __VA_ARGS__); \ goto send_error; \ } while (0) surface = gst_vaapi_surface_proxy_get_surface (rfp->proxy); if (!surface) SEND_ERROR ("failed to get decoded surface from render frame"); ensure_window_size (app, surface); crop_rect = gst_vaapi_surface_proxy_get_crop_rect (rfp->proxy); if (!gst_vaapi_surface_sync (surface)) SEND_ERROR ("failed to sync decoded surface"); if (G_LIKELY (!g_benchmark)) renderer_wait_until (app, rfp->pts); if (!gst_vaapi_window_put_surface (app->window, surface, crop_rect, NULL, GST_VAAPI_PICTURE_STRUCTURE_FRAME)) SEND_ERROR ("failed to render surface %" GST_VAAPI_ID_FORMAT, GST_VAAPI_ID_ARGS (gst_vaapi_surface_get_id (surface))); app->num_frames++; render_frame_replace (&app->last_frame, rfp); return TRUE; #undef SEND_ERROR send_error: app_send_error (app, error); return FALSE; } static gpointer renderer_thread (gpointer data) { App *const app = data; RenderFrame *rfp; g_print ("Render thread started\n"); while (!app->render_thread_cancel) { rfp = g_async_queue_timeout_pop (app->decoder_queue, 1000000); if (rfp && !renderer_process (app, rfp)) break; } return NULL; } static gboolean flush_decoder_queue (App * app) { RenderFrame *rfp; /* Flush pending surfaces */ do { rfp = g_async_queue_try_pop (app->decoder_queue); if (!rfp) return TRUE; } while (renderer_process (app, rfp)); return FALSE; } static gboolean start_renderer (App * app) { app->render_thread = g_thread_try_new ("Renderer Thread", renderer_thread, app, NULL); if (!app->render_thread) return FALSE; return TRUE; } static gboolean stop_renderer (App * app) { app->render_thread_cancel = TRUE; g_thread_join (app->render_thread); g_print ("Render thread stopped\n"); flush_decoder_queue (app); render_frame_replace (&app->last_frame, NULL); return TRUE; } static void app_free (App * app) { if (!app) return; if (app->file) { g_mapped_file_unref (app->file); app->file = NULL; } g_free (app->file_name); gst_vaapi_decoder_replace (&app->decoder, NULL); gst_vaapi_window_replace (&app->window, NULL); gst_vaapi_display_replace (&app->display, NULL); if (app->decoder_queue) { g_async_queue_unref (app->decoder_queue); app->decoder_queue = NULL; } if (app->timer) { g_timer_destroy (app->timer); app->timer = NULL; } g_cond_clear (&app->render_ready); g_cond_clear (&app->event_cond); g_mutex_clear (&app->mutex); g_slice_free (App, app); } static App * app_new (void) { App *app; app = g_slice_new0 (App); if (!app) return NULL; g_mutex_init (&app->mutex); g_cond_init (&app->event_cond); g_cond_init (&app->render_ready); app_set_framerate (app, 60, 1); app->window_width = 640; app->window_height = 480; app->decoder_queue = g_async_queue_new_full ( (GDestroyNotify) render_frame_free); if (!app->decoder_queue) goto error; app->timer = g_timer_new (); if (!app->timer) goto error; return app; error: app_free (app); return NULL; } static gboolean app_check_events (App * app) { GError *error = NULL; gboolean stop = FALSE; do { g_mutex_lock (&app->mutex); while (app->event == APP_RUNNING) g_cond_wait (&app->event_cond, &app->mutex); switch (app->event) { case APP_GOT_ERROR: error = app->error; app->error = NULL; /* fall-through */ case APP_GOT_EOS: stop = TRUE; break; default: break; } g_mutex_unlock (&app->mutex); } while (!stop); if (!error) return TRUE; g_message ("%s error: %s", get_error_string (error->code), error->message); g_error_free (error); return FALSE; } static gboolean app_run (App * app, int argc, char *argv[]) { if (argc < 2) { g_message ("no bitstream file specified"); return FALSE; } app->file_name = g_strdup (argv[1]); if (!g_file_test (app->file_name, G_FILE_TEST_IS_REGULAR)) { g_message ("failed to find file '%s'", app->file_name); return FALSE; } app->codec = identify_codec (app->file_name); if (!app->codec) { app->codec = identify_codec_from_string (g_codec_str); if (!app->codec) { g_message ("failed to identify codec for '%s'", app->file_name); return FALSE; } } g_print ("Simple decoder (%s bitstream)\n", string_from_codec (app->codec)); app->display = video_output_create_display (NULL); if (!app->display) { g_message ("failed to create VA display"); return FALSE; } app->window = video_output_create_window (app->display, app->window_width, app->window_height); if (!app->window) { g_message ("failed to create window"); return FALSE; } gst_vaapi_window_show (app->window); if (!start_decoder (app)) { g_message ("failed to start decoder thread"); return FALSE; } if (!start_renderer (app)) { g_message ("failed to start renderer thread"); return FALSE; } app_check_events (app); stop_renderer (app); stop_decoder (app); g_print ("Decoded %u frames", app->num_frames); if (g_benchmark) { const gdouble elapsed = g_timer_elapsed (app->timer, NULL); g_print (" in %.2f sec (%.1f fps)\n", elapsed, (gdouble) app->num_frames / elapsed); } g_print ("\n"); return TRUE; } int main (int argc, char *argv[]) { App *app; gint ret; if (!video_output_init (&argc, argv, g_options)) g_error ("failed to initialize video output subsystem"); app = app_new (); if (!app) g_error ("failed to create application context"); ret = !app_run (app, argc, argv); app_free (app); g_free (g_codec_str); video_output_exit (); return ret; }