Configure voice for the actual FiveM track
Legacy and Enhanced do not use the same voice configuration. Legacy uses Mumble. Enhanced replaces that implementation and provides server-managed voice channels; its Mumble compatibility layer is deprecated. Choose the track first, then choose one resource to own voice state. Running two voice resources that both change proximity, targets or submix assignments produces conflicting state.
The contracts below follow the pinned Cfx.re voice manual . They are not an assertion that this documentation task executed audio or a two-client voice test.
Legacy configuration
These client ConVars can be inspected or changed through F8. Replicated server configuration uses setr, subject to the resource and track supporting the setting.
| ConVar | Documented behavior |
|---|---|
voice_inBitrate | Input bitrate, 16000–128000; default 48000. Higher bitrate also consumes more bandwidth. |
voice_use2dAudio | Exclusively 2D audio; default false. |
voice_use3dAudio | Directional audio; deprecated on FiveM but available on RedM. The manual describes camera-relative positioning. Default false. |
voice_useSendingRangeOnly | Limit reception to clients whose sending range includes the listener; default false. |
voice_useNativeAudio | Game audio/filter support, needed for submixes; default false. This is not a switch between Mumble and another network transport. |
For Legacy submix work, a minimal server setting is:
setr voice_useNativeAudio trueSet proximity on the client with MumbleSetTalkerProximity(distance). For example, MumbleSetTalkerProximity(15.0) selects a 15-unit range. NetworkSetTalkerProximity also calls the game’s proximity native before configuring Mumble. Do not make competing per-frame calls from a second resource when a voice provider already owns that setting.
Legacy submixes and player IDs
CreateAudioSubmix(name) returns an existing submix for the same name, or creates it; -1 means creation failed. Apply effects, connect an output, then assign the result to a server player ID, not an array index or client-local player index.
This standalone client diagnostic is for a Legacy test server without another submix owner. Add it as client_script 'client.lua' in a resource depending on the voice setup. It changes how the local client processes the selected speakers; it does not authorize channel access.
-- client.lua (Legacy); enable voice_useNativeAudio before testing.
local submix = CreateAudioSubmix('docs_radio_preview')
local assigned = {}
if submix ~= -1 then
SetAudioSubmixEffectRadioFx(submix, 1)
SetAudioSubmixEffectParamInt(submix, 1, GetHashKey('default'), 1)
SetAudioSubmixEffectParamFloat(submix, 1, GetHashKey('freq_low'), 300.0)
SetAudioSubmixEffectParamFloat(submix, 1, GetHashKey('freq_hi'), 6000.0)
AddAudioSubmixOutput(submix, 1)
end
local function resetAssignments()
for serverId in pairs(assigned) do
MumbleSetSubmixForServerId(serverId, -1)
end
assigned = {}
end
RegisterCommand('docvoicepreview', function()
resetAssignments()
if submix == -1 then
print('Submix unavailable; check the track and native-audio setting.')
return
end
for _, playerIndex in ipairs(GetActivePlayers()) do
if playerIndex ~= PlayerId() then
local serverId = GetPlayerServerId(playerIndex)
MumbleSetSubmixForServerId(serverId, submix)
assigned[serverId] = true
end
end
end, false)
RegisterCommand('docvoicereset', resetAssignments, false)
AddEventHandler('onResourceStop', function(name)
if name == GetCurrentResourceName() then resetAssignments() end
end)Use docvoicereset after the test. This snapshot does not continuously enroll new speakers; a real provider must manage joins, disconnects and ownership of assignments. Player IDs can be reused. Do not retain a stale assignment table as a permanent identity registry.
SetAudioSubmixOutputVolumes(submix, outputSlot, frontLeft, frontRight, rearLeft, rearRight, channel5, channel6) controls each output channel. The output slot is not the destination submix ID passed to AddAudioSubmixOutput. To test front-left-only output, use slot 0 after creating the first output, set frontLeft to 1.0, and set the other five channel gains to 0.0. Restore the normal mix afterward; actual device layouts still need an audio test.
Legacy channels, listeners and volume overrides
| Native | Purpose and boundary |
|---|---|
MumbleCreateChannel(id) | Create a permanent channel. Temporary channels disappear when the last client leaves. |
MumbleSetVoiceChannel(id) | Change the local voice channel. Leaving Root can stop normal proximity chat. |
MumbleAddVoiceChannelListen(id) | Add a channel to the local listening set. |
MumbleRemoveVoiceChannelListen(id) | Undo that listening subscription. |
MumbleSetVolumeOverrideByServerId(id, volume) | Override a speaker’s local volume; -1.0 resets it. |
MumbleSetSubmixForServerId(id, submix) | Assign a speaker’s effect; -1 removes the assignment. |
A Legacy radio provider normally combines voice targets, proximity checks and a server roster rather than simply moving everyone out of Root. The server roster must validate requested channel IDs, permissions and membership, remove disconnected sessions and send the updated roster after changes. A client-side channel number, volume override or obfuscated event name is not a private-channel security boundary. Use the event-validation guide for server requests.
For provider implementation examples, the official manual links pma-voice . Inspect that provider’s actual version and supported track rather than assuming every fork is interchangeable. An external Mumble deployment is a provider-specific design; do not invent generic mumble_url settings for an unrelated resource.
Enhanced: enable the voice server
For the internal voice server, add the actual command to server.cfg:
voice_internalThe external voice-server feature is experimental. It uses another FXServer instance and a gateway, not a standalone Mumble daemon.
# External voice FXServer configuration.
# Configure sv_licenseKey privately: it MUST match the game server's key.
voice_external_host 0.0.0.0:30123 GAME_SERVER_IP:30122# Game FXServer configuration.
voice_external_connect 0.0.0.0:30122 VOICE_SERVER_IP:30123Replace the address placeholders. The first argument to voice_external_host is the external voice server’s bind address; its second argument points back to the gateway. The first argument to voice_external_connect binds that gateway on the game server; the second locates the external voice server. Configure network access deliberately and protect the shared license key. The game server synchronizes channel state to the voice instance. Test failure and reconnection before adopting this experimental topology.
Enhanced: compatibility is not equivalent to migration
setr sv_mumble true enables deprecated Mumble compatibility. The official manual warns that this permits client-controlled channels: a client can join a channel and listen. Do not enable it and then describe server-side radio groups as confidential.
The compatibility layer behaves as a single voice target for MumbleClearVoiceTarget, MumbleSetVoiceTarget and target-based natives. Migrating code that expects several independent targets requires a behavior review, not a prefix replacement.
The following Legacy ConVars are removed on Enhanced: voice_use2dAudio, voice_use3dAudio, voice_useSendingRangeOnly and voice_useNativeAudio. voice_inBitrate remains. New channel-management code should use the server APIs below.
Enhanced channel-management contracts
All six functions in this table execute server-side. Client audio positioning is a separate concern.
| Function | Contract |
|---|---|
CreateVoiceChannel(mode, maxDistance) | Returns a channel ID starting at 0. 65535 is the exhaustion sentinel, not a usable channel. |
DeleteVoiceChannel(channelId) | Delete an empty or populated channel. |
AddPlayerToVoiceChannel(channelId, playerSource) | Add a server player ID. Adding an existing member resets both mute and deaf state. |
RemovePlayerFromVoiceChannel(channelId, playerSource) | Remove membership; an absent member is a no-op. |
SetPlayerMutedInVoiceChannel(channelId, playerSource, muted) | A muted member can hear but cannot transmit. |
SetPlayerDeafInVoiceChannel(channelId, playerSource, deaf) | A deaf member can transmit but cannot hear. |
Mute and deaf can be combined. A listen-only member is muted and not deaf. The pinned manual’s listen-only example uses mute correctly, but an adjacent sentence reverses the wording; follow the function definitions, not that sentence.
| Mode | Behavior |
|---|---|
0 | Non-spatial channel; recipients come from channel membership. |
1 | Spatial channel; recipients are calculated using maxDistance. |
2 | Custom streaming. The pinned manual states that the API to feed custom streaming is not yet available; do not choose this as a working default. |
3 | Spatial behavior plus automatic deletion after the final member leaves. |
A disconnect automatically removes a player from all engine channels. Your resource still needs to clear its own membership, permission and session caches. Delete resource-owned persistent channels when the resource stops.
A bounded Enhanced radio example
This server-only resource owns one non-spatial test channel. There is no arbitrary user-controlled channel allocation. Permission comes from ACE; the client cannot supply another player’s ID. Do not use it alongside another provider that owns the same experience.
-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
server_script 'server.lua'-- server.lua (Enhanced only; voice_internal configured in server.cfg)
local radio = CreateVoiceChannel(0, 0.0)
local members = {}
local function leave(src)
if members[src] and radio ~= 65535 then
RemovePlayerFromVoiceChannel(radio, src)
end
members[src] = nil
end
RegisterCommand('docradio', function(src, args)
if src <= 0 then return end
if args[1] == 'leave' then
leave(src)
return
end
if radio == 65535 or not IsPlayerAceAllowed(src, 'docs.radio') then return end
if args[1] ~= 'join' and args[1] ~= 'listen' then return end
if not members[src] then
AddPlayerToVoiceChannel(radio, src)
members[src] = true
end
-- This command owns these two states. A production moderation layer
-- must combine its decisions here rather than having competing writers.
SetPlayerMutedInVoiceChannel(radio, src, args[1] == 'listen')
SetPlayerDeafInVoiceChannel(radio, src, false)
end, false)
AddEventHandler('playerDropped', function()
members[source] = nil
end)
AddEventHandler('onResourceStop', function(name)
if name ~= GetCurrentResourceName() then return end
if radio ~= 65535 then DeleteVoiceChannel(radio) end
members = {}
end)Grant docs.radio only to a test principal using the ACE permission workflow. Run /docradio join, /docradio listen and /docradio leave. Join does not create a proximity channel: this is a radio example, not a complete voice replacement. Revoke membership immediately when permissions change; changing an ACE alone does not automatically remove an already admitted engine member. A production provider also needs bounded request rates and a central moderation policy.
Acceptance and troubleshooting
Record the track, client build, FXServer artifact, provider revision and configuration. With two clients, test allowed and denied joins, listen-only behavior, mute, deaf, repeated joins, disconnect/reconnect, resource restart and proximity outside the radio. Test ID zero and allocation failure in control-flow tests. Only real clients establish that audio is audible and isolated as intended.
When no audio arrives, inspect devices and push-to-talk first, then transport connection, membership, mute/deaf, routing and provider ownership. When effects are wrong, inspect submix IDs and server IDs. When an Enhanced migration fails, check removed ConVars, compatibility assumptions and target semantics before changing unrelated firewall rules.
Continue with Legacy versus Enhanced, network IDs, server networking and source coverage.