Ferigolo
All projects

Personal project / In development

Remote Desktop App

Native H.264 engine, PipeWire capture and NVIDIA/Intel acceleration with rendering outside the WebView.

My role
Personal development · media engine and architecture
Technologies
C++26 · FFmpeg · CUDA / QSV · SDL3 · WebRTC · Tauri
Stage
In development

01 /The context

Screen sharing separates the management interface from video processing. The Linux/Wayland path combines system portal capture, H.264 encoding, WebRTC transport and presentation in a native window.

02 /My contribution

Development of the C++26 engine, capture, encoders, decoders, media transport and SDL3 client. Next.js/Tauri launches the C++ executable as a sidecar through a Rust command; Python/asyncio/websockets handles SDP and ICE rendezvous. The engine structure makes frame movement, memory transfers and synchronization points explicit.

From capture to the native window

01 / Host · capture and memory

  1. Wayland → portalQt6/DBus authorizes the ScreenCast session.
  2. PipeWire / SPACapture thread negotiates format, stride and DRM modifier.
  3. DMA-BUF → DRM PRIMEFD describes the surface; av_hwframe_map attempts mapping to CUDA or QSV.

NVIDIA MemFd alternative: mmap → CPU libswscale (RGB/BGRA → NV12) → av_hwframe_transfer_data to the GPU. Mapping support depends on formats and drivers.

02 / Host → client · H.264 and network

  1. FFmpeg encoderh264_nvenc / CUDA · h264_qsv / Intel · AVFrame → AVPacket
  2. libdatachannelH264RtpPacketizer + RTCP SR/NACK → WebRTC → H264RtpDepacketizer
  3. FFmpeg decoderh264_cuvid / CUDA, h264_qsv / Intel or software H.264.

NVENC configures a 5 Mb/s bitrate, zero B-frames and a 2 s GOP. RTP timestamps advance at 90 kHz according to FPS.

03 / Client · GPU and presentation

  1. Decoded frameCUDA keeps AVFrame on the GPU. QSV includes a download to RAM; software converts to NV12.
  2. Y / UV texturesCUDA–OpenGL: registration, mapping and asynchronous device-to-device copies of NV12 planes.
  3. SDL3 / OpenGLUnmap → cudaStreamSynchronize(0) → SDL_RenderTexture → SDL_RenderPresent

std::mutex and av_frame_clone protect frame exchange. CUDA completion precedes SDL drawing; RAM paths use SDL_UpdateNVTexture. NV12 planes are copied between surfaces in GPU memory.

04 / Control · UI and handshake

  1. Next.js / TauriRust command start_engine launches the core-engine sidecar.
  2. Python / WebSocketasyncio + websockets relay registration, SDP and ICE candidates.
  3. Native peerslibdatachannel establishes P2P media; DataChannel carries resolution.

Signaling exchanges metadata; video uses the media channel between peers.

05 / Next steps

  1. macOSExpand native capture and codecs with ScreenCaptureKit / VideoToolbox.
  2. VPP and video memoryMove color conversion to the GPU and reduce transfers to RAM.
  3. Audio and remote inputAdd system/microphone audio and keyboard/mouse injection.
Capture, codec, transport and native presentation architecture. Dashed nodes indicate the next implementation steps.

03 /Architecture and implementation

Capture and buffers

Qt6/DBus accesses the XDG ScreenCast portal; PipeWire delivers buffers on its own thread. The engine passes FD, dimensions, stride, SPA format and DRM modifier to the encoder, then returns the buffer after the callback. AVDRMFrameDescriptor describes the DMA-BUF. Duplicating the FD retains the buffer reference during processing.

Encoders and color conversion

CudaEncoder uses FFmpeg h264_nvenc, a CUDA context and NV12 surfaces. IntelIrisEncoder configures h264_qsv and a pool of 32 QSV surfaces. Both contain DRM PRIME mapping through av_hwframe_map. In NVIDIA’s MemFd fallback, libswscale converts pixels on the CPU before av_hwframe_transfer_data uploads them; the next step is to move all color conversion to the GPU.

Decoders and alternatives

CudaDecoder selects h264_cuvid and negotiates AV_PIX_FMT_CUDA for GPU-resident frames. The Intel module uses h264_qsv and includes DRM PRIME mapping, but also transfers the frame to RAM. SoftwareDecoder uses AV_CODEC_ID_H264 and libswscale for NV12. Automatic codec selection and integration across variants are next steps.

CPU–GPU synchronization

std::mutex serializes frame replacement and reading; av_frame_clone retains references. The renderer registers SDL3’s OpenGL textures, maps Y/UV and uses cudaMemcpy2DToArrayAsync with cudaMemcpyDeviceToDevice. After unmapping, cudaStreamSynchronize(0) waits for completion before drawing. This wait coordinates the CPU thread with completion of GPU copies before presentation.

Transport and timing

The source uses libdatachannel, unlike the libwebrtc named in the initial blueprint. H.264 NAL units are packetized into RTP, with host RTCP Sender Reports and NACK; the client depacketizes and requests a keyframe when the track opens. Timestamps advance by 90,000/FPS. A condition_variable waits for the track to open before sending: network coordination, separate from local frame availability in the renderer.

Libraries by responsibility

FFmpeg: libavcodec/libavutil for codecs and hardware contexts, libswscale for CPU conversion; libavfilter is included in the filter layer dependencies. PipeWire/SPA, Qt6/DBus, libdrm and EGL/GBM cover Linux capture and formats. CUDA and QSV/libmfx/libva support hardware variants; SDL3/OpenGL presents frames, libdatachannel transports media and nlohmann/json serializes messages.

04 /Status and next steps

In development. Next steps: consolidate codec selection, move color conversion to the GPU, expand macOS support and add audio and remote input.

Next projectDev Blog

Let’s build the next idea.

Let’s talk about opportunities, projects and the engineering challenges ahead.

Connect on LinkedIn
Find me elsewhereGitHub