gstreamer/tests/simple-decoder.c
2013-04-10 14:58:16 +02:00

712 lines
18 KiB
C

/*
* simple-decoder.c - Simple Decoder Application
*
* Copyright (C) 2013 Intel Corporation
*
* 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;
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;
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, "<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 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_user_data(proxy,
app, (GDestroyNotify)decoder_release);
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_caps(GObject *obj, GParamSpec *pspec, void *user_data)
{
App * const app = user_data;
GstVideoCodecState *codec_state;
g_assert(app->decoder == GST_VAAPI_DECODER(obj));
codec_state = gst_vaapi_decoder_get_codec_state(app->decoder);
app_set_framerate(app, codec_state->info.fps_n, codec_state->info.fps_d);
gst_video_codec_state_unref(codec_state);
}
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;
g_signal_connect(G_OBJECT(app->decoder), "notify::caps",
G_CALLBACK(handle_decoder_caps), app);
g_timer_start(app->timer);
app->decoder_thread = g_thread_create(decoder_thread, app, TRUE, 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;
#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);
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, NULL, 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_create(renderer_thread, app, TRUE, 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);
g_clear_object(&app->decoder);
g_clear_object(&app->window);
g_clear_object(&app->display);
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;
}