* [PATCH 1/3] new configure option to enable gstreamer
@ 2025-04-07 10:59 Dietmar Maurer
2025-04-07 10:59 ` [PATCH 2/3] add vnc h264 encoder Dietmar Maurer
2025-04-07 10:59 ` [PATCH 3/3] vnc: h264: send additional frames after the display is clean Dietmar Maurer
0 siblings, 2 replies; 9+ messages in thread
From: Dietmar Maurer @ 2025-04-07 10:59 UTC (permalink / raw)
To: qemu-devel, marcandre.lureau; +Cc: Dietmar Maurer
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
meson.build | 10 ++++++++++
meson_options.txt | 2 ++
scripts/meson-buildoptions.sh | 5 ++++-
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/meson.build b/meson.build
index 41f68d3806..28ca37855a 100644
--- a/meson.build
+++ b/meson.build
@@ -1348,6 +1348,14 @@ if not get_option('zstd').auto() or have_block
required: get_option('zstd'),
method: 'pkg-config')
endif
+
+gstreamer = not_found
+if not get_option('gstreamer').auto() or have_block
+ gstreamer = dependency('gstreamer-1.0 gstreamer-base-1.0', version: '>=1.22.0',
+ required: get_option('gstreamer'),
+ method: 'pkg-config')
+endif
+
qpl = not_found
if not get_option('qpl').auto() or have_system
qpl = dependency('qpl', version: '>=1.5.0',
@@ -2563,6 +2571,7 @@ config_host_data.set('CONFIG_MALLOC_TRIM', has_malloc_trim)
config_host_data.set('CONFIG_STATX', has_statx)
config_host_data.set('CONFIG_STATX_MNT_ID', has_statx_mnt_id)
config_host_data.set('CONFIG_ZSTD', zstd.found())
+config_host_data.set('CONFIG_GSTREAMER', gstreamer.found())
config_host_data.set('CONFIG_QPL', qpl.found())
config_host_data.set('CONFIG_UADK', uadk.found())
config_host_data.set('CONFIG_QATZIP', qatzip.found())
@@ -4836,6 +4845,7 @@ summary_info += {'snappy support': snappy}
summary_info += {'bzip2 support': libbzip2}
summary_info += {'lzfse support': liblzfse}
summary_info += {'zstd support': zstd}
+summary_info += {'gstreamer support': gstreamer}
summary_info += {'Query Processing Library support': qpl}
summary_info += {'UADK Library support': uadk}
summary_info += {'qatzip support': qatzip}
diff --git a/meson_options.txt b/meson_options.txt
index 59d973bca0..11cd132be5 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -254,6 +254,8 @@ option('vnc_sasl', type : 'feature', value : 'auto',
description: 'SASL authentication for VNC server')
option('vte', type : 'feature', value : 'auto',
description: 'vte support for the gtk UI')
+option('gstreamer', type : 'feature', value : 'auto',
+ description: 'for VNC H.264 encoding with gstreamer')
# GTK Clipboard implementation is disabled by default, since it may cause hangs
# of the guest VCPUs. See gitlab issue 1150:
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 3e8e00852b..b0c273d61e 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -229,6 +229,7 @@ meson_options_help() {
printf "%s\n" ' Xen PCI passthrough support'
printf "%s\n" ' xkbcommon xkbcommon support'
printf "%s\n" ' zstd zstd compression support'
+ printf "%s\n" ' gstreamer gstreamer support (H264 for VNC)'
}
_meson_option_parse() {
case $1 in
@@ -581,6 +582,8 @@ _meson_option_parse() {
--disable-xkbcommon) printf "%s" -Dxkbcommon=disabled ;;
--enable-zstd) printf "%s" -Dzstd=enabled ;;
--disable-zstd) printf "%s" -Dzstd=disabled ;;
- *) return 1 ;;
+ --enable-gstreamer) printf "%s" -Dgstreamer=enabled ;;
+ --disable-gstreamer) printf "%s" -Dgstreamer=disabled ;;
+ *) return 1 ;;
esac
}
--
2.39.5
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 2/3] add vnc h264 encoder
2025-04-07 10:59 [PATCH 1/3] new configure option to enable gstreamer Dietmar Maurer
@ 2025-04-07 10:59 ` Dietmar Maurer
2025-04-07 20:36 ` Marc-André Lureau
2025-04-07 10:59 ` [PATCH 3/3] vnc: h264: send additional frames after the display is clean Dietmar Maurer
1 sibling, 1 reply; 9+ messages in thread
From: Dietmar Maurer @ 2025-04-07 10:59 UTC (permalink / raw)
To: qemu-devel, marcandre.lureau; +Cc: Dietmar Maurer
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
ui/meson.build | 1 +
ui/vnc-enc-h264.c | 269 ++++++++++++++++++++++++++++++++++++++++++++++
ui/vnc-jobs.c | 49 ++++++---
ui/vnc.c | 21 ++++
ui/vnc.h | 21 ++++
5 files changed, 346 insertions(+), 15 deletions(-)
create mode 100644 ui/vnc-enc-h264.c
diff --git a/ui/meson.build b/ui/meson.build
index 35fb04cadf..34f1f33699 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -46,6 +46,7 @@ vnc_ss.add(files(
))
vnc_ss.add(zlib, jpeg)
vnc_ss.add(when: sasl, if_true: files('vnc-auth-sasl.c'))
+vnc_ss.add(when: gstreamer, if_true: files('vnc-enc-h264.c'))
system_ss.add_all(when: [vnc, pixman], if_true: vnc_ss)
system_ss.add(when: vnc, if_false: files('vnc-stubs.c'))
diff --git a/ui/vnc-enc-h264.c b/ui/vnc-enc-h264.c
new file mode 100644
index 0000000000..ca8e206335
--- /dev/null
+++ b/ui/vnc-enc-h264.c
@@ -0,0 +1,269 @@
+#include "qemu/osdep.h"
+#include "vnc.h"
+
+#include <gst/gst.h>
+
+static void libavcodec_destroy_encoder_context(VncState *vs)
+{
+ if (!vs->h264) {
+ return;
+ }
+
+ if (vs->h264->source) {
+ gst_object_unref(vs->h264->source);
+ vs->h264->source = NULL;
+ }
+
+ if (vs->h264->convert) {
+ gst_object_unref(vs->h264->convert);
+ vs->h264->convert = NULL;
+ }
+
+ if (vs->h264->gst_encoder) {
+ gst_object_unref(vs->h264->gst_encoder);
+ vs->h264->sink = NULL;
+ }
+
+ if (vs->h264->sink) {
+ gst_object_unref(vs->h264->sink);
+ vs->h264->sink = NULL;
+ }
+
+ if (vs->h264->pipeline) {
+ gst_object_unref(vs->h264->pipeline);
+ vs->h264->pipeline = NULL;
+ }
+}
+
+static bool libavcodec_create_encoder_context(VncState *vs, int w, int h)
+{
+ g_assert(vs->h264 != NULL);
+
+ if (vs->h264->sink) {
+ if (w != vs->h264->width || h != vs->h264->height) {
+ libavcodec_destroy_encoder_context(vs);
+ }
+ }
+
+ if (vs->h264->sink) {
+ return TRUE;
+ }
+
+ vs->h264->width = w;
+ vs->h264->height = h;
+
+ vs->h264->source = gst_element_factory_make("appsrc", "source");
+ if (!vs->h264->source) {
+ VNC_DEBUG("Could not create gst source\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ vs->h264->convert = gst_element_factory_make("videoconvert", "convert");
+ if (!vs->h264->convert) {
+ VNC_DEBUG("Could not create gst convert element\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ vs->h264->gst_encoder = gst_element_factory_make("x264enc", "gst-encoder");
+ if (!vs->h264->gst_encoder) {
+ VNC_DEBUG("Could not create gst x264 encoder\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ g_object_set(vs->h264->gst_encoder, "tune", 4, NULL); /* zerolatency */
+ /* fix for zerolatency with novnc (without, noVNC displays green stripes) */
+ g_object_set(vs->h264->gst_encoder, "threads", 1, NULL);
+
+ g_object_set(vs->h264->gst_encoder, "pass", 5, NULL); /* Constant Quality */
+ g_object_set(vs->h264->gst_encoder, "quantizer", 26, NULL);
+
+ /* avoid access unit delimiters (Nal Unit Type 9) - not required */
+ g_object_set(vs->h264->gst_encoder, "aud", false, NULL);
+
+ vs->h264->sink = gst_element_factory_make("appsink", "sink");
+ if (!vs->h264->sink) {
+ VNC_DEBUG("Could not create gst sink\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ vs->h264->pipeline = gst_pipeline_new("vnc-h264-pipeline");
+ if (!vs->h264->pipeline) {
+ VNC_DEBUG("Could not create gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->source);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->source)) {
+ gst_object_unref(vs->h264->source);
+ VNC_DEBUG("Could not add source to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->convert);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->convert)) {
+ gst_object_unref(vs->h264->convert);
+ VNC_DEBUG("Could not add convert to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->gst_encoder);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->gst_encoder)) {
+ gst_object_unref(vs->h264->gst_encoder);
+ VNC_DEBUG("Could not add encoder to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->sink);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->sink)) {
+ gst_object_unref(vs->h264->sink);
+ VNC_DEBUG("Could not add sink to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ GstCaps *source_caps = gst_caps_new_simple(
+ "video/x-raw",
+ "format", G_TYPE_STRING, "BGRx",
+ "framerate", GST_TYPE_FRACTION, 33, 1,
+ "width", G_TYPE_INT, w,
+ "height", G_TYPE_INT, h,
+ NULL);
+
+ if (!source_caps) {
+ VNC_DEBUG("Could not create source caps filter\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ g_object_set(vs->h264->source, "caps", source_caps, NULL);
+ gst_caps_unref(source_caps);
+
+ if (gst_element_link_many(
+ vs->h264->source,
+ vs->h264->convert,
+ vs->h264->gst_encoder,
+ vs->h264->sink,
+ NULL
+ ) != TRUE) {
+ VNC_DEBUG("Elements could not be linked.\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ /* Start playing */
+ int ret = gst_element_set_state(vs->h264->pipeline, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ VNC_DEBUG("Unable to set the pipeline to the playing state.\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int vnc_h264_encoder_init(VncState *vs)
+{
+ g_assert(vs->h264 == NULL);
+
+ vs->h264 = g_malloc0(sizeof(VncH264));
+
+ return 0;
+}
+
+int vnc_h264_send_framebuffer_update(
+ VncState *vs,
+ int _x,
+ int _y,
+ int _w,
+ int _h,
+) {
+ g_assert(vs->h264 != NULL);
+ g_assert(vs->vd != NULL);
+ g_assert(vs->vd->server != NULL);
+
+ int width = pixman_image_get_width(vs->vd->server);
+ int height = pixman_image_get_height(vs->vd->server);
+
+ g_assert(width == vs->client_width);
+ g_assert(height == vs->client_height);
+
+ int rdb_h264_flags = 0;
+
+ if (vs->h264->sink) {
+ if (width != vs->h264->width || height != vs->h264->height) {
+ rdb_h264_flags = 2;
+ }
+ } else {
+ rdb_h264_flags = 2;
+ }
+
+ if (!libavcodec_create_encoder_context(vs, width, height)) {
+ VNC_DEBUG("Create encoder context failed\n");
+ return -1;
+ }
+
+ g_assert(vs->h264->sink != NULL);
+
+ uint8_t *src_data_ptr = vnc_server_fb_ptr(vs->vd, 0, 0);
+ size_t src_data_size = width * height * VNC_SERVER_FB_BYTES;
+
+ GstBuffer *buffer = gst_buffer_new_wrapped_full(
+ 0, src_data_ptr, src_data_size, 0, src_data_size, NULL, NULL);
+
+ GstFlowReturn ret;
+ g_signal_emit_by_name(vs->h264->source, "push-buffer", buffer, &ret);
+
+ if (ret != GST_FLOW_OK) {
+ VNC_DEBUG("gst appsrc push buffer failed\n");
+ return -1;
+ }
+
+ GstSample *sample;
+ /* Retrieve the buffer */
+ g_signal_emit_by_name(vs->h264->sink, "pull-sample", &sample);
+ if (sample) {
+ GstBuffer *buf = gst_sample_get_buffer(sample);
+ GstMapInfo map;
+ if (gst_buffer_map(buf, &map, 0)) {
+ vnc_framebuffer_update(vs, 0, 0, width, height, VNC_ENCODING_H264);
+ vnc_write_s32(vs, map.size); /* write data length */
+ vnc_write_s32(vs, rdb_h264_flags); /* write flags */
+ rdb_h264_flags = 0;
+
+ VNC_DEBUG("GST vnc_h264_update send %ld\n", map.size);
+
+ vnc_write(vs, map.data, map.size);
+
+ gst_buffer_unmap(buf, &map);
+
+ return 1;
+ } else {
+ VNC_DEBUG("unable to map sample\n");
+ }
+ gst_sample_unref(sample);
+ return 1;
+ } else {
+ VNC_DEBUG("gst no data\n");
+ return 0;
+ }
+}
+
+void vnc_h264_clear(VncState *vs)
+{
+ if (!vs->h264) {
+ return;
+ }
+
+ libavcodec_destroy_encoder_context(vs);
+
+ g_free(vs->h264);
+ vs->h264 = NULL;
+}
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
index fcca7ec632..853a547d9a 100644
--- a/ui/vnc-jobs.c
+++ b/ui/vnc-jobs.c
@@ -193,6 +193,7 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
local->zlib = orig->zlib;
local->hextile = orig->hextile;
local->zrle = orig->zrle;
+ local->h264 = orig->h264;
local->client_width = orig->client_width;
local->client_height = orig->client_height;
}
@@ -204,6 +205,7 @@ static void vnc_async_encoding_end(VncState *orig, VncState *local)
orig->zlib = local->zlib;
orig->hextile = local->hextile;
orig->zrle = local->zrle;
+ orig->h264 = local->h264;
orig->lossy_rect = local->lossy_rect;
}
@@ -284,25 +286,42 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
vnc_write_u16(&vs, 0);
vnc_lock_display(job->vs->vd);
- QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
- int n;
-
- if (job->vs->ioc == NULL) {
- vnc_unlock_display(job->vs->vd);
- /* Copy persistent encoding data */
- vnc_async_encoding_end(job->vs, &vs);
- goto disconnected;
- }
- if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
- n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y,
- entry->rect.w, entry->rect.h);
+ if (vs.vnc_encoding == VNC_ENCODING_H264) {
+ int width = pixman_image_get_width(vs.vd->server);
+ int height = pixman_image_get_height(vs.vd->server);
+ int n = vnc_send_framebuffer_update(&vs, 0, 0, width, height);
+ if (n >= 0) {
+ n_rectangles += n;
+ }
+ QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
+ g_free(entry);
+ }
+ } else {
+ QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
+ int n;
+
+ if (job->vs->ioc == NULL) {
+ vnc_unlock_display(job->vs->vd);
+ /* Copy persistent encoding data */
+ vnc_async_encoding_end(job->vs, &vs);
+ goto disconnected;
+ }
- if (n >= 0) {
- n_rectangles += n;
+ if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
+ n = vnc_send_framebuffer_update(
+ &vs,
+ entry->rect.x,
+ entry->rect.y,
+ entry->rect.w,
+ entry->rect.h);
+
+ if (n >= 0) {
+ n_rectangles += n;
+ }
}
+ g_free(entry);
}
- g_free(entry);
}
trace_vnc_job_nrects(&vs, job, n_rectangles);
vnc_unlock_display(job->vs->vd);
diff --git a/ui/vnc.c b/ui/vnc.c
index 9241caaad9..2e60b55e47 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -972,6 +972,9 @@ int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
case VNC_ENCODING_ZYWRLE:
n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h);
break;
+ case VNC_ENCODING_H264:
+ n = vnc_h264_send_framebuffer_update(vs, x, y, w, h);
+ break;
default:
vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
n = vnc_raw_send_framebuffer_update(vs, x, y, w, h);
@@ -1326,6 +1329,10 @@ void vnc_disconnect_finish(VncState *vs)
vnc_tight_clear(vs);
vnc_zrle_clear(vs);
+#ifdef CONFIG_GSTREAMER
+ vnc_h264_clear(vs);
+#endif
+
#ifdef CONFIG_VNC_SASL
vnc_sasl_client_cleanup(vs);
#endif /* CONFIG_VNC_SASL */
@@ -2181,6 +2188,16 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
vnc_set_feature(vs, VNC_FEATURE_ZYWRLE);
vs->vnc_encoding = enc;
break;
+#ifdef CONFIG_GSTREAMER
+ case VNC_ENCODING_H264:
+ if (vnc_h264_encoder_init(vs) == 0) {
+ vnc_set_feature(vs, VNC_FEATURE_H264);
+ vs->vnc_encoding = enc;
+ } else {
+ VNC_DEBUG("vnc_h264_encoder_init failed\n");
+ }
+ break;
+#endif
case VNC_ENCODING_DESKTOPRESIZE:
vnc_set_feature(vs, VNC_FEATURE_RESIZE);
break;
@@ -4291,6 +4308,10 @@ int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
Error *local_err = NULL;
char *id = (char *)qemu_opts_id(opts);
+#ifdef CONFIG_GSTREAMER
+ gst_init(NULL, NULL);
+#endif
+
assert(id);
vnc_display_init(id, &local_err);
if (local_err) {
diff --git a/ui/vnc.h b/ui/vnc.h
index acc53a2cc1..7e232f7dac 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -46,6 +46,10 @@
#include "vnc-enc-zrle.h"
#include "ui/kbd-state.h"
+#ifdef CONFIG_GSTREAMER
+#include <gst/gst.h>
+#endif
+
// #define _VNC_DEBUG 1
#ifdef _VNC_DEBUG
@@ -231,6 +235,14 @@ typedef struct VncZywrle {
int buf[VNC_ZRLE_TILE_WIDTH * VNC_ZRLE_TILE_HEIGHT];
} VncZywrle;
+#ifdef CONFIG_GSTREAMER
+typedef struct VncH264 {
+ GstElement *pipeline, *source, *gst_encoder, *sink, *convert;
+ size_t width;
+ size_t height;
+} VncH264;
+#endif
+
struct VncRect
{
int x;
@@ -344,6 +356,9 @@ struct VncState
VncHextile hextile;
VncZrle *zrle;
VncZywrle zywrle;
+#ifdef CONFIG_GSTREAMER
+ VncH264 *h264;
+#endif
Notifier mouse_mode_notifier;
@@ -404,6 +419,7 @@ enum {
#define VNC_ENCODING_TRLE 0x0000000f
#define VNC_ENCODING_ZRLE 0x00000010
#define VNC_ENCODING_ZYWRLE 0x00000011
+#define VNC_ENCODING_H264 0x00000032 /* 50 */
#define VNC_ENCODING_COMPRESSLEVEL0 0xFFFFFF00 /* -256 */
#define VNC_ENCODING_QUALITYLEVEL0 0xFFFFFFE0 /* -32 */
#define VNC_ENCODING_XCURSOR 0xFFFFFF10 /* -240 */
@@ -464,6 +480,7 @@ enum VncFeatures {
VNC_FEATURE_XVP,
VNC_FEATURE_CLIPBOARD_EXT,
VNC_FEATURE_AUDIO,
+ VNC_FEATURE_H264,
};
@@ -625,6 +642,10 @@ int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
void vnc_zrle_clear(VncState *vs);
+int vnc_h264_encoder_init(VncState *vs);
+int vnc_h264_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+void vnc_h264_clear(VncState *vs);
+
/* vnc-clipboard.c */
void vnc_server_cut_text_caps(VncState *vs);
void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text);
--
2.39.5
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 3/3] vnc: h264: send additional frames after the display is clean
2025-04-07 10:59 [PATCH 1/3] new configure option to enable gstreamer Dietmar Maurer
2025-04-07 10:59 ` [PATCH 2/3] add vnc h264 encoder Dietmar Maurer
@ 2025-04-07 10:59 ` Dietmar Maurer
2025-04-07 20:17 ` Marc-André Lureau
1 sibling, 1 reply; 9+ messages in thread
From: Dietmar Maurer @ 2025-04-07 10:59 UTC (permalink / raw)
To: qemu-devel, marcandre.lureau; +Cc: Dietmar Maurer
So that encoder can improve the picture quality.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
ui/vnc.c | 25 ++++++++++++++++++++++++-
ui/vnc.h | 3 +++
2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/ui/vnc.c b/ui/vnc.c
index 2e60b55e47..4ba0b715fd 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -3239,7 +3239,30 @@ static void vnc_refresh(DisplayChangeListener *dcl)
vnc_unlock_display(vd);
QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
- rects += vnc_update_client(vs, has_dirty);
+ int client_dirty = has_dirty;
+ if (vs->h264) {
+ if (client_dirty) {
+ vs->h264->keep_dirty = VNC_H264_KEEP_DIRTY;
+ } else {
+ if (vs->h264->keep_dirty > 0) {
+ client_dirty = 1;
+ vs->h264->keep_dirty--;
+ }
+ }
+ }
+
+ int count = vnc_update_client(vs, client_dirty);
+ rects += count;
+
+ if (vs->h264 && !count && vs->h264->keep_dirty) {
+ VncJob *job = vnc_job_new(vs);
+ int height = pixman_image_get_height(vd->server);
+ int width = pixman_image_get_width(vd->server);
+ vs->job_update = vs->update;
+ vs->update = VNC_STATE_UPDATE_NONE;
+ vnc_job_add_rect(job, 0, 0, width, height);
+ vnc_job_push(job);
+ }
/* vs might be free()ed here */
}
diff --git a/ui/vnc.h b/ui/vnc.h
index 7e232f7dac..e1b81d6bcc 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -236,10 +236,13 @@ typedef struct VncZywrle {
} VncZywrle;
#ifdef CONFIG_GSTREAMER
+/* Number of frames we send after the display is clean. */
+#define VNC_H264_KEEP_DIRTY 10
typedef struct VncH264 {
GstElement *pipeline, *source, *gst_encoder, *sink, *convert;
size_t width;
size_t height;
+ guint keep_dirty;
} VncH264;
#endif
--
2.39.5
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH 3/3] vnc: h264: send additional frames after the display is clean
2025-04-07 10:59 ` [PATCH 3/3] vnc: h264: send additional frames after the display is clean Dietmar Maurer
@ 2025-04-07 20:17 ` Marc-André Lureau
0 siblings, 0 replies; 9+ messages in thread
From: Marc-André Lureau @ 2025-04-07 20:17 UTC (permalink / raw)
To: Dietmar Maurer; +Cc: qemu-devel
On Mon, Apr 7, 2025 at 3:06 PM Dietmar Maurer <dietmar@proxmox.com> wrote:
>
> So that encoder can improve the picture quality.
>
> Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> ui/vnc.c | 25 ++++++++++++++++++++++++-
> ui/vnc.h | 3 +++
> 2 files changed, 27 insertions(+), 1 deletion(-)
>
> diff --git a/ui/vnc.c b/ui/vnc.c
> index 2e60b55e47..4ba0b715fd 100644
> --- a/ui/vnc.c
> +++ b/ui/vnc.c
> @@ -3239,7 +3239,30 @@ static void vnc_refresh(DisplayChangeListener *dcl)
> vnc_unlock_display(vd);
>
> QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
> - rects += vnc_update_client(vs, has_dirty);
> + int client_dirty = has_dirty;
> + if (vs->h264) {
> + if (client_dirty) {
> + vs->h264->keep_dirty = VNC_H264_KEEP_DIRTY;
> + } else {
> + if (vs->h264->keep_dirty > 0) {
> + client_dirty = 1;
> + vs->h264->keep_dirty--;
> + }
> + }
> + }
> +
> + int count = vnc_update_client(vs, client_dirty);
> + rects += count;
> +
> + if (vs->h264 && !count && vs->h264->keep_dirty) {
> + VncJob *job = vnc_job_new(vs);
> + int height = pixman_image_get_height(vd->server);
> + int width = pixman_image_get_width(vd->server);
> + vs->job_update = vs->update;
> + vs->update = VNC_STATE_UPDATE_NONE;
> + vnc_job_add_rect(job, 0, 0, width, height);
> + vnc_job_push(job);
> + }
> /* vs might be free()ed here */
> }
>
> diff --git a/ui/vnc.h b/ui/vnc.h
> index 7e232f7dac..e1b81d6bcc 100644
> --- a/ui/vnc.h
> +++ b/ui/vnc.h
> @@ -236,10 +236,13 @@ typedef struct VncZywrle {
> } VncZywrle;
>
> #ifdef CONFIG_GSTREAMER
> +/* Number of frames we send after the display is clean. */
> +#define VNC_H264_KEEP_DIRTY 10
> typedef struct VncH264 {
> GstElement *pipeline, *source, *gst_encoder, *sink, *convert;
> size_t width;
> size_t height;
> + guint keep_dirty;
> } VncH264;
> #endif
>
> --
> 2.39.5
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] add vnc h264 encoder
2025-04-07 10:59 ` [PATCH 2/3] add vnc h264 encoder Dietmar Maurer
@ 2025-04-07 20:36 ` Marc-André Lureau
2025-04-08 9:53 ` Dietmar Maurer
0 siblings, 1 reply; 9+ messages in thread
From: Marc-André Lureau @ 2025-04-07 20:36 UTC (permalink / raw)
To: Dietmar Maurer; +Cc: qemu-devel
Hi
Please resend the series with a cover letter
(https://www.qemu.org/docs/master/devel/submitting-a-patch.html#use-git-format-patch)
Some people prefer when the commit message has content too :)
At least, it would be useful if you point to a client that supports
the codec and allow us to test/use it.
Also documentation: this should be
https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#open-h-264-encoding
I suppose
On Mon, Apr 7, 2025 at 3:06 PM Dietmar Maurer <dietmar@proxmox.com> wrote:
>
> Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
> ---
> ui/meson.build | 1 +
> ui/vnc-enc-h264.c | 269 ++++++++++++++++++++++++++++++++++++++++++++++
> ui/vnc-jobs.c | 49 ++++++---
> ui/vnc.c | 21 ++++
> ui/vnc.h | 21 ++++
> 5 files changed, 346 insertions(+), 15 deletions(-)
> create mode 100644 ui/vnc-enc-h264.c
>
> diff --git a/ui/meson.build b/ui/meson.build
> index 35fb04cadf..34f1f33699 100644
> --- a/ui/meson.build
> +++ b/ui/meson.build
> @@ -46,6 +46,7 @@ vnc_ss.add(files(
> ))
> vnc_ss.add(zlib, jpeg)
> vnc_ss.add(when: sasl, if_true: files('vnc-auth-sasl.c'))
> +vnc_ss.add(when: gstreamer, if_true: files('vnc-enc-h264.c'))
> system_ss.add_all(when: [vnc, pixman], if_true: vnc_ss)
> system_ss.add(when: vnc, if_false: files('vnc-stubs.c'))
>
> diff --git a/ui/vnc-enc-h264.c b/ui/vnc-enc-h264.c
> new file mode 100644
> index 0000000000..ca8e206335
> --- /dev/null
> +++ b/ui/vnc-enc-h264.c
> @@ -0,0 +1,269 @@
> +#include "qemu/osdep.h"
> +#include "vnc.h"
> +
> +#include <gst/gst.h>
> +
> +static void libavcodec_destroy_encoder_context(VncState *vs)
it's not libavcodec.
> +{
> + if (!vs->h264) {
> + return;
> + }
> +
> + if (vs->h264->source) {
> + gst_object_unref(vs->h264->source);
> + vs->h264->source = NULL;
> + }
> +
> + if (vs->h264->convert) {
> + gst_object_unref(vs->h264->convert);
> + vs->h264->convert = NULL;
> + }
> +
> + if (vs->h264->gst_encoder) {
> + gst_object_unref(vs->h264->gst_encoder);
> + vs->h264->sink = NULL;
> + }
> +
> + if (vs->h264->sink) {
> + gst_object_unref(vs->h264->sink);
> + vs->h264->sink = NULL;
> + }
> +
> + if (vs->h264->pipeline) {
> + gst_object_unref(vs->h264->pipeline);
> + vs->h264->pipeline = NULL;
> + }
> +}
> +
> +static bool libavcodec_create_encoder_context(VncState *vs, int w, int h)
> +{
> + g_assert(vs->h264 != NULL);
> +
> + if (vs->h264->sink) {
> + if (w != vs->h264->width || h != vs->h264->height) {
> + libavcodec_destroy_encoder_context(vs);
> + }
> + }
> +
> + if (vs->h264->sink) {
> + return TRUE;
> + }
> +
> + vs->h264->width = w;
> + vs->h264->height = h;
> +
> + vs->h264->source = gst_element_factory_make("appsrc", "source");
> + if (!vs->h264->source) {
> + VNC_DEBUG("Could not create gst source\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + vs->h264->convert = gst_element_factory_make("videoconvert", "convert");
> + if (!vs->h264->convert) {
> + VNC_DEBUG("Could not create gst convert element\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + vs->h264->gst_encoder = gst_element_factory_make("x264enc", "gst-encoder");
It's libx264. And that might be an issue, since it's not generally
available (not in Fedora).
Have you tried to make it agnostic to the encoder element thanks to
encodebin instead?
> + if (!vs->h264->gst_encoder) {
> + VNC_DEBUG("Could not create gst x264 encoder\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + g_object_set(vs->h264->gst_encoder, "tune", 4, NULL); /* zerolatency */
> + /* fix for zerolatency with novnc (without, noVNC displays green stripes) */
> + g_object_set(vs->h264->gst_encoder, "threads", 1, NULL);
> +
> + g_object_set(vs->h264->gst_encoder, "pass", 5, NULL); /* Constant Quality */
> + g_object_set(vs->h264->gst_encoder, "quantizer", 26, NULL);
> +
> + /* avoid access unit delimiters (Nal Unit Type 9) - not required */
> + g_object_set(vs->h264->gst_encoder, "aud", false, NULL);
> +
> + vs->h264->sink = gst_element_factory_make("appsink", "sink");
> + if (!vs->h264->sink) {
> + VNC_DEBUG("Could not create gst sink\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + vs->h264->pipeline = gst_pipeline_new("vnc-h264-pipeline");
> + if (!vs->h264->pipeline) {
> + VNC_DEBUG("Could not create gst pipeline\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + gst_object_ref(vs->h264->source);
> + if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->source)) {
> + gst_object_unref(vs->h264->source);
> + VNC_DEBUG("Could not add source to gst pipeline\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + gst_object_ref(vs->h264->convert);
> + if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->convert)) {
> + gst_object_unref(vs->h264->convert);
> + VNC_DEBUG("Could not add convert to gst pipeline\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + gst_object_ref(vs->h264->gst_encoder);
> + if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->gst_encoder)) {
> + gst_object_unref(vs->h264->gst_encoder);
> + VNC_DEBUG("Could not add encoder to gst pipeline\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + gst_object_ref(vs->h264->sink);
> + if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->sink)) {
> + gst_object_unref(vs->h264->sink);
> + VNC_DEBUG("Could not add sink to gst pipeline\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + GstCaps *source_caps = gst_caps_new_simple(
> + "video/x-raw",
> + "format", G_TYPE_STRING, "BGRx",
> + "framerate", GST_TYPE_FRACTION, 33, 1,
> + "width", G_TYPE_INT, w,
> + "height", G_TYPE_INT, h,
> + NULL);
> +
> + if (!source_caps) {
> + VNC_DEBUG("Could not create source caps filter\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + g_object_set(vs->h264->source, "caps", source_caps, NULL);
> + gst_caps_unref(source_caps);
> +
> + if (gst_element_link_many(
> + vs->h264->source,
> + vs->h264->convert,
> + vs->h264->gst_encoder,
> + vs->h264->sink,
> + NULL
> + ) != TRUE) {
> + VNC_DEBUG("Elements could not be linked.\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + /* Start playing */
> + int ret = gst_element_set_state(vs->h264->pipeline, GST_STATE_PLAYING);
> + if (ret == GST_STATE_CHANGE_FAILURE) {
> + VNC_DEBUG("Unable to set the pipeline to the playing state.\n");
> + libavcodec_destroy_encoder_context(vs);
> + return FALSE;
> + }
> +
> + return TRUE;
> +}
> +
> +int vnc_h264_encoder_init(VncState *vs)
> +{
> + g_assert(vs->h264 == NULL);
> +
> + vs->h264 = g_malloc0(sizeof(VncH264));
> +
> + return 0;
> +}
> +
> +int vnc_h264_send_framebuffer_update(
> + VncState *vs,
> + int _x,
> + int _y,
> + int _w,
> + int _h,
> +) {
> + g_assert(vs->h264 != NULL);
> + g_assert(vs->vd != NULL);
> + g_assert(vs->vd->server != NULL);
> +
> + int width = pixman_image_get_width(vs->vd->server);
> + int height = pixman_image_get_height(vs->vd->server);
> +
> + g_assert(width == vs->client_width);
> + g_assert(height == vs->client_height);
> +
> + int rdb_h264_flags = 0;
> +
> + if (vs->h264->sink) {
> + if (width != vs->h264->width || height != vs->h264->height) {
> + rdb_h264_flags = 2;
> + }
> + } else {
> + rdb_h264_flags = 2;
> + }
> +
> + if (!libavcodec_create_encoder_context(vs, width, height)) {
> + VNC_DEBUG("Create encoder context failed\n");
> + return -1;
> + }
> +
> + g_assert(vs->h264->sink != NULL);
> +
> + uint8_t *src_data_ptr = vnc_server_fb_ptr(vs->vd, 0, 0);
> + size_t src_data_size = width * height * VNC_SERVER_FB_BYTES;
> +
> + GstBuffer *buffer = gst_buffer_new_wrapped_full(
> + 0, src_data_ptr, src_data_size, 0, src_data_size, NULL, NULL);
> +
> + GstFlowReturn ret;
> + g_signal_emit_by_name(vs->h264->source, "push-buffer", buffer, &ret);
> +
> + if (ret != GST_FLOW_OK) {
> + VNC_DEBUG("gst appsrc push buffer failed\n");
> + return -1;
> + }
> +
> + GstSample *sample;
> + /* Retrieve the buffer */
> + g_signal_emit_by_name(vs->h264->sink, "pull-sample", &sample);
> + if (sample) {
> + GstBuffer *buf = gst_sample_get_buffer(sample);
> + GstMapInfo map;
> + if (gst_buffer_map(buf, &map, 0)) {
> + vnc_framebuffer_update(vs, 0, 0, width, height, VNC_ENCODING_H264);
> + vnc_write_s32(vs, map.size); /* write data length */
> + vnc_write_s32(vs, rdb_h264_flags); /* write flags */
> + rdb_h264_flags = 0;
> +
> + VNC_DEBUG("GST vnc_h264_update send %ld\n", map.size);
> +
> + vnc_write(vs, map.data, map.size);
> +
> + gst_buffer_unmap(buf, &map);
> +
> + return 1;
> + } else {
> + VNC_DEBUG("unable to map sample\n");
> + }
> + gst_sample_unref(sample);
> + return 1;
> + } else {
> + VNC_DEBUG("gst no data\n");
> + return 0;
> + }
> +}
> +
> +void vnc_h264_clear(VncState *vs)
> +{
> + if (!vs->h264) {
> + return;
> + }
> +
> + libavcodec_destroy_encoder_context(vs);
> +
> + g_free(vs->h264);
> + vs->h264 = NULL;
> +}
> diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
> index fcca7ec632..853a547d9a 100644
> --- a/ui/vnc-jobs.c
> +++ b/ui/vnc-jobs.c
> @@ -193,6 +193,7 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
> local->zlib = orig->zlib;
> local->hextile = orig->hextile;
> local->zrle = orig->zrle;
> + local->h264 = orig->h264;
> local->client_width = orig->client_width;
> local->client_height = orig->client_height;
> }
> @@ -204,6 +205,7 @@ static void vnc_async_encoding_end(VncState *orig, VncState *local)
> orig->zlib = local->zlib;
> orig->hextile = local->hextile;
> orig->zrle = local->zrle;
> + orig->h264 = local->h264;
> orig->lossy_rect = local->lossy_rect;
> }
>
> @@ -284,25 +286,42 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
> vnc_write_u16(&vs, 0);
>
> vnc_lock_display(job->vs->vd);
> - QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
> - int n;
> -
> - if (job->vs->ioc == NULL) {
> - vnc_unlock_display(job->vs->vd);
> - /* Copy persistent encoding data */
> - vnc_async_encoding_end(job->vs, &vs);
> - goto disconnected;
> - }
>
> - if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
> - n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y,
> - entry->rect.w, entry->rect.h);
> + if (vs.vnc_encoding == VNC_ENCODING_H264) {
> + int width = pixman_image_get_width(vs.vd->server);
> + int height = pixman_image_get_height(vs.vd->server);
> + int n = vnc_send_framebuffer_update(&vs, 0, 0, width, height);
> + if (n >= 0) {
> + n_rectangles += n;
> + }
> + QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
> + g_free(entry);
> + }
> + } else {
> + QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
> + int n;
> +
> + if (job->vs->ioc == NULL) {
> + vnc_unlock_display(job->vs->vd);
> + /* Copy persistent encoding data */
> + vnc_async_encoding_end(job->vs, &vs);
> + goto disconnected;
> + }
>
> - if (n >= 0) {
> - n_rectangles += n;
> + if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
> + n = vnc_send_framebuffer_update(
> + &vs,
> + entry->rect.x,
> + entry->rect.y,
> + entry->rect.w,
> + entry->rect.h);
> +
> + if (n >= 0) {
> + n_rectangles += n;
> + }
> }
> + g_free(entry);
> }
> - g_free(entry);
> }
> trace_vnc_job_nrects(&vs, job, n_rectangles);
> vnc_unlock_display(job->vs->vd);
> diff --git a/ui/vnc.c b/ui/vnc.c
> index 9241caaad9..2e60b55e47 100644
> --- a/ui/vnc.c
> +++ b/ui/vnc.c
> @@ -972,6 +972,9 @@ int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
> case VNC_ENCODING_ZYWRLE:
> n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h);
> break;
> + case VNC_ENCODING_H264:
> + n = vnc_h264_send_framebuffer_update(vs, x, y, w, h);
> + break;
> default:
> vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
> n = vnc_raw_send_framebuffer_update(vs, x, y, w, h);
> @@ -1326,6 +1329,10 @@ void vnc_disconnect_finish(VncState *vs)
> vnc_tight_clear(vs);
> vnc_zrle_clear(vs);
>
> +#ifdef CONFIG_GSTREAMER
> + vnc_h264_clear(vs);
> +#endif
> +
> #ifdef CONFIG_VNC_SASL
> vnc_sasl_client_cleanup(vs);
> #endif /* CONFIG_VNC_SASL */
> @@ -2181,6 +2188,16 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
> vnc_set_feature(vs, VNC_FEATURE_ZYWRLE);
> vs->vnc_encoding = enc;
> break;
> +#ifdef CONFIG_GSTREAMER
> + case VNC_ENCODING_H264:
> + if (vnc_h264_encoder_init(vs) == 0) {
> + vnc_set_feature(vs, VNC_FEATURE_H264);
Before advertising support for the codec, it should actually check if
the encoder is present. It would also be useful to have an extra VNC
option like H264=on/off/auto.
> + vs->vnc_encoding = enc;
> + } else {
> + VNC_DEBUG("vnc_h264_encoder_init failed\n");
> + }
> + break;
> +#endif
> case VNC_ENCODING_DESKTOPRESIZE:
> vnc_set_feature(vs, VNC_FEATURE_RESIZE);
> break;
> @@ -4291,6 +4308,10 @@ int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
> Error *local_err = NULL;
> char *id = (char *)qemu_opts_id(opts);
>
> +#ifdef CONFIG_GSTREAMER
> + gst_init(NULL, NULL);
> +#endif
> +
> assert(id);
> vnc_display_init(id, &local_err);
> if (local_err) {
> diff --git a/ui/vnc.h b/ui/vnc.h
> index acc53a2cc1..7e232f7dac 100644
> --- a/ui/vnc.h
> +++ b/ui/vnc.h
> @@ -46,6 +46,10 @@
> #include "vnc-enc-zrle.h"
> #include "ui/kbd-state.h"
>
> +#ifdef CONFIG_GSTREAMER
> +#include <gst/gst.h>
> +#endif
> +
> // #define _VNC_DEBUG 1
>
> #ifdef _VNC_DEBUG
> @@ -231,6 +235,14 @@ typedef struct VncZywrle {
> int buf[VNC_ZRLE_TILE_WIDTH * VNC_ZRLE_TILE_HEIGHT];
> } VncZywrle;
>
> +#ifdef CONFIG_GSTREAMER
> +typedef struct VncH264 {
> + GstElement *pipeline, *source, *gst_encoder, *sink, *convert;
> + size_t width;
> + size_t height;
> +} VncH264;
> +#endif
> +
> struct VncRect
> {
> int x;
> @@ -344,6 +356,9 @@ struct VncState
> VncHextile hextile;
> VncZrle *zrle;
> VncZywrle zywrle;
> +#ifdef CONFIG_GSTREAMER
> + VncH264 *h264;
> +#endif
>
> Notifier mouse_mode_notifier;
>
> @@ -404,6 +419,7 @@ enum {
> #define VNC_ENCODING_TRLE 0x0000000f
> #define VNC_ENCODING_ZRLE 0x00000010
> #define VNC_ENCODING_ZYWRLE 0x00000011
> +#define VNC_ENCODING_H264 0x00000032 /* 50 */
> #define VNC_ENCODING_COMPRESSLEVEL0 0xFFFFFF00 /* -256 */
> #define VNC_ENCODING_QUALITYLEVEL0 0xFFFFFFE0 /* -32 */
> #define VNC_ENCODING_XCURSOR 0xFFFFFF10 /* -240 */
> @@ -464,6 +480,7 @@ enum VncFeatures {
> VNC_FEATURE_XVP,
> VNC_FEATURE_CLIPBOARD_EXT,
> VNC_FEATURE_AUDIO,
> + VNC_FEATURE_H264,
> };
>
>
> @@ -625,6 +642,10 @@ int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
> int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
> void vnc_zrle_clear(VncState *vs);
>
> +int vnc_h264_encoder_init(VncState *vs);
> +int vnc_h264_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
> +void vnc_h264_clear(VncState *vs);
> +
> /* vnc-clipboard.c */
> void vnc_server_cut_text_caps(VncState *vs);
> void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text);
> --
> 2.39.5
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/3] add vnc h264 encoder
2025-04-08 7:15 [PATCH 0/3] Add VNC Open H.264 Encoding Dietmar Maurer
@ 2025-04-08 7:15 ` Dietmar Maurer
0 siblings, 0 replies; 9+ messages in thread
From: Dietmar Maurer @ 2025-04-08 7:15 UTC (permalink / raw)
To: qemu-devel, marcandre.lureau; +Cc: Dietmar Maurer
This patch implements H264 support for VNC. The RFB protocol
extension is defined in:
https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#open-h-264-encoding
Currently the Gstreamer x264enc plugin (software encoder) is used
to encode the video stream.
The gstreamer pipe is:
appsrc -> videoconvert -> x264enc -> appsink
Note: videoconvert is required for RGBx to YUV420 conversion.
The code still use the VNC server framebuffer change detection,
and only encodes and sends video frames if there are changes.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
ui/meson.build | 1 +
ui/vnc-enc-h264.c | 269 ++++++++++++++++++++++++++++++++++++++++++++++
ui/vnc-jobs.c | 49 ++++++---
ui/vnc.c | 21 ++++
ui/vnc.h | 21 ++++
5 files changed, 346 insertions(+), 15 deletions(-)
create mode 100644 ui/vnc-enc-h264.c
diff --git a/ui/meson.build b/ui/meson.build
index 35fb04cadf..34f1f33699 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -46,6 +46,7 @@ vnc_ss.add(files(
))
vnc_ss.add(zlib, jpeg)
vnc_ss.add(when: sasl, if_true: files('vnc-auth-sasl.c'))
+vnc_ss.add(when: gstreamer, if_true: files('vnc-enc-h264.c'))
system_ss.add_all(when: [vnc, pixman], if_true: vnc_ss)
system_ss.add(when: vnc, if_false: files('vnc-stubs.c'))
diff --git a/ui/vnc-enc-h264.c b/ui/vnc-enc-h264.c
new file mode 100644
index 0000000000..ca8e206335
--- /dev/null
+++ b/ui/vnc-enc-h264.c
@@ -0,0 +1,269 @@
+#include "qemu/osdep.h"
+#include "vnc.h"
+
+#include <gst/gst.h>
+
+static void libavcodec_destroy_encoder_context(VncState *vs)
+{
+ if (!vs->h264) {
+ return;
+ }
+
+ if (vs->h264->source) {
+ gst_object_unref(vs->h264->source);
+ vs->h264->source = NULL;
+ }
+
+ if (vs->h264->convert) {
+ gst_object_unref(vs->h264->convert);
+ vs->h264->convert = NULL;
+ }
+
+ if (vs->h264->gst_encoder) {
+ gst_object_unref(vs->h264->gst_encoder);
+ vs->h264->sink = NULL;
+ }
+
+ if (vs->h264->sink) {
+ gst_object_unref(vs->h264->sink);
+ vs->h264->sink = NULL;
+ }
+
+ if (vs->h264->pipeline) {
+ gst_object_unref(vs->h264->pipeline);
+ vs->h264->pipeline = NULL;
+ }
+}
+
+static bool libavcodec_create_encoder_context(VncState *vs, int w, int h)
+{
+ g_assert(vs->h264 != NULL);
+
+ if (vs->h264->sink) {
+ if (w != vs->h264->width || h != vs->h264->height) {
+ libavcodec_destroy_encoder_context(vs);
+ }
+ }
+
+ if (vs->h264->sink) {
+ return TRUE;
+ }
+
+ vs->h264->width = w;
+ vs->h264->height = h;
+
+ vs->h264->source = gst_element_factory_make("appsrc", "source");
+ if (!vs->h264->source) {
+ VNC_DEBUG("Could not create gst source\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ vs->h264->convert = gst_element_factory_make("videoconvert", "convert");
+ if (!vs->h264->convert) {
+ VNC_DEBUG("Could not create gst convert element\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ vs->h264->gst_encoder = gst_element_factory_make("x264enc", "gst-encoder");
+ if (!vs->h264->gst_encoder) {
+ VNC_DEBUG("Could not create gst x264 encoder\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ g_object_set(vs->h264->gst_encoder, "tune", 4, NULL); /* zerolatency */
+ /* fix for zerolatency with novnc (without, noVNC displays green stripes) */
+ g_object_set(vs->h264->gst_encoder, "threads", 1, NULL);
+
+ g_object_set(vs->h264->gst_encoder, "pass", 5, NULL); /* Constant Quality */
+ g_object_set(vs->h264->gst_encoder, "quantizer", 26, NULL);
+
+ /* avoid access unit delimiters (Nal Unit Type 9) - not required */
+ g_object_set(vs->h264->gst_encoder, "aud", false, NULL);
+
+ vs->h264->sink = gst_element_factory_make("appsink", "sink");
+ if (!vs->h264->sink) {
+ VNC_DEBUG("Could not create gst sink\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ vs->h264->pipeline = gst_pipeline_new("vnc-h264-pipeline");
+ if (!vs->h264->pipeline) {
+ VNC_DEBUG("Could not create gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->source);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->source)) {
+ gst_object_unref(vs->h264->source);
+ VNC_DEBUG("Could not add source to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->convert);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->convert)) {
+ gst_object_unref(vs->h264->convert);
+ VNC_DEBUG("Could not add convert to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->gst_encoder);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->gst_encoder)) {
+ gst_object_unref(vs->h264->gst_encoder);
+ VNC_DEBUG("Could not add encoder to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ gst_object_ref(vs->h264->sink);
+ if (!gst_bin_add(GST_BIN(vs->h264->pipeline), vs->h264->sink)) {
+ gst_object_unref(vs->h264->sink);
+ VNC_DEBUG("Could not add sink to gst pipeline\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ GstCaps *source_caps = gst_caps_new_simple(
+ "video/x-raw",
+ "format", G_TYPE_STRING, "BGRx",
+ "framerate", GST_TYPE_FRACTION, 33, 1,
+ "width", G_TYPE_INT, w,
+ "height", G_TYPE_INT, h,
+ NULL);
+
+ if (!source_caps) {
+ VNC_DEBUG("Could not create source caps filter\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ g_object_set(vs->h264->source, "caps", source_caps, NULL);
+ gst_caps_unref(source_caps);
+
+ if (gst_element_link_many(
+ vs->h264->source,
+ vs->h264->convert,
+ vs->h264->gst_encoder,
+ vs->h264->sink,
+ NULL
+ ) != TRUE) {
+ VNC_DEBUG("Elements could not be linked.\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ /* Start playing */
+ int ret = gst_element_set_state(vs->h264->pipeline, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ VNC_DEBUG("Unable to set the pipeline to the playing state.\n");
+ libavcodec_destroy_encoder_context(vs);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int vnc_h264_encoder_init(VncState *vs)
+{
+ g_assert(vs->h264 == NULL);
+
+ vs->h264 = g_malloc0(sizeof(VncH264));
+
+ return 0;
+}
+
+int vnc_h264_send_framebuffer_update(
+ VncState *vs,
+ int _x,
+ int _y,
+ int _w,
+ int _h,
+) {
+ g_assert(vs->h264 != NULL);
+ g_assert(vs->vd != NULL);
+ g_assert(vs->vd->server != NULL);
+
+ int width = pixman_image_get_width(vs->vd->server);
+ int height = pixman_image_get_height(vs->vd->server);
+
+ g_assert(width == vs->client_width);
+ g_assert(height == vs->client_height);
+
+ int rdb_h264_flags = 0;
+
+ if (vs->h264->sink) {
+ if (width != vs->h264->width || height != vs->h264->height) {
+ rdb_h264_flags = 2;
+ }
+ } else {
+ rdb_h264_flags = 2;
+ }
+
+ if (!libavcodec_create_encoder_context(vs, width, height)) {
+ VNC_DEBUG("Create encoder context failed\n");
+ return -1;
+ }
+
+ g_assert(vs->h264->sink != NULL);
+
+ uint8_t *src_data_ptr = vnc_server_fb_ptr(vs->vd, 0, 0);
+ size_t src_data_size = width * height * VNC_SERVER_FB_BYTES;
+
+ GstBuffer *buffer = gst_buffer_new_wrapped_full(
+ 0, src_data_ptr, src_data_size, 0, src_data_size, NULL, NULL);
+
+ GstFlowReturn ret;
+ g_signal_emit_by_name(vs->h264->source, "push-buffer", buffer, &ret);
+
+ if (ret != GST_FLOW_OK) {
+ VNC_DEBUG("gst appsrc push buffer failed\n");
+ return -1;
+ }
+
+ GstSample *sample;
+ /* Retrieve the buffer */
+ g_signal_emit_by_name(vs->h264->sink, "pull-sample", &sample);
+ if (sample) {
+ GstBuffer *buf = gst_sample_get_buffer(sample);
+ GstMapInfo map;
+ if (gst_buffer_map(buf, &map, 0)) {
+ vnc_framebuffer_update(vs, 0, 0, width, height, VNC_ENCODING_H264);
+ vnc_write_s32(vs, map.size); /* write data length */
+ vnc_write_s32(vs, rdb_h264_flags); /* write flags */
+ rdb_h264_flags = 0;
+
+ VNC_DEBUG("GST vnc_h264_update send %ld\n", map.size);
+
+ vnc_write(vs, map.data, map.size);
+
+ gst_buffer_unmap(buf, &map);
+
+ return 1;
+ } else {
+ VNC_DEBUG("unable to map sample\n");
+ }
+ gst_sample_unref(sample);
+ return 1;
+ } else {
+ VNC_DEBUG("gst no data\n");
+ return 0;
+ }
+}
+
+void vnc_h264_clear(VncState *vs)
+{
+ if (!vs->h264) {
+ return;
+ }
+
+ libavcodec_destroy_encoder_context(vs);
+
+ g_free(vs->h264);
+ vs->h264 = NULL;
+}
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
index fcca7ec632..853a547d9a 100644
--- a/ui/vnc-jobs.c
+++ b/ui/vnc-jobs.c
@@ -193,6 +193,7 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
local->zlib = orig->zlib;
local->hextile = orig->hextile;
local->zrle = orig->zrle;
+ local->h264 = orig->h264;
local->client_width = orig->client_width;
local->client_height = orig->client_height;
}
@@ -204,6 +205,7 @@ static void vnc_async_encoding_end(VncState *orig, VncState *local)
orig->zlib = local->zlib;
orig->hextile = local->hextile;
orig->zrle = local->zrle;
+ orig->h264 = local->h264;
orig->lossy_rect = local->lossy_rect;
}
@@ -284,25 +286,42 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
vnc_write_u16(&vs, 0);
vnc_lock_display(job->vs->vd);
- QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
- int n;
-
- if (job->vs->ioc == NULL) {
- vnc_unlock_display(job->vs->vd);
- /* Copy persistent encoding data */
- vnc_async_encoding_end(job->vs, &vs);
- goto disconnected;
- }
- if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
- n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y,
- entry->rect.w, entry->rect.h);
+ if (vs.vnc_encoding == VNC_ENCODING_H264) {
+ int width = pixman_image_get_width(vs.vd->server);
+ int height = pixman_image_get_height(vs.vd->server);
+ int n = vnc_send_framebuffer_update(&vs, 0, 0, width, height);
+ if (n >= 0) {
+ n_rectangles += n;
+ }
+ QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
+ g_free(entry);
+ }
+ } else {
+ QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
+ int n;
+
+ if (job->vs->ioc == NULL) {
+ vnc_unlock_display(job->vs->vd);
+ /* Copy persistent encoding data */
+ vnc_async_encoding_end(job->vs, &vs);
+ goto disconnected;
+ }
- if (n >= 0) {
- n_rectangles += n;
+ if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
+ n = vnc_send_framebuffer_update(
+ &vs,
+ entry->rect.x,
+ entry->rect.y,
+ entry->rect.w,
+ entry->rect.h);
+
+ if (n >= 0) {
+ n_rectangles += n;
+ }
}
+ g_free(entry);
}
- g_free(entry);
}
trace_vnc_job_nrects(&vs, job, n_rectangles);
vnc_unlock_display(job->vs->vd);
diff --git a/ui/vnc.c b/ui/vnc.c
index 9241caaad9..2e60b55e47 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -972,6 +972,9 @@ int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
case VNC_ENCODING_ZYWRLE:
n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h);
break;
+ case VNC_ENCODING_H264:
+ n = vnc_h264_send_framebuffer_update(vs, x, y, w, h);
+ break;
default:
vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
n = vnc_raw_send_framebuffer_update(vs, x, y, w, h);
@@ -1326,6 +1329,10 @@ void vnc_disconnect_finish(VncState *vs)
vnc_tight_clear(vs);
vnc_zrle_clear(vs);
+#ifdef CONFIG_GSTREAMER
+ vnc_h264_clear(vs);
+#endif
+
#ifdef CONFIG_VNC_SASL
vnc_sasl_client_cleanup(vs);
#endif /* CONFIG_VNC_SASL */
@@ -2181,6 +2188,16 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
vnc_set_feature(vs, VNC_FEATURE_ZYWRLE);
vs->vnc_encoding = enc;
break;
+#ifdef CONFIG_GSTREAMER
+ case VNC_ENCODING_H264:
+ if (vnc_h264_encoder_init(vs) == 0) {
+ vnc_set_feature(vs, VNC_FEATURE_H264);
+ vs->vnc_encoding = enc;
+ } else {
+ VNC_DEBUG("vnc_h264_encoder_init failed\n");
+ }
+ break;
+#endif
case VNC_ENCODING_DESKTOPRESIZE:
vnc_set_feature(vs, VNC_FEATURE_RESIZE);
break;
@@ -4291,6 +4308,10 @@ int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
Error *local_err = NULL;
char *id = (char *)qemu_opts_id(opts);
+#ifdef CONFIG_GSTREAMER
+ gst_init(NULL, NULL);
+#endif
+
assert(id);
vnc_display_init(id, &local_err);
if (local_err) {
diff --git a/ui/vnc.h b/ui/vnc.h
index acc53a2cc1..7e232f7dac 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -46,6 +46,10 @@
#include "vnc-enc-zrle.h"
#include "ui/kbd-state.h"
+#ifdef CONFIG_GSTREAMER
+#include <gst/gst.h>
+#endif
+
// #define _VNC_DEBUG 1
#ifdef _VNC_DEBUG
@@ -231,6 +235,14 @@ typedef struct VncZywrle {
int buf[VNC_ZRLE_TILE_WIDTH * VNC_ZRLE_TILE_HEIGHT];
} VncZywrle;
+#ifdef CONFIG_GSTREAMER
+typedef struct VncH264 {
+ GstElement *pipeline, *source, *gst_encoder, *sink, *convert;
+ size_t width;
+ size_t height;
+} VncH264;
+#endif
+
struct VncRect
{
int x;
@@ -344,6 +356,9 @@ struct VncState
VncHextile hextile;
VncZrle *zrle;
VncZywrle zywrle;
+#ifdef CONFIG_GSTREAMER
+ VncH264 *h264;
+#endif
Notifier mouse_mode_notifier;
@@ -404,6 +419,7 @@ enum {
#define VNC_ENCODING_TRLE 0x0000000f
#define VNC_ENCODING_ZRLE 0x00000010
#define VNC_ENCODING_ZYWRLE 0x00000011
+#define VNC_ENCODING_H264 0x00000032 /* 50 */
#define VNC_ENCODING_COMPRESSLEVEL0 0xFFFFFF00 /* -256 */
#define VNC_ENCODING_QUALITYLEVEL0 0xFFFFFFE0 /* -32 */
#define VNC_ENCODING_XCURSOR 0xFFFFFF10 /* -240 */
@@ -464,6 +480,7 @@ enum VncFeatures {
VNC_FEATURE_XVP,
VNC_FEATURE_CLIPBOARD_EXT,
VNC_FEATURE_AUDIO,
+ VNC_FEATURE_H264,
};
@@ -625,6 +642,10 @@ int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
void vnc_zrle_clear(VncState *vs);
+int vnc_h264_encoder_init(VncState *vs);
+int vnc_h264_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+void vnc_h264_clear(VncState *vs);
+
/* vnc-clipboard.c */
void vnc_server_cut_text_caps(VncState *vs);
void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text);
--
2.39.5
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] add vnc h264 encoder
2025-04-07 20:36 ` Marc-André Lureau
@ 2025-04-08 9:53 ` Dietmar Maurer
2025-04-08 10:37 ` Marc-André Lureau
0 siblings, 1 reply; 9+ messages in thread
From: Dietmar Maurer @ 2025-04-08 9:53 UTC (permalink / raw)
To: Marc-André Lureau; +Cc: qemu-devel
> Please resend the series with a cover letter
> (https://www.qemu.org/docs/master/devel/submitting-a-patch.html#use-git-format-patch)
Ok, just resend this series with a cover letter and commit message.
(patches unchanged)
> > +#include <gst/gst.h>
> > +
> > +static void libavcodec_destroy_encoder_context(VncState *vs)
>
> it's not libavcodec.
I will fix that in v2.
> > +#ifdef CONFIG_GSTREAMER
> > + case VNC_ENCODING_H264:
> > + if (vnc_h264_encoder_init(vs) == 0) {
> > + vnc_set_feature(vs, VNC_FEATURE_H264);
>
> Before advertising support for the codec, it should actually check if
> the encoder is present.
ok.
> It would also be useful to have an extra VNC
> option like H264=on/off/auto.
I thought it would be better to do that at the client?
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] add vnc h264 encoder
2025-04-08 9:53 ` Dietmar Maurer
@ 2025-04-08 10:37 ` Marc-André Lureau
2025-04-08 11:10 ` Dietmar Maurer
0 siblings, 1 reply; 9+ messages in thread
From: Marc-André Lureau @ 2025-04-08 10:37 UTC (permalink / raw)
To: Dietmar Maurer; +Cc: qemu-devel
Hi
On Tue, Apr 8, 2025 at 1:54 PM Dietmar Maurer <dietmar@proxmox.com> wrote:
>
> > Please resend the series with a cover letter
> > (https://www.qemu.org/docs/master/devel/submitting-a-patch.html#use-git-format-patch)
>
> Ok, just resend this series with a cover letter and commit message.
> (patches unchanged)
ok, thanks
>
> > > +#include <gst/gst.h>
> > > +
> > > +static void libavcodec_destroy_encoder_context(VncState *vs)
> >
> > it's not libavcodec.
>
> I will fix that in v2.
What about encodebin suggestion?
>
>
> > > +#ifdef CONFIG_GSTREAMER
> > > + case VNC_ENCODING_H264:
> > > + if (vnc_h264_encoder_init(vs) == 0) {
> > > + vnc_set_feature(vs, VNC_FEATURE_H264);
> >
> > Before advertising support for the codec, it should actually check if
> > the encoder is present.
>
> ok.
>
> > It would also be useful to have an extra VNC
> > option like H264=on/off/auto.
>
> I thought it would be better to do that at the client?
Well, it can be worth it to prevent h264 usage from the server too. Or
to ensure the server is h264-capable. (this wasn't seen as much
necessary for other codecs that are low-resource and/or patent-free,
but may make sense too)
--
Marc-André Lureau
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] add vnc h264 encoder
2025-04-08 10:37 ` Marc-André Lureau
@ 2025-04-08 11:10 ` Dietmar Maurer
0 siblings, 0 replies; 9+ messages in thread
From: Dietmar Maurer @ 2025-04-08 11:10 UTC (permalink / raw)
To: Marc-André Lureau; +Cc: qemu-devel
> > > > +#include <gst/gst.h>
> > > > +
> > > > +static void libavcodec_destroy_encoder_context(VncState *vs)
> > >
> > > it's not libavcodec.
> >
> > I will fix that in v2.
>
> What about encodebin suggestion?
I found no way to configure codec specific option (i.e. x264 zerolatency). I there a way? It is crucial to set those option
to get reasonable quality.
Anyway, I can test a view option, for example x264, openh264, and
maybe vah264enc (hardware support), and then simply use what is available.
I think we should also have the option to allow h264 if we detect
HW support, but disable software encoders. Or let the user select
the list of allowed codec?
> >
> > > > +#ifdef CONFIG_GSTREAMER
> > > > + case VNC_ENCODING_H264:
> > > > + if (vnc_h264_encoder_init(vs) == 0) {
> > > > + vnc_set_feature(vs, VNC_FEATURE_H264);
> > >
> > > Before advertising support for the codec, it should actually check if
> > > the encoder is present.
> >
> > ok.
> >
> > > It would also be useful to have an extra VNC
> > > option like H264=on/off/auto.
> >
> > I thought it would be better to do that at the client?
>
> Well, it can be worth it to prevent h264 usage from the server too. Or
> to ensure the server is h264-capable. (this wasn't seen as much
> necessary for other codecs that are low-resource and/or patent-free,
> but may make sense too)
Maybe H264=on,off,codec-list
on: automatically select the codec
off: disable h264
codec-list: a list of allowed codecs
- Dietmar
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-04-08 11:11 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-07 10:59 [PATCH 1/3] new configure option to enable gstreamer Dietmar Maurer
2025-04-07 10:59 ` [PATCH 2/3] add vnc h264 encoder Dietmar Maurer
2025-04-07 20:36 ` Marc-André Lureau
2025-04-08 9:53 ` Dietmar Maurer
2025-04-08 10:37 ` Marc-André Lureau
2025-04-08 11:10 ` Dietmar Maurer
2025-04-07 10:59 ` [PATCH 3/3] vnc: h264: send additional frames after the display is clean Dietmar Maurer
2025-04-07 20:17 ` Marc-André Lureau
-- strict thread matches above, loose matches on Subject: below --
2025-04-08 7:15 [PATCH 0/3] Add VNC Open H.264 Encoding Dietmar Maurer
2025-04-08 7:15 ` [PATCH 2/3] add vnc h264 encoder Dietmar Maurer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).