larperCord Buy Me a Coffee at ko-fi.com Português

Voice calls

larperCord joins real Discord voice channels, with end-to-end encryption. Everything voice-related runs on its own thread, created only when you join a call. Outside a call, the module uses no CPU and the app doesn't initialize audio, unless you open Settings → Voice, which reads the microphone for its level meter while it's open.

Using it

Click a voice channel to join. The panel above your user shows the call, with mute, deafen and disconnect. You can also leave through "Leave call" when you right-click the voice channel. Whoever is talking gets a ring around their avatar.

The mixer gives each person their own volume.

The call mixer

The panel also shows your ping, the connection quality by color and when you're transmitting, and it warns you if the microphone doesn't open or if the server muted or deafened you. Other people's mute and deafen show in the call list.

Channels where you lack the permission to connect are shown with a padlock, and the app doesn't try.

Settings → Voice

The voice settings page

How it works

LayerWhat it uses
SignalingVoice gateway v8 over WebSocket, with heartbeat (seq_ack) and RESUME when the connection drops
ConnectionIP discovery (74-byte packet) and Select Protocol over UDP
Transport encryptionRTP with aead_xchacha20_poly1305_rtpsize
End-to-end encryptionDAVE over MLS, through libdave and MLSpp
CodecOpus at 48 kHz stereo, 20 ms per packet. Lost packets become Opus packet-loss concealment
AudioSDL3 mixes. Each person is one SDL_AudioStream on the same output device, and their volume is the stream's gain. The microphone is a stream read every 20 ms

A few details that took work to get right:

Limits today

Debugging

bash
LARPER_VOICE_DEBUG=1 larpercord

This prints opcodes, close codes and DAVE events, and microphone level and frames sent. It never prints the token or any keys.

To test only the connection, --voice-test joins a channel for 25 seconds, always muted:

bash
LARPER_TOKEN=... larpercord --voice-test GUILD_ID CHANNEL_ID

The protocol follows Discord's voice documentation and the DAVE protocol documentation.

Source of this page on GitHub