This way the video encoder is not forced to use malloc()/free(). This also allows more flexibility in how the video encoder manages the buffer which allows for a zero-copy implementation in both video encoders. Signed-off-by: Francois Gouget <fgouget@xxxxxxxxxxxxxxx> --- server/dcc-send.c | 25 +++++++------- server/dcc.c | 5 --- server/dcc.h | 3 -- server/gstreamer-encoder.c | 59 +++++++++++++++++++++----------- server/mjpeg-encoder.c | 85 +++++++++++++++++++++++++++++----------------- server/video-encoder.h | 26 ++++++++++---- 6 files changed, 126 insertions(+), 77 deletions(-) diff --git a/server/dcc-send.c b/server/dcc-send.c index 91d6352..3be443e 100644 --- a/server/dcc-send.c +++ b/server/dcc-send.c @@ -1649,6 +1649,12 @@ static void red_lossy_marshall_qxl_draw_text(RedChannelClient *rcc, } } +static void red_release_video_encoder_buffer(uint8_t *data, void *opaque) +{ + VideoBuffer *buffer = (VideoBuffer*)opaque; + buffer->free(buffer); +} + static int red_marshall_stream_data(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, Drawable *drawable) { @@ -1657,7 +1663,6 @@ static int red_marshall_stream_data(RedChannelClient *rcc, Stream *stream = drawable->stream; SpiceImage *image; uint32_t frame_mm_time; - int n; int width, height; int ret; @@ -1689,7 +1694,6 @@ static int red_marshall_stream_data(RedChannelClient *rcc, StreamAgent *agent = &dcc->stream_agents[get_stream_id(display, stream)]; uint64_t time_now = spice_get_monotonic_time_ns(); - size_t outbuf_size; if (!dcc->use_video_encoder_rate_control) { if (time_now - agent->last_send_time < (1000 * 1000 * 1000) / agent->fps) { @@ -1701,19 +1705,17 @@ static int red_marshall_stream_data(RedChannelClient *rcc, } } + VideoBuffer *outbuf; /* workaround for vga streams */ frame_mm_time = drawable->red_drawable->mm_time ? drawable->red_drawable->mm_time : reds_get_mm_time(); - outbuf_size = dcc->send_data.stream_outbuf_size; ret = !agent->video_encoder ? VIDEO_ENCODER_FRAME_UNSUPPORTED : agent->video_encoder->encode_frame(agent->video_encoder, frame_mm_time, &image->u.bitmap, width, height, &drawable->red_drawable->u.copy.src_area, - stream->top_down, - &dcc->send_data.stream_outbuf, - &outbuf_size, &n); + stream->top_down, &outbuf); switch (ret) { case VIDEO_ENCODER_FRAME_DROP: spice_assert(dcc->use_video_encoder_rate_control); @@ -1729,7 +1731,6 @@ static int red_marshall_stream_data(RedChannelClient *rcc, spice_error("bad return value (%d) from VideoEncoder::encode_frame", ret); return FALSE; } - dcc->send_data.stream_outbuf_size = outbuf_size; if (!drawable->sized_stream) { SpiceMsgDisplayStreamData stream_data; @@ -1738,7 +1739,7 @@ static int red_marshall_stream_data(RedChannelClient *rcc, stream_data.base.id = get_stream_id(display, stream); stream_data.base.multi_media_time = frame_mm_time; - stream_data.data_size = n; + stream_data.data_size = outbuf->size; spice_marshall_msg_display_stream_data(base_marshaller, &stream_data); } else { @@ -1748,7 +1749,7 @@ static int red_marshall_stream_data(RedChannelClient *rcc, stream_data.base.id = get_stream_id(display, stream); stream_data.base.multi_media_time = frame_mm_time; - stream_data.data_size = n; + stream_data.data_size = outbuf->size; stream_data.width = width; stream_data.height = height; stream_data.dest = drawable->red_drawable->bbox; @@ -1757,12 +1758,12 @@ static int red_marshall_stream_data(RedChannelClient *rcc, rect_debug(&stream_data.dest); spice_marshall_msg_display_stream_data_sized(base_marshaller, &stream_data); } - spice_marshaller_add_ref(base_marshaller, - dcc->send_data.stream_outbuf, n); + spice_marshaller_add_ref_full(base_marshaller, outbuf->data, outbuf->size, + &red_release_video_encoder_buffer, outbuf); agent->last_send_time = time_now; #ifdef STREAM_STATS agent->stats.num_frames_sent++; - agent->stats.size_sent += n; + agent->stats.size_sent += outbuf->size; agent->stats.end = frame_mm_time; #endif diff --git a/server/dcc.c b/server/dcc.c index 99b2540..d94d960 100644 --- a/server/dcc.c +++ b/server/dcc.c @@ -381,10 +381,6 @@ DisplayChannelClient *dcc_new(DisplayChannel *display, // TODO: tune quality according to bandwidth dcc->jpeg_quality = 85; - size_t stream_buf_size; - stream_buf_size = 32*1024; - dcc->send_data.stream_outbuf = spice_malloc(stream_buf_size); - dcc->send_data.stream_outbuf_size = stream_buf_size; dcc->send_data.free_list.res = spice_malloc(sizeof(SpiceResourceList) + DISPLAY_FREE_LIST_DEFAULT_SIZE * sizeof(SpiceResourceID)); @@ -492,7 +488,6 @@ void dcc_stop(DisplayChannelClient *dcc) dcc->pixmap_cache = NULL; dcc_release_glz(dcc); dcc_palette_cache_reset(dcc); - free(dcc->send_data.stream_outbuf); free(dcc->send_data.free_list.res); dcc_destroy_stream_agents(dcc); dcc_encoders_free(dcc); diff --git a/server/dcc.h b/server/dcc.h index 436d0be..502b0eb 100644 --- a/server/dcc.h +++ b/server/dcc.h @@ -88,9 +88,6 @@ struct DisplayChannelClient { uint32_t palette_cache_items; struct { - uint32_t stream_outbuf_size; - uint8_t *stream_outbuf; // caution stream buffer is also used as compress bufs!!! - FreeList free_list; uint64_t pixmap_cache_items[MAX_DRAWABLE_PIXMAP_CACHE_ITEMS]; int num_pixmap_cache_items; diff --git a/server/gstreamer-encoder.c b/server/gstreamer-encoder.c index 09e3678..fc04f78 100644 --- a/server/gstreamer-encoder.c +++ b/server/gstreamer-encoder.c @@ -37,6 +37,12 @@ typedef struct { uint32_t bpp; } SpiceFormatForGStreamer; +typedef struct SpiceGstVideoBuffer { + VideoBuffer base; + GstBuffer *gst_buffer; + GstMapInfo map; +} SpiceGstVideoBuffer; + typedef struct SpiceGstEncoder { VideoEncoder base; @@ -80,6 +86,25 @@ typedef struct SpiceGstEncoder { } SpiceGstEncoder; +/* ---------- The SpiceGstVideoBuffer implementation ---------- */ + +static void spice_gst_video_buffer_free(VideoBuffer *video_buffer) +{ + SpiceGstVideoBuffer *buffer = (SpiceGstVideoBuffer*)video_buffer; + if (buffer->gst_buffer) { + gst_buffer_unref(buffer->gst_buffer); + } + free(buffer); +} + +static SpiceGstVideoBuffer* create_gst_video_buffer(void) +{ + SpiceGstVideoBuffer *buffer = spice_new0(SpiceGstVideoBuffer, 1); + buffer->base.free = spice_gst_video_buffer_free; + return buffer; +} + + /* ---------- Miscellaneous SpiceGstEncoder helpers ---------- */ static inline double get_mbps(uint64_t bit_rate) @@ -461,29 +486,22 @@ static int push_raw_frame(SpiceGstEncoder *encoder, const SpiceBitmap *bitmap, /* A helper for spice_gst_encoder_encode_frame() */ static int pull_compressed_buffer(SpiceGstEncoder *encoder, - uint8_t **outbuf, size_t *outbuf_size, - int *data_size) + VideoBuffer **outbuf) { - spice_return_val_if_fail(outbuf && outbuf_size, VIDEO_ENCODER_FRAME_UNSUPPORTED); - GstSample *sample = gst_app_sink_pull_sample(encoder->appsink); if (sample) { - GstMapInfo map; - GstBuffer *buffer = gst_sample_get_buffer(sample); - if (buffer && gst_buffer_map(buffer, &map, GST_MAP_READ)) { - int size = gst_buffer_get_size(buffer); - if (!*outbuf || *outbuf_size < size) { - free(*outbuf); - *outbuf = spice_malloc(size); - *outbuf_size = size; - } - /* TODO Try to avoid this copy by changing the GstBuffer handling */ - memcpy(*outbuf, map.data, size); - *data_size = size; - gst_buffer_unmap(buffer, &map); + SpiceGstVideoBuffer *buffer = create_gst_video_buffer(); + buffer->gst_buffer = gst_sample_get_buffer(sample); + if (buffer->gst_buffer && + gst_buffer_map(buffer->gst_buffer, &buffer->map, GST_MAP_READ)) { + buffer->base.data = buffer->map.data; + buffer->base.size = gst_buffer_get_size(buffer->gst_buffer); + *outbuf = (VideoBuffer*)buffer; + gst_buffer_ref(buffer->gst_buffer); gst_sample_unref(sample); return VIDEO_ENCODER_FRAME_ENCODE_DONE; } + buffer->base.free((VideoBuffer*)buffer); gst_sample_unref(sample); } spice_debug("failed to pull the compressed buffer"); @@ -505,10 +523,11 @@ static int spice_gst_encoder_encode_frame(VideoEncoder *video_encoder, const SpiceBitmap *bitmap, int width, int height, const SpiceRect *src, int top_down, - uint8_t **outbuf, size_t *outbuf_size, - int *data_size) + VideoBuffer **outbuf) { SpiceGstEncoder *encoder = (SpiceGstEncoder*)video_encoder; + g_return_val_if_fail(outbuf != NULL, VIDEO_ENCODER_FRAME_UNSUPPORTED); + *outbuf = NULL; if (width != encoder->width || height != encoder->height || encoder->spice_format != bitmap->format) { @@ -534,7 +553,7 @@ static int spice_gst_encoder_encode_frame(VideoEncoder *video_encoder, int rc = push_raw_frame(encoder, bitmap, src, top_down); if (rc == VIDEO_ENCODER_FRAME_ENCODE_DONE) { - rc = pull_compressed_buffer(encoder, outbuf, outbuf_size, data_size); + rc = pull_compressed_buffer(encoder, outbuf); if (rc != VIDEO_ENCODER_FRAME_ENCODE_DONE) { /* The input buffer will be stuck in the pipeline, preventing * later ones from being processed. So reset the pipeline. diff --git a/server/mjpeg-encoder.c b/server/mjpeg-encoder.c index 41237a7..cd86f0c 100644 --- a/server/mjpeg-encoder.c +++ b/server/mjpeg-encoder.c @@ -70,6 +70,9 @@ static const int mjpeg_quality_samples[MJPEG_QUALITY_SAMPLE_NUM] = {20, 30, 40, */ #define MJPEG_WARMUP_TIME (NSEC_PER_SEC * 3) +/* The compressed buffer initial size. */ +#define MJPEG_INITIAL_BUFFER_SIZE (32 * 1024) + enum { MJPEG_QUALITY_EVAL_TYPE_SET, MJPEG_QUALITY_EVAL_TYPE_UPGRADE, @@ -154,6 +157,11 @@ typedef struct MJpegEncoderRateControl { uint64_t warmup_start_time; } MJpegEncoderRateControl; +typedef struct MJpegVideoBuffer { + VideoBuffer base; + size_t maxsize; +} MJpegVideoBuffer; + typedef struct MJpegEncoder { VideoEncoder base; uint8_t *row; @@ -180,6 +188,26 @@ static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size, uint64_t byte_rate, uint32_t latency); +static void mjpeg_video_buffer_free(VideoBuffer *video_buffer) +{ + MJpegVideoBuffer *buffer = (MJpegVideoBuffer*)video_buffer; + free(buffer->base.data); + free(buffer); +} + +static MJpegVideoBuffer* create_mjpeg_video_buffer(void) +{ + MJpegVideoBuffer *buffer = spice_new0(MJpegVideoBuffer, 1); + buffer->base.free = mjpeg_video_buffer_free; + buffer->maxsize = MJPEG_INITIAL_BUFFER_SIZE; + buffer->base.data = malloc(buffer->maxsize); + if (!buffer->base.data) { + free(buffer); + buffer = NULL; + } + return buffer; +} + static inline int rate_control_is_active(MJpegEncoder* encoder) { return encoder->cbs.get_roundtrip_ms != NULL; @@ -283,24 +311,22 @@ static void term_mem_destination(j_compress_ptr cinfo) /* * Prepare for output to a memory buffer. - * The caller may supply an own initial buffer with appropriate size. - * Otherwise, or when the actual data output exceeds the given size, - * the library adapts the buffer size as necessary. - * The standard library functions malloc/free are used for allocating - * larger memory, so the buffer is available to the application after - * finishing compression, and then the application is responsible for - * freeing the requested memory. + * The caller must supply its own initial buffer and size. + * When the actual data output exceeds the given size, the library + * will adapt the buffer size as necessary using the malloc()/free() + * functions. The buffer is available to the application after the + * compression and the application is then responsible for freeing it. */ - static void spice_jpeg_mem_dest(j_compress_ptr cinfo, unsigned char ** outbuffer, size_t * outsize) { mem_destination_mgr *dest; -#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ - if (outbuffer == NULL || outsize == NULL) /* sanity check */ + if (outbuffer == NULL || *outbuffer == NULL || + outsize == NULL || *outsize == 0) { /* sanity check */ ERREXIT(cinfo, JERR_BUFFER_SIZE); + } /* The destination object is made permanent so that multiple JPEG images * can be written to the same buffer without re-executing jpeg_mem_dest. @@ -315,13 +341,6 @@ spice_jpeg_mem_dest(j_compress_ptr cinfo, dest->pub.term_destination = term_mem_destination; dest->outbuffer = outbuffer; dest->outsize = outsize; - if (*outbuffer == NULL || *outsize == 0) { - /* Allocate initial buffer */ - *outbuffer = malloc(OUTPUT_BUF_SIZE); - if (*outbuffer == NULL) - ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10); - *outsize = OUTPUT_BUF_SIZE; - } dest->pub.next_output_byte = dest->buffer = *outbuffer; dest->pub.free_in_buffer = dest->bufsize = *outsize; @@ -707,7 +726,7 @@ static void mjpeg_encoder_adjust_fps(MJpegEncoder *encoder, uint64_t now) static int mjpeg_encoder_start_frame(MJpegEncoder *encoder, SpiceBitmapFmt format, int width, int height, - uint8_t **dest, size_t *dest_len, + MJpegVideoBuffer *buffer, uint32_t frame_mm_time) { uint32_t quality; @@ -789,7 +808,7 @@ static int mjpeg_encoder_start_frame(MJpegEncoder *encoder, } } - spice_jpeg_mem_dest(&encoder->cinfo, dest, dest_len); + spice_jpeg_mem_dest(&encoder->cinfo, &buffer->base.data, &buffer->maxsize); encoder->cinfo.image_width = width; encoder->cinfo.image_height = height; @@ -930,26 +949,30 @@ static int mjpeg_encoder_encode_frame(VideoEncoder *video_encoder, const SpiceBitmap *bitmap, int width, int height, const SpiceRect *src, int top_down, - uint8_t **outbuf, size_t *outbuf_size, - int *data_size) + VideoBuffer **outbuf) { MJpegEncoder *encoder = (MJpegEncoder*)video_encoder; + MJpegVideoBuffer *buffer = create_mjpeg_video_buffer(); + if (!buffer) { + return VIDEO_ENCODER_FRAME_UNSUPPORTED; + } int ret = mjpeg_encoder_start_frame(encoder, bitmap->format, width, height, - outbuf, outbuf_size, - frame_mm_time); - if (ret != VIDEO_ENCODER_FRAME_ENCODE_DONE) { - return ret; + buffer, frame_mm_time); + if (ret == VIDEO_ENCODER_FRAME_ENCODE_DONE) { + if (encode_frame(encoder, src, bitmap, top_down)) { + buffer->base.size = mjpeg_encoder_end_frame(encoder); + *outbuf = (VideoBuffer*)buffer; + } else { + ret = VIDEO_ENCODER_FRAME_UNSUPPORTED; + } } - if (!encode_frame(encoder, src, bitmap, top_down)) { - return VIDEO_ENCODER_FRAME_UNSUPPORTED; + if (ret != VIDEO_ENCODER_FRAME_ENCODE_DONE) { + buffer->base.free((VideoBuffer*)buffer); } - - *data_size = mjpeg_encoder_end_frame(encoder); - - return VIDEO_ENCODER_FRAME_ENCODE_DONE; + return ret; } diff --git a/server/video-encoder.h b/server/video-encoder.h index 8f3807b..f94fd69 100644 --- a/server/video-encoder.h +++ b/server/video-encoder.h @@ -21,6 +21,22 @@ #ifndef _H_VIDEO_ENCODER #define _H_VIDEO_ENCODER +/* A structure containing the data for a compressed frame. See encode_frame(). */ +typedef struct VideoBuffer VideoBuffer; +struct VideoBuffer { + /* A pointer to the compressed frame data. */ + uint8_t *data; + + /* The size of the compressed frame in bytes. */ + uint32_t size; + + /* Releases the video buffer resources and deallocates it. + * + * @buffer: The video buffer. + */ + void (*free)(VideoBuffer *buffer); +}; + enum { VIDEO_ENCODER_FRAME_UNSUPPORTED = -1, VIDEO_ENCODER_FRAME_DROP, @@ -45,11 +61,9 @@ struct VideoEncoder { * @bitmap: The Spice screen. * @src: A rectangle specifying the area occupied by the video. * @top_down: If true the first video line is specified by src.top. - * @outbuf: The buffer for the compressed frame. This must either - * be NULL or point to a buffer allocated by malloc - * since it may be reallocated, if its size is too small. - * @outbuf_size: The size of the outbuf buffer. - * @data_size: The size of the compressed frame. + * @outbuf: A pointer to a VideoBuffer structure containing the + * compressed frame if successful. Call the buffer's + * free() method as soon as it is no longer needed. * @return: * VIDEO_ENCODER_FRAME_ENCODE_DONE if successful. * VIDEO_ENCODER_FRAME_UNSUPPORTED if the frame cannot be encoded. @@ -59,7 +73,7 @@ struct VideoEncoder { int (*encode_frame)(VideoEncoder *encoder, uint32_t frame_mm_time, const SpiceBitmap *bitmap, int width, int height, const SpiceRect *src, int top_down, - uint8_t **outbuf, size_t *outbuf_size, int *data_size); + VideoBuffer** outbuf); /* * Bit rate control methods. -- 2.8.0.rc3 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel