/* * 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 #include "codec.h" #include "output.h" static gchar *g_codec_str; static gboolean g_use_pixmap; static gboolean g_benchmark; static GOptionEntry g_options[] = { { "codec", 'c', 0, G_OPTION_ARG_STRING, &g_codec_str, "suggested codec", NULL }, { "pixmap", 0, 0, G_OPTION_ARG_NONE, &g_use_pixmap, "use render-to-pixmap", 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; GCond decoder_ready; GAsyncQueue *decoder_queue; GstVaapiCodec codec; guint fps_n; guint fps_d; guint32 frame_duration; guint surface_width; guint surface_height; GstVaapiPixmap *pixmaps[2]; guint pixmap_id; guint pixmap_width; guint pixmap_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 void decoder_release(App *app) { g_mutex_lock(&app->mutex); g_cond_signal(&app->decoder_ready); g_mutex_unlock(&app->mutex); } 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_surface, got_eos = FALSE; gint64 end_time; 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); get_surface: status = gst_vaapi_decoder_get_surface(app->decoder, &proxy); switch (status) { case GST_VAAPI_DECODER_STATUS_SUCCESS: gst_vaapi_surface_proxy_set_destroy_notify(proxy, (GDestroyNotify)decoder_release, app); 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; case GST_VAAPI_DECODER_STATUS_ERROR_NO_SURFACE: end_time = g_get_monotonic_time() + G_TIME_SPAN_SECOND; g_mutex_lock(&app->mutex); got_surface = g_cond_wait_until(&app->decoder_ready, &app->mutex, end_time); g_mutex_unlock(&app->mutex); if (got_surface) goto get_surface; SEND_ERROR("failed to acquire a surface within one second"); 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; #if USE_JPEG_DECODER case GST_VAAPI_CODEC_JPEG: app->decoder = gst_vaapi_decoder_jpeg_new(app->display, caps); break; #endif 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 gboolean ensure_pixmaps(App *app, GstVaapiSurface *surface, const GstVaapiRectangle *crop_rect) { GstVaapiPixmap *pixmaps[G_N_ELEMENTS(app->pixmaps)]; guint num_pixmaps, i, width, height; gboolean success = FALSE; if (crop_rect) { width = crop_rect->width; height = crop_rect->height; } else gst_vaapi_surface_get_size(surface, &width, &height); if (app->pixmap_width == width && app->pixmap_height == height) return TRUE; for (i = 0, num_pixmaps = 0; i < G_N_ELEMENTS(pixmaps); i++) { GstVaapiPixmap * const pixmap = video_output_create_pixmap(app->display, GST_VIDEO_FORMAT_xRGB, width, height); if (!pixmap) goto end; pixmaps[num_pixmaps++] = pixmap; } for (i = 0; i < num_pixmaps; i++) gst_vaapi_pixmap_replace(&app->pixmaps[i], pixmaps[i]); app->pixmap_width = width; app->pixmap_height = height; success = TRUE; end: for (i = 0; i < num_pixmaps; i++) gst_vaapi_pixmap_replace(&pixmaps[i], NULL); return success; } 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 (g_use_pixmap && !ensure_pixmaps(app, surface, crop_rect)) SEND_ERROR("failed to create intermediate pixmaps"); 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 (G_UNLIKELY(g_use_pixmap)) { GstVaapiPixmap * const pixmap = app->pixmaps[app->pixmap_id]; if (!gst_vaapi_pixmap_put_surface(pixmap, surface, crop_rect, GST_VAAPI_PICTURE_STRUCTURE_FRAME)) SEND_ERROR("failed to render to pixmap"); if (!gst_vaapi_window_put_pixmap(app->window, pixmap, NULL, NULL)) SEND_ERROR("failed to render surface %" GST_VAAPI_ID_FORMAT, GST_VAAPI_ID_ARGS(pixmap)); app->pixmap_id = (app->pixmap_id + 1) % G_N_ELEMENTS(app->pixmaps); } else 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) { guint i; if (!app) return; if (app->file) { g_mapped_file_unref(app->file); app->file = NULL; } g_free(app->file_name); for (i = 0; i < G_N_ELEMENTS(app->pixmaps); i++) gst_vaapi_pixmap_replace(&app->pixmaps[i], NULL); 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; } g_cond_clear(&app->decoder_ready); 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->decoder_ready); 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; }