gstreamer/tests/internal/simple-decoder.c
Matthew Waters d270654c48 gst: don't use volatile to mean atomic
volatile is not sufficient to provide atomic guarantees and real atomics
should be used instead.  GCC 11 has started warning about using volatile
with atomic operations.

https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719

Discovered in https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/868

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-vaapi/-/merge_requests/418>
2021-03-19 19:03:54 +11:00

691 lines
16 KiB
C

/*
* simple-decoder.c - Simple Decoder Application
*
* Copyright (C) 2013-2014 Intel Corporation
* Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
*
* 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 <stdarg.h>
#include <gst/vaapi/gstvaapidecoder.h>
#include <gst/vaapi/gstvaapidecoder_h264.h>
#include <gst/vaapi/gstvaapidecoder_jpeg.h>
#include <gst/vaapi/gstvaapidecoder_mpeg2.h>
#include <gst/vaapi/gstvaapidecoder_mpeg4.h>
#include <gst/vaapi/gstvaapidecoder_vc1.h>
#include <gst/vaapi/gstvaapiwindow.h>
#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;
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;
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, "<success>");
DEFINE_STATUS (END_OF_STREAM, "<EOS>");
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 = "<unknown>";
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, "<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 (!g_atomic_int_get (&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);
g_atomic_int_set (&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 (!g_atomic_int_get (&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)
{
g_atomic_int_set (&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;
}