Hi, On Wed, Oct 19, 2016 at 04:23:23PM +0200, Victor Toso wrote: > Hi, > > On Wed, Oct 19, 2016 at 02:49:38PM +0100, Frediano Ziglio wrote: > > Add an utility to make possible to check various features of > > VideoEncoder. > > 2 GStreamer plugins are used in a chain like this: > > (1) input pipeline -> (2) video encoder -> (3) output pipeline > > While converting output from (1) is compared with output of (3) > > making sure the streaming is working correctly. > > You can set various options: > > - part of the input pipeline description to allow specifying different > > video from GStreamer test ones to a video file; > > - the encoder to use; > > - different image properties to use for (2) input: > > - different bit depth; > > - top/down or down/up; > > - initial bitrate. > > > > The idea is to use this helper in combination with a shell script > > and some video sources to make able to test various settings. > > Also can be used to extend the current encoder list. > > > > As an example you can use a command like > > > > $ ./gst-test -e gstreamer:vp8 -i \ > > 'filesrc location=bbb_sunflower_1080p_30fps_normal.mp4 \ > > ! decodebin ! videoconvert' > > > > to check vp8 encoding. > > Great to warm ourselves in this cold winter! </joke> > > > > Currently it does not emulate bandwidth changes as stream reports > > from the client are not coded. > > Seems to work well here, I tried vp8 too. > I guess it would not hard to simulate latency or bandwith by playing > with queue element although we might want to keep that as TODO for > now. Ah, might bad. Did not see we actually have handlers for that in VideoEncoderRateControlCbs > > Also, it would be nice to use videotestsrc by default when we do make > check. > > toso > > > > > Signed-off-by: Frediano Ziglio <fziglio@xxxxxxxxxx> > > --- > > server/tests/Makefile.am | 9 + > > server/tests/gst-test.c | 891 +++++++++++++++++++++++++++++++++++++++++++++++ > > 2 files changed, 900 insertions(+) > > create mode 100644 server/tests/gst-test.c > > > > First stuff I can do is remove the > > > > The XXX format has not been tested yet > > > > message coming out from gstreamer-encoder.c. > > > > diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am > > index 8580a9a..c799779 100644 > > --- a/server/tests/Makefile.am > > +++ b/server/tests/Makefile.am > > @@ -57,6 +57,7 @@ noinst_PROGRAMS = \ > > test_vdagent \ > > test_display_width_stride \ > > spice-server-replay \ > > + gst-test \ > > $(check_PROGRAMS) \ > > $(NULL) > > > > @@ -105,3 +106,11 @@ libstat_test4_a_SOURCES = stat-test.c > > libstat_test4_a_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_COMPRESS_STAT=1 -DTEST_RED_WORKER_STAT=1 -DTEST_NAME=stat_test4 > > > > test_qxl_parsing_LDADD = ../libserver.la $(LDADD) > > + > > +gst_test_SOURCES = gst-test.c \ > > + $(NULL) > > +gst_test_CPPFLAGS = \ > > + $(AM_CPPFLAGS) \ > > + $(GSTREAMER_0_10_CFLAGS) \ > > + $(GSTREAMER_1_0_CFLAGS) \ > > + $(NULL) > > diff --git a/server/tests/gst-test.c b/server/tests/gst-test.c > > new file mode 100644 > > index 0000000..8bde72a > > --- /dev/null > > +++ b/server/tests/gst-test.c > > @@ -0,0 +1,891 @@ > > +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ > > +/* > > + Copyright (C) 2016 Red Hat, Inc. > > + > > + 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, see <http://www.gnu.org/licenses/>. > > +*/ > > +/* Utility to check video encoder code > > + */ > > +#undef NDEBUG > > +#include <config.h> > > +#include <stdio.h> > > +#include <glib.h> > > +#include <string.h> > > +#include <errno.h> > > +#include <math.h> > > +#include <common/log.h> > > +#include <gst/gst.h> > > +#include <gst/app/gstappsrc.h> > > +#include <gst/app/gstappsink.h> > > + > > +#include "video-encoder.h" > > +#include "spice-bitmap-utils.h" > > +#include "reds.h" // reds_get_mm_time > > + > > +// clipping informations passed in command line > > +typedef enum { > > + COORDS_INVALID, > > + COORDS_NUMBER, > > + COORDS_PERCENT, > > +} CoordsUnit; > > +static struct { > > + unsigned int value; > > + CoordsUnit unit; > > +} clipping_coords[4]; > > +enum { > > + COORDS_BOX, > > + COORDS_SIZE > > +} clipping_type; > > + > > +typedef struct { > > + gint refs; > > + SpiceBitmap *bitmap; > > +} TestFrame; > > + > > +typedef void (*SampleProc)(GstSample *sample, void *param); > > + > > +typedef struct { > > + GstAppSrc *appsrc; > > + GstAppSink *appsink; > > + GstElement *gst_pipeline; > > + SampleProc sample_proc; > > + void *sample_param; > > +} TestPipeline; > > + > > +typedef struct { > > + const char *name; > > + new_video_encoder_t new_encoder; > > + SpiceVideoCodecType coded_type; > > + // see spice-gtk channel-display-gst.c > > + const char *caps_dec; > > +} EncoderInfo; > > + > > +// our video encoder we are testing > > +static VideoEncoder *video_encoder = NULL; > > + > > +// image settings > > +static gboolean top_down = FALSE; > > +static SpiceBitmapFmt bitmap_format = SPICE_BITMAP_FMT_32BIT; > > + > > +static gboolean clipping_type_computed = FALSE; > > +static SpiceRect clipping_rect; > > +static GAsyncQueue *frame_queue = NULL; > > +static TestPipeline *input_pipeline, *output_pipeline; > > +// minimum image different expected, depends on quality > > +// and encoder > > +static double minimum_psnr = 25; > > +static uint64_t starting_bit_rate = 3000000; > > + > > +static void compute_clipping_rect(GstSample *sample); > > +static void parse_clipping(const char *clipping); > > +static TestFrame *gst_to_spice_frame(GstSample *sample); > > +static void bitmap_free(SpiceBitmap *bitmap); > > +static void frame_ref(TestFrame *frame); > > +static void frame_unref(TestFrame *frame); > > +static void pipeline_send_raw_data(TestPipeline *pipeline, VideoBuffer *buffer); > > +static void create_input_pipeline(const char *input_pipeline, > > + SampleProc sample_proc, void *param); > > +static void create_output_pipeline(const EncoderInfo *encoder, > > + SampleProc output_frames, void *param); > > +static void create_video_encoder(const EncoderInfo *encoder); > > +static const EncoderInfo *get_encoder_info(const char *encoder_name); > > +static SpiceBitmapFmt get_bitmap_format(const char *format); > > +static double compute_psnr(SpiceBitmap *bitmap1, int32_t x1, int32_t y1, > > + SpiceBitmap *bitmap2, int32_t x2, int32_t y2, > > + int32_t w, int32_t h); > > + > > +// handle output frames from input pipeline > > +static void > > +input_frames(GstSample *sample, void *param) > > +{ > > + spice_assert(video_encoder && frame_queue && sample); > > + > > + if (SPICE_UNLIKELY(!clipping_type_computed)) { > > + compute_clipping_rect(sample); > > + } > > + > > + VideoBuffer *p_outbuf = NULL; > > + // TODO correct ?? emulate another timer ?? > > + uint32_t frame_mm_time = reds_get_mm_time(); > > + > > + // convert frame to SpiceBitmap/DRM prime > > + TestFrame *frame = gst_to_spice_frame(sample); > > + > > + // send frame to our video encoder (must be from a single thread) > > + int res = video_encoder->encode_frame(video_encoder, frame_mm_time, frame->bitmap, > > + &clipping_rect, top_down, frame, > > + &p_outbuf); > > + switch (res) { > > + case VIDEO_ENCODER_FRAME_ENCODE_DONE: > > + // save frame into queue for comparison later > > + frame_ref(frame); > > + g_async_queue_push(frame_queue, frame); > > + spice_assert(p_outbuf); > > + pipeline_send_raw_data(output_pipeline, p_outbuf); > > + break; > > + case VIDEO_ENCODER_FRAME_UNSUPPORTED: > > + // ?? what to do ?? > > + // should not happen, format passed should be supported > > + // could happen for serious problems and encoder gave up > > + spice_assert(0); > > + break; > > + case VIDEO_ENCODER_FRAME_DROP: > > + break; > > + default: > > + // invalid value returned > > + spice_assert(0); > > + } > > + > > + // TODO call client_stream_report to simulate this report from the client > > + > > + frame_unref(frame); > > +} > > + > > +// handle output frames from output pipeline > > +static void > > +output_frames(GstSample *sample, void *param) > > +{ > > + TestFrame *curr_frame = gst_to_spice_frame(sample); > > + > > + // get first frame queued > > + TestFrame *expected_frame = g_async_queue_try_pop(frame_queue); > > + if (!expected_frame) { > > + g_printerr("Frame not present in the queue but arrived in output!\n"); > > + exit(1); > > + } > > + > > + // TODO try to understand if this is correct > > + if (!top_down) { > > + expected_frame->bitmap->flags ^= SPICE_BITMAP_FLAGS_TOP_DOWN; > > + } > > +#ifdef DUMP_BITMAP > > + dump_bitmap(curr_frame->bitmap); > > + dump_bitmap(expected_frame->bitmap); > > +#endif > > + > > + // compute difference > > + double psnr = compute_psnr(curr_frame->bitmap, clipping_rect.left, clipping_rect.top, > > + expected_frame->bitmap, 0, 0, > > + clipping_rect.right - clipping_rect.left, > > + clipping_rect.bottom - clipping_rect.top); > > + > > + // check is more or less the same > > + if (psnr < minimum_psnr) { > > + g_printerr("Frame PSNR too low, got %g minimum %g\n", psnr, minimum_psnr); > > + exit(1); > > + } > > + > > + frame_unref(expected_frame); > > + frame_unref(curr_frame); > > +} > > + > > +static const EncoderInfo encoder_infos[] = { > > + { "mjpeg", mjpeg_encoder_new, SPICE_VIDEO_CODEC_TYPE_MJPEG, > > + "caps=image/jpeg ! jpegdec" }, > > + { "gstreamer:mjpeg", gstreamer_encoder_new, SPICE_VIDEO_CODEC_TYPE_MJPEG, > > + "caps=image/jpeg ! jpegdec" }, > > + { "gstreamer:vp8", gstreamer_encoder_new, SPICE_VIDEO_CODEC_TYPE_VP8, > > + "caps=video/x-vp8 ! vp8dec" }, > > + { "gstreamer:h264", gstreamer_encoder_new, SPICE_VIDEO_CODEC_TYPE_H264, > > + "! h264parse ! avdec_h264" }, > > + { NULL, NULL } > > +}; > > + > > +int main(int argc, char *argv[]) > > +{ > > + gchar *input_pipeline_desc = NULL; > > + gchar *image_format = "32BIT"; > > + gchar *encoder_name = "mjpeg"; > > + gboolean use_hw_encoder = FALSE; // TODO use > > + gchar *clipping = "(0,0)x(100%,100%)"; > > + > > + frame_queue = g_async_queue_new(); > > + > > + // - input pipeline > > + // - top/down > > + // - format for video encoder input (bits, rgb/bgr) > > + // - encoder (mjpeg/vp8/h264) > > + // - use h/w acceleration (if available) > > + // - clipping (part of the source) > > + // - TODO bandwidth changes? > > + // - TODO fps ?? > > + GOptionEntry entries[] = { > > + { "input-pipeline", 'i', 0, G_OPTION_ARG_STRING, &input_pipeline_desc, > > + "GStreamer input pipeline", "PIPELINE" }, > > + { "top-down", 0, 0, G_OPTION_ARG_NONE, &top_down, > > + "Image encoded as top-down", NULL }, > > + { "format", 'f', 0, G_OPTION_ARG_STRING, &image_format, > > + "Image format (16BIT/24BIT/32BIT/RGBA)", "FMT" }, > > + { "encoder", 'e', 0, G_OPTION_ARG_STRING, &encoder_name, > > + "Encoder to use", "ENC" }, > > + { "use-hw-encoder", 0, 0, G_OPTION_ARG_NONE, &use_hw_encoder, > > + "Use H/W encoders if possible", NULL }, > > + { "clipping", 0, 0, G_OPTION_ARG_STRING, &clipping, > > + "Clipping region (x1,y1)-(x2,y2) or (x,y)x(w,h)", "STRING" }, > > + { "starting-bitrate", 0, 0, G_OPTION_ARG_INT64, &starting_bit_rate, > > + "Initial bitrate", "BITRATE" }, > > + { NULL } > > + }; > > + > > + GOptionContext *context = NULL; > > + GError *error = NULL; > > + context = g_option_context_new("- helper for testing VideoEncoder"); > > + g_option_context_add_main_entries(context, entries, NULL); > > + if (!g_option_context_parse(context, &argc, &argv, &error)) { > > + g_printerr("Option parsing failed: %s\n", error->message); > > + exit(1); > > + } > > + > > + if (!input_pipeline_desc) { > > + g_printerr("Input pipeline option missing\n"); > > + exit(1); > > + } > > + > > + if (!encoder_name) { > > + g_printerr("Encoder name option missing\n"); > > + exit(1); > > + } > > + > > + const EncoderInfo *encoder = get_encoder_info(encoder_name); > > + if (!encoder) { > > + g_printerr("Encoder name unsupported: %s\n", encoder_name); > > + exit(1); > > + } > > + > > + bitmap_format = get_bitmap_format(image_format); > > + if (bitmap_format == SPICE_BITMAP_FMT_INVALID) { > > + g_printerr("Invalid image format: %s\n", image_format); > > + exit(1); > > + } > > + > > + parse_clipping(clipping); > > + > > + gst_init(&argc, &argv); > > + > > + // TODO give particular error if pipeline fails to be created > > + > > + create_output_pipeline(encoder, output_frames, NULL); > > + > > + create_video_encoder(encoder); > > + > > + create_input_pipeline(input_pipeline_desc, input_frames, NULL); > > + > > + // run all input streaming > > + GstBus *bus = gst_element_get_bus(input_pipeline->gst_pipeline); > > + GstMessage *eos = gst_bus_poll(bus, GST_MESSAGE_EOS, -1); > > + spice_assert(eos); > > + gst_object_unref(bus); > > + > > + video_encoder->destroy(video_encoder); > > + > > + // send EOS to output and wait > > + // this assure we processed all frames sent from input pipeline > > + if (gst_app_src_end_of_stream(output_pipeline->appsrc) != GST_FLOW_OK) { > > + g_printerr("gst_app_src_end_of_stream failed\n"); > > + exit(1); > > + } > > + bus = gst_element_get_bus(output_pipeline->gst_pipeline); > > + eos = gst_bus_poll(bus, GST_MESSAGE_EOS, 4); > > + spice_assert(eos); > > + gst_object_unref(bus); > > + > > + // check queue is now empty > > + TestFrame *frame = g_async_queue_try_pop(frame_queue); > > + if (frame) { > > + g_printerr("Queue not empty at the end\n"); > > + exit(1); > > + } > > + > > + return 0; > > +} > > + > > +static void > > +parse_clipping(const char *clipping) > > +{ > > + spice_assert(clipping); > > + > > +#define NUM_FMT "%31[^,)]" > > +#define NUM(n) coords[n] > > + char coords[4][32]; > > + char clipping_type_sign[2]; > > + int i; > > + > > + if (sscanf(clipping, "(" NUM_FMT "," NUM_FMT ")%1[x-](" NUM_FMT "," NUM_FMT ")", > > + NUM(0), NUM(1), clipping_type_sign, NUM(2), NUM(3)) < 5) { > > + goto format_error; > > + } > > + for (i = 0; i < 4; ++i) { > > + char *end = NULL; > > + errno = 0; > > + clipping_coords[i].unit = COORDS_NUMBER; > > + clipping_coords[i].value = strtoul(coords[i], &end, 10); > > + if (errno || !end || (strcmp(end, "") != 0 && strcmp(end, "%") != 0)) { > > + goto format_error; > > + } > > + if (strcmp(end, "%") == 0) { > > + clipping_coords[i].unit = COORDS_PERCENT; > > + if (clipping_coords[i].value > 100) { > > + goto format_error; > > + } > > + } > > + } > > + if (clipping_type_sign[0] == 'x') { > > + clipping_type = COORDS_SIZE; > > + } else { > > + clipping_type = COORDS_BOX; > > + } > > + return; > > + > > +format_error: > > + g_printerr("Invalid clipping format: %s\n", clipping); > > + exit(1); > > + > > +} > > + > > +static void > > +compute_clipping_rect(GstSample *sample) > > +{ > > + GstCaps *caps = gst_sample_get_caps(sample); > > + spice_assert(caps); > > + > > + GstStructure *s = gst_caps_get_structure(caps, 0); > > + spice_assert(s); > > + > > + gint width, height; > > + spice_assert(gst_structure_get_int(s, "width", &width) && > > + gst_structure_get_int(s, "height", &height)); > > + > > + // transform from percent to pixel values > > + int i; > > + unsigned int coords[4]; > > + for (i = 0; i < 4; ++i) { > > + unsigned int coord = coords[i] = clipping_coords[i].value; > > + if (clipping_coords[i].unit != COORDS_PERCENT) { > > + spice_assert(clipping_coords[i].unit == COORDS_NUMBER); > > + continue; > > + } > > + coords[i] = coord * ((i&1) ? height : width) / 100; > > + } > > + > > + // transform from sized to box > > + if (clipping_type == COORDS_SIZE) { > > + coords[2] += coords[0]; > > + coords[3] += coords[1]; > > + } > > + > > + // clip to sample > > + coords[0] = MIN(coords[0], width); > > + coords[1] = MIN(coords[1], height); > > + coords[2] = MIN(coords[2], width); > > + coords[3] = MIN(coords[3], height); > > + > > + // check coordinated are valid > > + spice_assert(coords[0] < coords[2]); > > + spice_assert(coords[1] < coords[3]); > > + > > + // set > > + clipping_rect.left = coords[0]; > > + clipping_rect.top = coords[1]; > > + clipping_rect.right = coords[2]; > > + clipping_rect.bottom = coords[3]; > > + clipping_type_computed = TRUE; > > +} > > + > > +static const EncoderInfo * > > +get_encoder_info(const char *encoder_name) > > +{ > > + const EncoderInfo *info; > > + for (info = encoder_infos; info->name; ++info) { > > + if (strcmp(info->name, encoder_name) == 0) { > > + return info; > > + } > > + } > > + return NULL; > > +} > > + > > +static GstFlowReturn > > +new_sample(GstAppSink *gstappsink, gpointer test_pipeline) > > +{ > > + TestPipeline *pipeline = test_pipeline; > > + > > + GstSample *sample = gst_app_sink_pull_sample(pipeline->appsink); > > + if (sample) { > > + pipeline->sample_proc(sample, pipeline->sample_param); > > + gst_sample_unref(sample); > > + } > > + return GST_FLOW_OK; > > +} > > + > > +static TestPipeline* > > +create_pipeline(const char *desc, SampleProc sample_proc, void *param) > > +{ > > + TestPipeline *pipeline = spice_new0(TestPipeline, 1); > > + > > + GError *err = NULL; > > + pipeline->gst_pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err); > > + if (!pipeline->gst_pipeline) { > > + g_printerr("GStreamer error: %s\n", err->message); > > + return NULL; > > + } > > + > > + pipeline->appsrc = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(pipeline->gst_pipeline), "src")); > > + pipeline->appsink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(pipeline->gst_pipeline), "sink")); > > + if (!pipeline->appsink) { > > + g_printerr("Appsync not found in pipeline: %s\n", desc); > > + return NULL; > > + } > > + > > + GstAppSinkCallbacks appsink_cbs = {NULL, NULL, new_sample, {NULL}}; > > + gst_app_sink_set_callbacks(pipeline->appsink, &appsink_cbs, pipeline, NULL); > > + > > + if (gst_element_set_state(pipeline->gst_pipeline, GST_STATE_PLAYING) == > > + GST_STATE_CHANGE_FAILURE) { > > + g_printerr("GStreamer error: Unable to set the pipeline to the playing state.\n"); > > + exit(1); > > + } > > + > > + pipeline->sample_proc = sample_proc; > > + pipeline->sample_param = param; > > + > > + return pipeline; > > +} > > + > > +static void > > +create_output_pipeline(const EncoderInfo *encoder, SampleProc sample_proc, void *param) > > +{ > > + gchar *desc = > > + g_strdup_printf("appsrc name=src is-live=true format=time max-bytes=0 block=true " > > + "%s ! videoconvert ! appsink name=sink caps=video/x-raw,format=BGRx" > > + " sync=false drop=false", encoder->caps_dec); > > + > > + TestPipeline *pipeline = create_pipeline(desc, sample_proc, param); > > + g_free(desc); > > + if (!pipeline) { > > + g_printerr("Error creating output pipeline\n"); > > + exit(1); > > + } > > + > > + output_pipeline = pipeline; > > +} > > + > > +static void > > +create_input_pipeline(const char *input_pipeline_desc, SampleProc sample_proc, void *param) > > +{ > > + gchar *desc = > > + g_strdup_printf("%s ! appsink name=sink caps=video/x-raw,format=BGRx" > > + " sync=false drop=false", input_pipeline_desc); > > + > > + TestPipeline *pipeline = create_pipeline(desc, sample_proc, param); > > + g_free(desc); > > + if (!pipeline) { > > + // TODO specific error > > + g_printerr("Error creating input pipeline\n"); > > + exit(1); > > + } > > + > > + input_pipeline = pipeline; > > +} > > + > > +static void > > +video_buffer_release(VideoBuffer *video_buffer) > > +{ > > + video_buffer->free(video_buffer); > > +} > > + > > +static void > > +pipeline_send_raw_data(TestPipeline *pipeline, VideoBuffer *video_buffer) > > +{ > > + GstBuffer *buffer = > > + gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS, > > + video_buffer->data, video_buffer->size, > > + 0, video_buffer->size, > > + video_buffer, (void (*)(void*)) video_buffer_release); > > + > > + GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE; > > + GST_BUFFER_DTS(buffer) = GST_CLOCK_TIME_NONE; > > + > > + if (gst_app_src_push_buffer(pipeline->appsrc, buffer) != GST_FLOW_OK) { > > + g_printerr("GStreamer error: unable to push frame of size %u\n", video_buffer->size); > > + exit(1); > > + } > > +} > > + > > +static uint32_t > > +mock_get_roundtrip_ms(void *opaque) > > +{ > > + // TODO > > + return 20; > > +} > > + > > +static uint32_t > > +mock_get_source_fps(void *opaque) > > +{ > > + // TODO > > + return 10; > > +} > > + > > +static void > > +mock_update_client_playback_delay(void *opaque, uint32_t delay_ms) > > +{ > > + // TODO > > +} > > + > > +static VideoEncoderRateControlCbs rate_control_cbs = { > > + .get_roundtrip_ms = mock_get_roundtrip_ms, > > + .get_source_fps = mock_get_source_fps, > > + .update_client_playback_delay = mock_update_client_playback_delay, > > +}; > > + > > +static void > > +create_video_encoder(const EncoderInfo *encoder) > > +{ > > + spice_assert(encoder); > > + > > + video_encoder = encoder->new_encoder(encoder->coded_type, starting_bit_rate, &rate_control_cbs, > > + (bitmap_ref_t) frame_ref, (bitmap_unref_t) frame_unref); > > + // TODO return not supported error > > + spice_assert(video_encoder); > > +} > > + > > +static void > > +frame_ref(TestFrame *frame) > > +{ > > + g_atomic_int_inc(&frame->refs); > > +} > > + > > +static void > > +frame_unref(TestFrame *frame) > > +{ > > + if (!g_atomic_int_dec_and_test(&frame->refs)) { > > + return; > > + } > > + bitmap_free(frame->bitmap); > > + free(frame); > > +} > > + > > +static void > > +bitmap_free(SpiceBitmap *bitmap) > > +{ > > + if (!bitmap) { > > + return; > > + } > > + spice_assert(!bitmap->palette); > > + spice_assert(bitmap->data); > > + spice_chunks_destroy(bitmap->data); > > + free(bitmap); > > +} > > + > > +static SpiceChunks* chunks_alloc(uint32_t stride, uint32_t height, uint32_t split); > > +static uint8_t *bitmap_get_line(SpiceBitmap *bitmap, int y); > > +static uint32_t compute_stride(int width, SpiceBitmapFmt format); > > +typedef void convert_line_t(uint8_t *dest, const uint8_t *src, uint32_t width); > > +static convert_line_t convert_line16; > > +static convert_line_t convert_line24; > > +static convert_line_t convert_line32; > > +static convert_line_t *get_convert_line(SpiceBitmapFmt format); > > + > > +static SpiceBitmap * > > +gst_to_spice_bitmap(GstSample *sample) > > +{ > > + GstCaps *caps = gst_sample_get_caps(sample); > > + spice_assert(caps); > > + > > + GstStructure *s = gst_caps_get_structure(caps, 0); > > + spice_assert(s); > > + > > + gint width, height; > > + spice_assert(gst_structure_get_int(s, "width", &width) && > > + gst_structure_get_int(s, "height", &height)); > > + > > + SpiceBitmap *bitmap = spice_new0(SpiceBitmap, 1); > > + bitmap->format = bitmap_format; > > + bitmap->flags = top_down ? SPICE_BITMAP_FLAGS_TOP_DOWN : 0; > > + bitmap->x = width; > > + bitmap->y = height; > > + bitmap->stride = compute_stride(width, bitmap->format); > > + // TODO support splitting chunks for debugging > > + bitmap->data = chunks_alloc(bitmap->stride, height, height); > > + > > + GstBuffer *buffer = gst_sample_get_buffer(sample); > > + GstMapInfo mapinfo; > > + if (!gst_buffer_map(buffer, &mapinfo, GST_MAP_READ)) { > > + spice_error("GStreamer error: could not map the buffer"); > > + } > > + > > + // convert image > > + gint y; > > + convert_line_t *convert_line = get_convert_line(bitmap->format); > > + for (y = 0; y < height; ++y) { > > + convert_line(bitmap_get_line(bitmap, y), > > + mapinfo.data + y * width * 4, > > + width); > > + } > > + gst_buffer_unmap(buffer, &mapinfo); > > + // TODO should we unref buffer ?? > > + > > + return bitmap; > > +} > > + > > +static uint32_t > > +compute_stride(int width, SpiceBitmapFmt format) > > +{ > > + spice_assert(width > 0); > > + > > + switch (format) { > > + case SPICE_BITMAP_FMT_16BIT: > > + return width * 2; > > + case SPICE_BITMAP_FMT_24BIT: > > + return width * 3; > > + case SPICE_BITMAP_FMT_32BIT: > > + case SPICE_BITMAP_FMT_RGBA: > > + return width * 4; > > + default: > > + break; > > + } > > + spice_assert(0); > > + return 0; > > +} > > + > > +static SpiceChunks* > > +chunks_alloc(uint32_t stride, uint32_t height, uint32_t split) > > +{ > > + spice_assert(stride && height && split); > > + const uint32_t num_chunks = (height + split - 1) / split; > > + SpiceChunks *chunks = spice_malloc0(sizeof(SpiceChunks) + sizeof(SpiceChunk) * num_chunks); > > + > > + chunks->data_size = stride * height; > > + chunks->num_chunks = num_chunks; > > + chunks->flags = SPICE_CHUNKS_FLAGS_FREE; > > + unsigned n; > > + uint32_t allocated = 0; > > + for (n = 0; n < num_chunks; ++n) { > > + SpiceChunk *chunk = &chunks->chunk[n]; > > + uint32_t len = stride * split; > > + spice_assert(chunks->data_size > allocated); > > + len = MIN(len, chunks->data_size - allocated); > > + chunk->data = spice_malloc0(len); > > + chunk->len = len; > > + allocated += len; > > + } > > + spice_assert(chunks->data_size == allocated); > > + return chunks; > > +} > > + > > +static uint8_t * > > +bitmap_get_line(SpiceBitmap *bitmap, int y) > > +{ > > + spice_assert(bitmap && y >= 0 && y < bitmap->y); > > + if (!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) { > > + y = bitmap->y - y - 1; > > + } > > + const uint32_t stride = bitmap->stride; > > + SpiceChunk *chunk = &bitmap->data->chunk[0]; > > + uint32_t chunk_left = chunk->len; > > + while (y) { > > + if (chunk_left <= stride) { > > + spice_assert(chunk_left == stride); > > + ++chunk; > > + spice_assert(chunk < &bitmap->data->chunk[bitmap->data->num_chunks]); > > + chunk_left = chunk->len; > > + spice_assert(chunk_left >= stride); > > + } else { > > + chunk_left -= stride; > > + } > > + --y; > > + } > > + return chunk->data + (chunk->len - chunk_left); > > +} > > + > > +static convert_line_t * > > +get_convert_line(SpiceBitmapFmt format) > > +{ > > + switch (format) { > > + case SPICE_BITMAP_FMT_16BIT: > > + return convert_line16; > > + case SPICE_BITMAP_FMT_24BIT: > > + return convert_line24; > > + case SPICE_BITMAP_FMT_32BIT: > > + case SPICE_BITMAP_FMT_RGBA: > > + return convert_line32; > > + default: > > + break; > > + } > > + spice_assert(0); > > + return 0; > > +} > > + > > +static void > > +convert_line16(uint8_t *dest, const uint8_t *src, uint32_t width) > > +{ > > + uint16_t *dest16 = (uint16_t *) dest; > > + for (; width; --width) { > > + *dest16++ = (src[0] >> 3) | ((src[1] & 0xf8) << 2) | ((src[2] & 0xf8) << 7); > > + src += 4; > > + } > > +} > > + > > +static void > > +convert_line24(uint8_t *dest, const uint8_t *src, uint32_t width) > > +{ > > + for (; width; --width) { > > + *dest++ = *src++; > > + *dest++ = *src++; > > + *dest++ = *src++; > > + ++src; > > + } > > +} > > + > > +static void > > +convert_line32(uint8_t *dest, const uint8_t *src, uint32_t width) > > +{ > > + for (; width; --width) { > > + *dest++ = *src++; > > + *dest++ = *src++; > > + *dest++ = *src++; > > + *dest++ = 0; > > + ++src; > > + } > > +} > > + > > +static SpiceBitmapFmt > > +get_bitmap_format(const char *format) > > +{ > > +#define FMT(fmt) if (strcmp(format, #fmt) == 0) { return SPICE_BITMAP_FMT_ ## fmt; } > > + FMT(32BIT); > > + FMT(24BIT); > > + FMT(16BIT); > > + FMT(RGBA); > > +#undef FMT > > + return SPICE_BITMAP_FMT_INVALID; > > +} > > + > > +static TestFrame * > > +gst_to_spice_frame(GstSample *sample) > > +{ > > + TestFrame *frame = spice_new0(TestFrame, 1); > > + frame->refs = 1; > > + frame->bitmap = gst_to_spice_bitmap(sample); > > + return frame; > > +} > > + > > +static uint32_t > > +line_diff_rgb(const uint8_t *pixel1, const uint8_t *pixel2, uint32_t w) > > +{ > > + uint32_t diff_sum = 0; > > + for (w *= 3; w; --w) { > > + int diff = *pixel1 - *pixel2; > > + diff_sum += diff * diff; > > + ++pixel1; > > + ++pixel2; > > + } > > + return diff_sum; > > +} > > + > > +typedef uint8_t *bitmap_extract_rgb_line_t(SpiceBitmap *bitmap, uint8_t *buf, > > + int32_t x, int32_t y, int32_t w); > > +static bitmap_extract_rgb_line_t *get_bitmap_extract(SpiceBitmapFmt format); > > +static bitmap_extract_rgb_line_t bitmap_extract16; > > +static bitmap_extract_rgb_line_t bitmap_extract24; > > +static bitmap_extract_rgb_line_t bitmap_extract32; > > + > > +// compute PSNR > > +// see https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio > > +// higher is better (less data loosed) > > +// typical are 30-50 > > +static double > > +compute_psnr(SpiceBitmap *bitmap1, int32_t x1, int32_t y1, > > + SpiceBitmap *bitmap2, int32_t x2, int32_t y2, > > + int32_t w, int32_t h) > > +{ > > + spice_assert(x1 >= 0 && y1 >= 0); > > + spice_assert(x2 >= 0 && y2 >= 0); > > + spice_assert(w > 0 && h > 0); > > + spice_assert(x1 + w <= bitmap1->x); > > + spice_assert(y1 + h <= bitmap1->y); > > + spice_assert(x2 + w <= bitmap2->x); > > + spice_assert(y2 + h <= bitmap2->y); > > + > > + int y; > > + uint64_t diff_sum = 0; > > + uint8_t pixels[2][w*3]; > > + bitmap_extract_rgb_line_t *extract1 = get_bitmap_extract(bitmap1->format); > > + bitmap_extract_rgb_line_t *extract2 = get_bitmap_extract(bitmap2->format); > > + for (y = 0; y < h; ++y) { > > + uint8_t *line1 = extract1(bitmap1, pixels[0], x1, y1 + y, w); > > + uint8_t *line2 = extract2(bitmap2, pixels[1], x2, y2 + y, w); > > + diff_sum += line_diff_rgb(line1, line2, w); > > + } > > + > > + double mse = (double) diff_sum / (w*h*3); > > + double psnr = 10 * log10(255*255/mse); > > + > > + return psnr; > > +} > > + > > +static bitmap_extract_rgb_line_t * > > +get_bitmap_extract(SpiceBitmapFmt format) > > +{ > > + switch (format) { > > + case SPICE_BITMAP_FMT_16BIT: > > + return bitmap_extract16; > > + case SPICE_BITMAP_FMT_24BIT: > > + return bitmap_extract24; > > + case SPICE_BITMAP_FMT_32BIT: > > + case SPICE_BITMAP_FMT_RGBA: > > + return bitmap_extract32; > > + default: > > + break; > > + } > > + spice_assert(0); > > + return 0; > > +} > > + > > +static uint8_t * > > +bitmap_extract24(SpiceBitmap *bitmap, uint8_t *buf, int32_t x, int32_t y, int32_t w) > > +{ > > + uint8_t *line = bitmap_get_line(bitmap, y) + x * 3; > > + return line; > > +} > > + > > +static uint8_t * > > +bitmap_extract32(SpiceBitmap *bitmap, uint8_t *buf, int32_t x, int32_t y, int32_t w) > > +{ > > + const uint8_t *line = bitmap_get_line(bitmap, y) + x * 4; > > + uint8_t *dest = buf; > > + for (; w; --w) { > > + *dest++ = *line++; > > + *dest++ = *line++; > > + *dest++ = *line++; > > + ++line; > > + } > > + return buf; > > +} > > + > > +static uint8_t * > > +bitmap_extract16(SpiceBitmap *bitmap, uint8_t *buf, int32_t x, int32_t y, int32_t w) > > +{ > > + const uint16_t *line = (const uint16_t *)(bitmap_get_line(bitmap, y) + x * 2); > > + uint8_t *dest = buf; > > + for (; w; --w) { > > + uint16_t pixel = *line++; > > + uint8_t comp; > > + comp = (pixel >> 0) & 0x1f; > > + *dest++ = (comp << 3) | (comp >> 2); > > + comp = (pixel >> 5) & 0x1f; > > + *dest++ = (comp << 3) | (comp >> 2); > > + comp = (pixel >> 10) & 0x1f; > > + *dest++ = (comp << 3) | (comp >> 2); > > + } > > + return buf; > > +} > > -- > > 2.7.4 > > > > _______________________________________________ > > Spice-devel mailing list > > Spice-devel@xxxxxxxxxxxxxxxxxxxxx > > https://lists.freedesktop.org/mailman/listinfo/spice-devel
Attachment:
signature.asc
Description: PGP signature
_______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel