This patch adds a new function to enable the creation of Gst memory with the dmabuf fd as the source by using a dmabuf allocator. And, it also adds a mechanism to register and invoke any callbacks once the Gst memory object is no longer used by the pipeline. This patch also ensures that the source_fps value is always non-zero. v2: (suggestions from Frediano) - Moved the code associated with add_frame() and pipeline configuration into separate functions that are used when encoding dmabuf fd Cc: Frediano Ziglio <freddy77@xxxxxxxxx> Cc: Gerd Hoffmann <kraxel@xxxxxxxxxx> Cc: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> Cc: Dongwon Kim <dongwon.kim@xxxxxxxxx> Signed-off-by: Vivek Kasireddy <vivek.kasireddy@xxxxxxxxx> --- meson.build | 2 +- server/gstreamer-encoder.c | 164 ++++++++++++++++++++++++++++++------- 2 files changed, 135 insertions(+), 31 deletions(-) diff --git a/meson.build b/meson.build index b1237e61..d6aea60a 100644 --- a/meson.build +++ b/meson.build @@ -131,7 +131,7 @@ endforeach spice_server_has_gstreamer = false spice_server_gst_version = get_option('gstreamer') if spice_server_gst_version != 'no' - gst_deps = ['gstreamer', 'gstreamer-base', 'gstreamer-app', 'gstreamer-video'] + gst_deps = ['gstreamer', 'gstreamer-base', 'gstreamer-app', 'gstreamer-video', 'gstreamer-allocators'] foreach dep : gst_deps dep = '@0@-@1@'.format(dep, spice_server_gst_version) spice_server_deps += dependency(dep) diff --git a/server/gstreamer-encoder.c b/server/gstreamer-encoder.c index be8e3111..33e65345 100644 --- a/server/gstreamer-encoder.c +++ b/server/gstreamer-encoder.c @@ -27,6 +27,7 @@ # pragma GCC diagnostic ignored "-Wunused-const-variable" #endif #include <gst/gst.h> +#include <gst/allocators/gstdmabuf.h> #include <gst/app/gstappsrc.h> #include <gst/app/gstappsink.h> #include <gst/video/video.h> @@ -283,6 +284,7 @@ typedef struct SpiceGstEncoder { /* How many frames were dropped by the server since the last encoded frame. */ uint32_t server_drops; + GstAllocator *allocator; } SpiceGstEncoder; @@ -318,8 +320,12 @@ static inline double get_mbps(uint64_t bit_rate) */ static uint32_t get_source_fps(const SpiceGstEncoder *encoder) { - return encoder->cbs.get_source_fps ? - encoder->cbs.get_source_fps(encoder->cbs.opaque) : SPICE_GST_DEFAULT_FPS; + uint32_t source_fps = 0; + + if (encoder->cbs.get_source_fps) { + source_fps = encoder->cbs.get_source_fps(encoder->cbs.opaque); + } + return source_fps ? source_fps : SPICE_GST_DEFAULT_FPS; } static uint32_t get_network_latency(const SpiceGstEncoder *encoder) @@ -1523,6 +1529,7 @@ static void spice_gst_encoder_destroy(VideoEncoder *video_encoder) { SpiceGstEncoder *encoder = (SpiceGstEncoder*)video_encoder; + gst_object_unref(encoder->allocator); free_pipeline(encoder); pthread_mutex_destroy(&encoder->outbuf_mutex); pthread_cond_destroy(&encoder->outbuf_cond); @@ -1533,35 +1540,45 @@ static void spice_gst_encoder_destroy(VideoEncoder *video_encoder) g_free(encoder); } +static void spice_gst_encoder_add_frame(SpiceGstEncoder *encoder, + VideoBuffer **outbuf, + uint64_t start, + uint32_t frame_mm_time) +{ + uint32_t last_mm_time = get_last_frame_mm_time(encoder); + + add_frame(encoder, frame_mm_time, spice_get_monotonic_time_ns() - start, + (*outbuf)->size); + + int32_t refill = encoder->bit_rate * (frame_mm_time - last_mm_time) / MSEC_PER_SEC / 8; + encoder->vbuffer_free = MIN(encoder->vbuffer_free + refill, + encoder->vbuffer_size) - (*outbuf)->size; + + server_increase_bit_rate(encoder, frame_mm_time); + update_next_frame_mm_time(encoder); +} + static VideoEncodeResults -spice_gst_encoder_encode_frame(VideoEncoder *video_encoder, - uint32_t frame_mm_time, - const SpiceBitmap *bitmap, - const SpiceRect *src, int top_down, - gpointer bitmap_opaque, - VideoBuffer **outbuf) +spice_gst_encoder_configure_pipeline(SpiceGstEncoder *encoder, + uint32_t width, uint32_t height, + const SpiceBitmap *bitmap, + uint32_t frame_mm_time) { - SpiceGstEncoder *encoder = (SpiceGstEncoder*)video_encoder; - g_return_val_if_fail(outbuf != NULL, VIDEO_ENCODER_FRAME_UNSUPPORTED); - *outbuf = NULL; + SpiceBitmapFmt format = bitmap ? (SpiceBitmapFmt) bitmap->format : + SPICE_BITMAP_FMT_32BIT; - /* Unref the last frame's bitmap_opaque structures if any */ - clear_zero_copy_queue(encoder, FALSE); - - uint32_t width = src->right - src->left; - uint32_t height = src->bottom - src->top; if (width != encoder->width || height != encoder->height || - encoder->spice_format != bitmap->format) { + encoder->spice_format != format) { spice_debug("video format change: width %d -> %d, height %d -> %d, format %d -> %d", encoder->width, width, encoder->height, height, - encoder->spice_format, bitmap->format); - encoder->format = map_format((SpiceBitmapFmt) bitmap->format); + encoder->spice_format, format); + encoder->format = map_format(format); if (encoder->format == GSTREAMER_FORMAT_INVALID) { - spice_warning("unable to map format type %d", bitmap->format); + spice_warning("unable to map format type %d", format); encoder->errors = 4; return VIDEO_ENCODER_FRAME_UNSUPPORTED; } - encoder->spice_format = (SpiceBitmapFmt) bitmap->format; + encoder->spice_format = format; encoder->width = width; encoder->height = height; if (encoder->bit_rate == 0) { @@ -1600,8 +1617,36 @@ spice_gst_encoder_encode_frame(VideoEncoder *video_encoder, return VIDEO_ENCODER_FRAME_UNSUPPORTED; } + return VIDEO_ENCODER_FRAME_ENCODE_DONE; +} + +static VideoEncodeResults +spice_gst_encoder_encode_frame(VideoEncoder *video_encoder, + uint32_t frame_mm_time, + const SpiceBitmap *bitmap, + const SpiceRect *src, int top_down, + gpointer bitmap_opaque, + VideoBuffer **outbuf) +{ + SpiceGstEncoder *encoder = (SpiceGstEncoder*)video_encoder; + g_return_val_if_fail(outbuf != NULL, VIDEO_ENCODER_FRAME_UNSUPPORTED); + VideoEncodeResults rc; + *outbuf = NULL; + + /* Unref the last frame's bitmap_opaque structures if any */ + clear_zero_copy_queue(encoder, FALSE); + + uint32_t width = src->right - src->left; + uint32_t height = src->bottom - src->top; + + rc = spice_gst_encoder_configure_pipeline(encoder, width, height, + bitmap, frame_mm_time); + if (rc != VIDEO_ENCODER_FRAME_ENCODE_DONE) { + return rc; + } + uint64_t start = spice_get_monotonic_time_ns(); - VideoEncodeResults rc = push_raw_frame(encoder, bitmap, src, top_down, bitmap_opaque); + rc = push_raw_frame(encoder, bitmap, src, top_down, bitmap_opaque); if (rc == VIDEO_ENCODER_FRAME_ENCODE_DONE) { rc = pull_compressed_buffer(encoder, outbuf); if (rc != VIDEO_ENCODER_FRAME_ENCODE_DONE) { @@ -1621,17 +1666,74 @@ spice_gst_encoder_encode_frame(VideoEncoder *video_encoder, if (rc != VIDEO_ENCODER_FRAME_ENCODE_DONE) { return rc; } - uint32_t last_mm_time = get_last_frame_mm_time(encoder); - add_frame(encoder, frame_mm_time, spice_get_monotonic_time_ns() - start, - (*outbuf)->size); - int32_t refill = encoder->bit_rate * (frame_mm_time - last_mm_time) / MSEC_PER_SEC / 8; - encoder->vbuffer_free = MIN(encoder->vbuffer_free + refill, - encoder->vbuffer_size) - (*outbuf)->size; + spice_gst_encoder_add_frame(encoder, outbuf, start, frame_mm_time); + return rc; +} - server_increase_bit_rate(encoder, frame_mm_time); - update_next_frame_mm_time(encoder); +static void +spice_gst_mem_free_cb(VideoEncoderDmabufData *dmabuf_data, GstMiniObject *obj) +{ + if (dmabuf_data->free) { + dmabuf_data->free(dmabuf_data); + } +} +static VideoEncodeResults +spice_gst_encoder_encode_dmabuf(VideoEncoder *video_encoder, + uint32_t frame_mm_time, + VideoEncoderDmabufData *dmabuf_data, + VideoBuffer **outbuf) +{ + SpiceGstEncoder *encoder = (SpiceGstEncoder*)video_encoder; + g_return_val_if_fail(outbuf != NULL, VIDEO_ENCODER_FRAME_UNSUPPORTED); + g_return_val_if_fail(dmabuf_data != NULL, VIDEO_ENCODER_FRAME_UNSUPPORTED); + VideoEncodeResults rc; + + rc = spice_gst_encoder_configure_pipeline(encoder, dmabuf_data->width, + dmabuf_data->height, NULL, + frame_mm_time); + if (rc != VIDEO_ENCODER_FRAME_ENCODE_DONE) { + return rc; + } + + gsize size = dmabuf_data->stride * dmabuf_data->height; + uint64_t start = spice_get_monotonic_time_ns(); + GstBuffer *buffer; + GstMemory *mem; + *outbuf = NULL; + + mem = gst_dmabuf_allocator_alloc_with_flags(encoder->allocator, + dmabuf_data->drm_dma_buf_fd, + size, + GST_FD_MEMORY_FLAG_DONT_CLOSE); + if (!mem) { + return rc; + } + buffer = gst_buffer_new(); + gst_buffer_append_memory(buffer, mem); + gst_mini_object_weak_ref(GST_MINI_OBJECT(mem), + (GstMiniObjectNotify)spice_gst_mem_free_cb, + dmabuf_data); + GstFlowReturn ret = gst_app_src_push_buffer(encoder->appsrc, buffer); + if (ret != GST_FLOW_OK) { + spice_warning("GStreamer error: unable to push source buffer (%d)", ret); + return rc; + } + + 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. Furthermore something went + * wrong with this pipeline, so it may be safer to rebuild it + * from scratch. + */ + free_pipeline(encoder); + encoder->errors++; + return rc; + } + + spice_gst_encoder_add_frame(encoder, outbuf, start, frame_mm_time); return rc; } @@ -1825,6 +1927,7 @@ VideoEncoder *gstreamer_encoder_new(SpiceVideoCodecType codec_type, SpiceGstEncoder *encoder = g_new0(SpiceGstEncoder, 1); encoder->base.destroy = spice_gst_encoder_destroy; encoder->base.encode_frame = spice_gst_encoder_encode_frame; + encoder->base.encode_dmabuf = spice_gst_encoder_encode_dmabuf; encoder->base.client_stream_report = spice_gst_encoder_client_stream_report; encoder->base.notify_server_frame_drop = spice_gst_encoder_notify_server_frame_drop; encoder->base.get_bit_rate = spice_gst_encoder_get_bit_rate; @@ -1837,6 +1940,7 @@ VideoEncoder *gstreamer_encoder_new(SpiceVideoCodecType codec_type, encoder->bitmap_ref = bitmap_ref; encoder->bitmap_unref = bitmap_unref; encoder->format = GSTREAMER_FORMAT_INVALID; + encoder->allocator = gst_dmabuf_allocator_new(); pthread_mutex_init(&encoder->outbuf_mutex, NULL); pthread_cond_init(&encoder->outbuf_cond, NULL); -- 2.43.0