diff options
-rw-r--r-- | tests/Makefile.am | 7 | ||||
-rw-r--r-- | tests/simple-encoder.c | 514 | ||||
-rw-r--r-- | tests/y4mreader.c | 261 | ||||
-rw-r--r-- | tests/y4mreader.h | 40 |
4 files changed, 822 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am index 9b016813..fa1ee3a4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,6 +6,7 @@ noinst_PROGRAMS = \ test-surfaces \ test-windows \ test-subpicture \ + simple-encoder \ $(NULL) if USE_GLX @@ -122,6 +123,12 @@ simple_decoder_SOURCES = $(simple_decoder_source_c) simple_decoder_CFLAGS = $(TEST_CFLAGS) $(GST_VIDEO_CFLAGS) simple_decoder_LDADD = libutils.la $(TEST_LIBS) $(GST_VIDEO_LIBS) +simple_encoder_source_c = simple-encoder.c y4mreader.c +simple_encoder_source_h = y4mreader.h +simple_encoder_SOURCES = $(simple_encoder_source_c) +simple_encoder_CFLAGS = $(TEST_CFLAGS) $(GST_VIDEO_CFLAGS) +simple_encoder_LDADD = libutils.la $(TEST_LIBS) $(GST_VIDEO_LIBS) + EXTRA_DIST = \ test-subpicture-data.h \ $(simple_decoder_source_h) \ diff --git a/tests/simple-encoder.c b/tests/simple-encoder.c new file mode 100644 index 00000000..1a6f32c5 --- /dev/null +++ b/tests/simple-encoder.c @@ -0,0 +1,514 @@ +/* + * simple-encoder.c - Test GstVaapiencoder + * + * Copyright (C) 2015 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 + */ + +#include "gst/vaapi/sysdeps.h" +#include <gst/vaapi/gstvaapiencoder_mpeg2.h> +#include <gst/vaapi/gstvaapiencoder_h264.h> +#include <gst/vaapi/gstvaapisurfacepool.h> +#include <gst/vaapi/gstvaapisurfaceproxy.h> + +#include "output.h" +#include "y4mreader.h" + +static guint g_bitrate = 0; +static gchar *g_codec_str; +static gchar *g_output_file_name; +static char **g_input_files = NULL; + +#define SURFACE_NUM 16 + +static GOptionEntry g_options[] = { + {"codec", 'c', 0, G_OPTION_ARG_STRING, &g_codec_str, + "codec to use for video encoding (h264/mpeg2)", NULL}, + {"bitrate", 'b', 0, G_OPTION_ARG_INT, &g_bitrate, + "desired bitrate expressed in kbps", NULL}, + {"output", 'o', 0, G_OPTION_ARG_FILENAME, &g_output_file_name, + "output file name", NULL}, + {G_OPTION_REMAINING, ' ', 0, G_OPTION_ARG_FILENAME_ARRAY, &g_input_files, + "input file name", NULL}, + {NULL} +}; + +typedef struct +{ + GstVaapiDisplay *display; + GstVaapiEncoder *encoder; + guint read_frames; + guint encoded_frames; + guint saved_frames; + Y4MReader *parser; + FILE *output_file; + guint input_stopped:1; + guint encode_failed:1; +} App; + +static inline gchar * +generate_output_filename (const gchar * ext) +{ + gchar *fn; + int i = 0; + + while (1) { + fn = g_strdup_printf ("temp%02d.%s", i, ext); + if (g_file_test (fn, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { + i++; + g_free (fn); + } else { + break; + } + } + + return fn; +} + +static gboolean +parse_options (int *argc, char *argv[]) +{ + GOptionContext *ctx; + gboolean success; + GError *error = NULL; + + ctx = g_option_context_new (" - encoder test options"); + if (!ctx) + return FALSE; + + g_option_context_add_group (ctx, gst_init_get_option_group ()); + g_option_context_add_main_entries (ctx, g_options, NULL); + g_option_context_set_help_enabled (ctx, TRUE); + success = g_option_context_parse (ctx, argc, &argv, &error); + if (!success) { + g_printerr ("Option parsing failed: %s\n", error->message); + g_error_free (error); + goto bail; + } + + if (!g_codec_str) + g_codec_str = g_strdup ("h264"); + if (!g_output_file_name) + g_output_file_name = generate_output_filename (g_codec_str); + +bail: + g_option_context_free (ctx); + return success; +} + +static void +print_yuv_info (App * app) +{ + g_print ("\n"); + g_print ("Encode : %s\n", g_codec_str); + g_print ("Resolution : %dx%d\n", app->parser->width, app->parser->height); + g_print ("Source YUV : %s\n", g_input_files ? g_input_files[0] : "stdin"); + g_print ("Frame Rate : %0.1f fps\n", + 1.0 * app->parser->fps_n / app->parser->fps_d); + g_print ("Coded file : %s\n", g_output_file_name); + g_print ("\n"); +} + +static void +print_num_frame (App * app) +{ + g_print ("\n"); + g_print ("read frames : %d\n", app->read_frames); + g_print ("encoded frames : %d\n", app->encoded_frames); + g_print ("saved frames : %d\n", app->saved_frames); + g_print ("\n"); +} + +static GstVaapiEncoder * +encoder_new (GstVaapiDisplay * display) +{ + GstVaapiEncoder *encoder = NULL; + + if (!g_strcmp0 (g_codec_str, "mpeg2")) + encoder = gst_vaapi_encoder_mpeg2_new (display); + else if (!g_strcmp0 (g_codec_str, "h264")) + encoder = gst_vaapi_encoder_h264_new (display); + else + return NULL; + + gst_vaapi_encoder_set_bitrate (encoder, g_bitrate); + + return encoder; +} + +static GstVideoCodecState * +new_codec_state (gint width, gint height, gint fps_n, gint fps_d) +{ + GstVideoCodecState *state; + + state = g_slice_new0 (GstVideoCodecState); + state->ref_count = 1; + gst_video_info_init (&state->info); + gst_video_info_set_format (&state->info, GST_VIDEO_FORMAT_ENCODED, width, + height); + + state->info.fps_n = fps_n; + state->info.fps_d = fps_d; + + return state; +} + +static gboolean +set_format (GstVaapiEncoder * encoder, gint width, gint height, gint fps_n, + gint fps_d) +{ + GstVideoCodecState *in_state; + GstVaapiEncoderStatus status; + GstCaps *caps; + + if (!g_strcmp0 (g_codec_str, "mpeg2")) { + caps = gst_caps_from_string ("video/mpeg"); + gst_caps_set_simple (caps, + "mpegversion", G_TYPE_INT, 2, + "systemstream", G_TYPE_BOOLEAN, FALSE, + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL); + } else if (!g_strcmp0 (g_codec_str, "h264")) { + caps = gst_caps_new_empty_simple ("video/x-h264"); + gst_caps_set_simple (caps, "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL); + } else { + return FALSE; + } + + in_state = new_codec_state (width, height, fps_n, fps_d); + status = gst_vaapi_encoder_set_codec_state (encoder, in_state); + gst_caps_unref (caps); + g_slice_free (GstVideoCodecState, in_state); + + return (status == GST_VAAPI_ENCODER_STATUS_SUCCESS); +} + +static GstBuffer * +allocate_buffer (GstVaapiCodedBuffer * vbuf) +{ + GstBuffer *buf; + gssize size; + + size = gst_vaapi_coded_buffer_get_size (vbuf); + if (size <= 0) { + g_warning ("Invalid VAAPI buffer size (%d)", size); + return NULL; + } + + buf = gst_buffer_new_and_alloc (size); + if (!buf) { + g_warning ("Failed to create output buffer of size %d", size); + return NULL; + } + + if (!gst_vaapi_coded_buffer_copy_into (buf, vbuf)) { + g_warning ("Failed to copy VAAPI buffer data"); + gst_buffer_unref (buf); + return NULL; + } + + return buf; +} + +static GstVaapiEncoderStatus +get_encoder_buffer (GstVaapiEncoder * encoder, GstBuffer ** buffer) +{ + GstVaapiCodedBufferProxy *proxy = NULL; + GstVaapiEncoderStatus status; + + status = gst_vaapi_encoder_get_buffer_with_timeout (encoder, &proxy, 50000); + if (status < GST_VAAPI_ENCODER_STATUS_SUCCESS) { + g_warning ("Failed to get a buffer from encoder: %d", status); + return status; + } else if (status > GST_VAAPI_ENCODER_STATUS_SUCCESS) { + return status; + } + + *buffer = allocate_buffer (GST_VAAPI_CODED_BUFFER_PROXY_BUFFER (proxy)); + gst_vaapi_coded_buffer_proxy_unref (proxy); + + return status; +} + +static gboolean +outputs_to_file (GstBuffer * buffer, FILE * file) +{ + GstMapInfo info; + size_t written; + gboolean ret = FALSE; + + gst_buffer_map (buffer, &info, GST_MAP_READ); + + if (info.size <= 0 || !info.data) + return FALSE; + + written = fwrite (info.data, 1, info.size, file); + if (written < info.size) { + g_warning ("write file error."); + goto bail; + } + + ret = TRUE; + +bail: + gst_buffer_unmap (buffer, &info); + return ret; +} + +static gpointer +get_buffer_thread (gpointer data) +{ + App *app = data; + + GstVaapiEncoderStatus ret; + GstBuffer *obuf; + + while (1) { + obuf = NULL; + ret = get_encoder_buffer (app->encoder, &obuf); + if (app->input_stopped && ret > GST_VAAPI_ENCODER_STATUS_SUCCESS) { + break; /* finished */ + } else if (ret > GST_VAAPI_ENCODER_STATUS_SUCCESS) { /* another chance */ + continue; + } + if (ret < GST_VAAPI_ENCODER_STATUS_SUCCESS) { /* fatal error */ + app->encode_failed = TRUE; + break; + } + + app->encoded_frames++; + g_debug ("encoded frame %d, buffer = %p", app->encoded_frames, obuf); + + if (app->output_file && outputs_to_file (obuf, app->output_file)) + app->saved_frames++; + + gst_buffer_unref (obuf); + } + + if (obuf) + gst_buffer_replace (&obuf, NULL); + + return NULL; +} + +static void +app_free (App * app) +{ + g_return_if_fail (app); + + if (app->parser) + y4m_reader_close (app->parser); + + if (app->encoder) { + gst_vaapi_encoder_flush (app->encoder); + gst_vaapi_encoder_unref (app->encoder); + } + + if (app->display) + gst_vaapi_display_unref (app->display); + + if (app->output_file) + fclose (app->output_file); + + g_slice_free (App, app); +} + +static App * +app_new (const gchar * input_fn, const gchar * output_fn) +{ + App *app = g_slice_new0 (App); + if (!app) + return NULL; + + app->parser = y4m_reader_open (input_fn); + if (!app->parser) { + g_warning ("Could not parse input stream."); + goto error; + } + + app->output_file = fopen (output_fn, "w"); + if (app->output_file == NULL) { + g_warning ("Could not open file \"%s\" for writing: %s.", output_fn, + g_strerror (errno)); + goto error; + } + + app->display = video_output_create_display (NULL); + if (!app->display) { + g_warning ("Could not create VA display."); + goto error; + } + + app->encoder = encoder_new (app->display); + if (!app->encoder) { + g_warning ("Could not create encoder."); + goto error; + } + + if (!set_format (app->encoder, app->parser->width, app->parser->height, + app->parser->fps_n, app->parser->fps_d)) { + g_warning ("Could not set format."); + goto error; + } + + return app; + +error: + app_free (app); + return NULL; +} + +static gboolean +upload_frame (GstVaapiEncoder * encoder, GstVaapiSurfaceProxy * proxy) +{ + GstVideoCodecFrame *frame; + GstVaapiEncoderStatus ret; + + frame = g_slice_new0 (GstVideoCodecFrame); + gst_video_codec_frame_set_user_data (frame, + gst_vaapi_surface_proxy_ref (proxy), + (GDestroyNotify) gst_vaapi_surface_proxy_unref); + + ret = gst_vaapi_encoder_put_frame (encoder, frame); + return (ret == GST_VAAPI_ENCODER_STATUS_SUCCESS); +} + +static gboolean +load_frame (App * app, GstVaapiImage * image) +{ + gboolean ret = FALSE; + + if (!gst_vaapi_image_map (image)) + return FALSE; + + ret = y4m_reader_load_image (app->parser, image); + + if (!gst_vaapi_image_unmap (image)) + return FALSE; + + return ret; +} + +static int +app_run (App * app) +{ + GstVaapiImage *image; + GstVaapiVideoPool *pool; + GThread *buffer_thread; + gsize id; + int ret = EXIT_FAILURE; + + image = gst_vaapi_image_new (app->display, GST_VIDEO_FORMAT_I420, + app->parser->width, app->parser->height); + + { + GstVideoInfo vi; + gst_video_info_set_format (&vi, GST_VIDEO_FORMAT_ENCODED, + app->parser->width, app->parser->height); + pool = gst_vaapi_surface_pool_new_full (app->display, &vi, 0); + } + + buffer_thread = g_thread_new ("get buffer thread", get_buffer_thread, app); + + while (1) { + if (!load_frame (app, image)) + break; + + if (!gst_vaapi_image_unmap (image)) + break; + + GstVaapiSurfaceProxy *proxy = + gst_vaapi_surface_proxy_new_from_pool (GST_VAAPI_SURFACE_POOL (pool)); + if (!proxy) { + g_warning ("Could not get surface proxy from pool."); + break; + } + GstVaapiSurface *surface = gst_vaapi_surface_proxy_get_surface (proxy); + if (!surface) { + g_warning ("Could not get surface from proxy."); + break; + } + + if (!gst_vaapi_surface_put_image (surface, image)) { + g_warning ("Could not update surface"); + break; + } + + if (!upload_frame (app->encoder, proxy)) { + g_warning ("put frame failed"); + break; + } + + app->read_frames++; + id = gst_vaapi_surface_get_id (surface); + g_debug ("input frame %d, surface id = %lu", app->read_frames, id); + + gst_vaapi_surface_proxy_unref (proxy); + } + + app->input_stopped = TRUE; + + g_thread_join (buffer_thread); + + if (!app->encode_failed && feof (app->parser->fp)) + ret = EXIT_SUCCESS; + + gst_vaapi_video_pool_replace (&pool, NULL); + gst_vaapi_object_unref (image); + return ret; +} + +int +main (int argc, char *argv[]) +{ + App *app; + int ret = EXIT_FAILURE; + gchar *input_fn; + + if (!parse_options (&argc, argv)) + return EXIT_FAILURE; + + /* @TODO: iterate all the input files */ + input_fn = g_input_files ? g_input_files[0] : NULL; + if (input_fn && !g_file_test (input_fn, + G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { + g_warning ("input file \"%s\" doesn't exist", input_fn); + goto bail; + } + + app = app_new (input_fn, g_output_file_name); + if (!app) + goto bail; + + print_yuv_info (app); + ret = app_run (app); + print_num_frame (app); + + app_free (app); + +bail: + g_free (g_codec_str); + g_free (g_output_file_name); + g_strfreev (g_input_files); + + gst_deinit (); + + return ret; +} diff --git a/tests/y4mreader.c b/tests/y4mreader.c new file mode 100644 index 00000000..aeb1d808 --- /dev/null +++ b/tests/y4mreader.c @@ -0,0 +1,261 @@ +/* + * y4mreader.c - Y4M parser + * + * Copyright (C) 2015 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 + */ + +#include "y4mreader.h" + +#include <stdio.h> + +/* format documentation: + * http://wiki.multimedia.cx/index.php?title=YUV4MPEG2 */ + +static inline gboolean +parse_int (const gchar * str, glong * out_value_ptr) +{ + gint saved_errno; + glong value; + gboolean ret; + + if (!str) + return FALSE; + str += 1; + if (!str) + return FALSE; + + saved_errno = errno; + errno = 0; + value = strtol (str, NULL, 0); + ret = (errno == 0); + errno = saved_errno; + *out_value_ptr = value; + + return ret; +} + +static gboolean +parse_header (Y4MReader * file) +{ + gint i, j; + guint8 header[BUFSIZ]; + gint8 b; + size_t s; + gchar *str; + + memset (header, 0, BUFSIZ); + s = fread (header, 1, 9, file->fp); + if (s < 9) + return FALSE; + + if (memcmp (header, "YUV4MPEG2", 9) != 0) + return FALSE; + + for (i = 9; i < BUFSIZ - 1; i++) { + b = fgetc (file->fp); + if (b == EOF) + return FALSE; + if (b == 0xa) + break; + header[i] = b; + } + + if (i == BUFSIZ - 1) + return FALSE; + + j = 9; + while (j < i) { + if ((header[j] != 0x20) && (header[j - 1] == 0x20)) { + switch (header[j]) { + case 'W': + if (!parse_int ((gchar *) & header[j], (glong *) & file->width)) + return FALSE; + break; + case 'H': + if (!parse_int ((gchar *) & header[j], (glong *) & file->height)) + return FALSE; + break; + case 'C': + str = (char *) &header[j + 1]; + if (strncmp (str, "420", 3) != 0) { + g_warning ("Unsupported chroma subsampling."); + return FALSE; /* unsupported chroma subsampling */ + } + break; + case 'I': + str = (char *) &header[j + 1]; + if (*str != 'p' && *str != '?') { + g_warning ("Interlaced content are not supported."); + return FALSE; /* interlaced is unsupported */ + } + break; + case 'F': /* frame rate ratio */ + { + guint num, den; + + if (!parse_int ((gchar *) & header[j], (glong *) & num)) + return FALSE; + while ((header[j] != ':') && (j < i)) + j++; + if (!parse_int ((gchar *) & header[j], (glong *) & den)) + return FALSE; + + if (num <= 0 || den <= 0) { + file->fps_n = 30; /* default to 30 fps */ + file->fps_d = 1; + } else { + file->fps_n = num; + file->fps_d = den; + } + break; + } + case 'A': /* sample aspect ration */ + break; + case 'X': /* metadata */ + break; + default: + break; + } + } + j++; + } + + return TRUE; +} + +Y4MReader * +y4m_reader_open (const gchar * filename) +{ + Y4MReader *imagefile; + + imagefile = g_slice_new0 (Y4MReader); + + if (filename) { + imagefile->fp = fopen (filename, "r"); + if (!imagefile->fp) { + g_warning ("open file %s error", filename); + goto bail; + } + } else { + imagefile->fp = stdin; + } + + if (!parse_header (imagefile)) + goto bail; + + return imagefile; + +bail: + if (imagefile->fp && imagefile->fp != stdin) + fclose (imagefile->fp); + + g_slice_free (Y4MReader, imagefile); + return NULL; +} + +void +y4m_reader_close (Y4MReader * file) +{ + g_return_if_fail (file); + + if (file->fp && file->fp != stdin) + fclose (file->fp); + + g_slice_free (Y4MReader, file); +} + +static gboolean +skip_frame_header (Y4MReader * file) +{ + gint i; + guint8 header[BUFSIZ]; + gint8 b; + size_t s; + + memset (header, 0, BUFSIZ); + s = fread (header, 1, 5, file->fp); + if (s < 5) + return FALSE; + + if (memcmp (header, "FRAME", 5) != 0) + return FALSE; + + for (i = 5; i < BUFSIZ - 1; i++) { + b = fgetc (file->fp); + if (b == EOF) + return FALSE; + if (b == 0xa) + break; + header[i] = b; + } + + return (i < BUFSIZ - 1); +} + +gboolean +y4m_reader_load_image (Y4MReader * file, GstVaapiImage * image) +{ + guint8 *plane; + size_t s; + guint frame_size, stride, i; + + g_return_val_if_fail (gst_vaapi_image_is_mapped (image), FALSE); + g_return_val_if_fail (file && file->fp, FALSE); + + /* only valid for I420 */ + frame_size = file->height * file->width * 3 / 2; + if (gst_vaapi_image_get_data_size (image) < frame_size) + return FALSE; + if (gst_vaapi_image_get_plane_count (image) != 3) + return FALSE; + + if (!skip_frame_header (file)) + return FALSE; + + /* Y plane */ + plane = gst_vaapi_image_get_plane (image, 0); + stride = gst_vaapi_image_get_pitch (image, 0); + for (i = 0; i < file->height; i++) { + s = fread (plane, 1, file->width, file->fp); + if (s != file->width) + return FALSE; + plane += stride; + } + + /* U plane */ + plane = gst_vaapi_image_get_plane (image, 1); + stride = gst_vaapi_image_get_pitch (image, 1); + for (i = 0; i < file->height / 2; i++) { + s = fread (plane, 1, file->width / 2, file->fp); + if (s != file->width / 2) + return FALSE; + plane += stride; + } + + /* V plane */ + plane = gst_vaapi_image_get_plane (image, 2); + stride = gst_vaapi_image_get_pitch (image, 2); + for (i = 0; i < file->height / 2; i++) { + s = fread (plane, 1, file->width / 2, file->fp); + if (s != file->width / 2) + return FALSE; + plane += stride; + } + + return TRUE; +} diff --git a/tests/y4mreader.h b/tests/y4mreader.h new file mode 100644 index 00000000..30d754bc --- /dev/null +++ b/tests/y4mreader.h @@ -0,0 +1,40 @@ +/* + * y4mreader.h - Y4M parser + * + * Copyright (C) 2015 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 + */ + +#include "gst/vaapi/sysdeps.h" +#include <gst/vaapi/gstvaapiimage.h> + +typedef struct _Y4MReader Y4MReader; + +struct _Y4MReader +{ + FILE *fp; + guint width; + guint height; + gint fps_n; + gint fps_d; +}; + +Y4MReader *y4m_reader_open (const gchar * filename); + +void y4m_reader_close (Y4MReader * file); + +gboolean y4m_reader_load_image (Y4MReader * file, GstVaapiImage * image); |