Native audio in FiveM: AWC wave packs, dat54 sound data and custom soundsets
Most custom sounds on FiveM servers play through NUI: an HTML audio element in a browser layer, with volume faked from distance. It works, but it is not the game's audio engine. It ignores reverb in tunnels, does not duck under explosions, cannot attach to a moving vehicle properly and is heard at full clarity through walls. Native audio uses the real engine: your sound is packed into the same container format Rockstar uses and played with the same natives as a vanilla siren or door buzzer. It takes more setup, and this guide walks through every piece of it.
Short answer: Native audio needs two things streamed from a resource: an .awc wave pack holding the audio, and a .dat54.rel sound-data file that defines named sounds and a soundset pointing at that wave pack. Declare them with data_file 'AUDIO_WAVEPACK' (the folder) and data_file 'AUDIO_SOUNDDATA' (the path without 54.rel), load the bank in a script with RequestScriptAudioBank('folder/bank', false), then play sounds with PlaySoundFromEntity, PlaySoundFromCoord or PlaySoundFrontend using the sound name and soundset name.

#Native audio versus NUI audio
| NUI audio | Native audio | |
|---|---|---|
| Setup | Drop an .ogg in the resource | Build .awc and .dat54.rel files |
| Positional | Faked with distance maths | Real 3D, attached to entities |
| Environment | None | Reverb, occlusion, game volume sliders |
| Follows a moving car | Only with constant updates | Yes, when played from the entity |
| Best for | UI sounds, music, simple effects | Sirens, vehicle and weapon sounds, world effects |
If you only need a phone ringtone or a notification ping, NUI audio is simpler and entirely adequate; the basics are in custom sounds in FiveM. Native audio pays off when the sound belongs in the world: a siren that fades behind buildings, a door alarm that sounds like it is coming from the door, an engine note.
#The files involved
| File | What it is | Declared as |
|---|---|---|
mybank.awc | Audio Wave Container holding one or more waves | Inside the AUDIO_WAVEPACK folder |
mysounds.dat54.rel | Sound data: sounds, soundsets, references to waves | AUDIO_SOUNDDATA |
mygame.dat151.rel | Game data: vehicle, radio and other game objects | AUDIO_GAMEDATA, only when needed |
A simple custom sound only needs the first two. Game data (dat151) is used for vehicle engine sounds, radio stations and similar game objects, and is not needed for sounds a script triggers by name.
Inside the sound data, two kinds of entries do the work for scripted sounds. A simple sound points at one wave inside a container, identified by the container name (the wave-pack folder and .awc name) and the wave name. A soundset maps script-facing names to those sounds. Scripts refer to the soundset name and the sound name inside it, never to the wave directly. More elaborate sound types exist for loops, randomised variations and crossfades, but a simple sound per wave covers alarms, sirens and one-shot effects.
#Building the audio
- Prepare mono, 16-bit WAV files. 32 kHz or 48 kHz sample rates are the safe choices; 48 kHz is expected for radio content.
- Trim silence and normalise loudness. Native sounds are mixed with the game, so a file mastered as loud as possible will clip next to gunfire.
- Pack the waves into an
.awc. CodeWalker can import and export.awcvia XML, and community tools such as native-audio-tool and AWCMaster automate the conversion and generate the matching sound data. - Create the
.dat54.relwith a simple sound per wave and a soundset listing them. Generating it with a tool avoids hand-editing hashes. - Choose PCM for quality or ADPCM for roughly a quarter of the size. ADPCM is fine for most effects.
#The resource layout and manifest
my_audio/
├─ fxmanifest.lua
├─ client.lua
├─ audiodirectory/
│ └─ my_sounds.awc
└─ data/
└─ my_sounds.dat54.relfx_version 'cerulean'
game 'gta5'
client_script 'client.lua'
files {
'audiodirectory/my_sounds.awc',
'data/my_sounds.dat54.rel',
}
data_file 'AUDIO_WAVEPACK' 'audiodirectory'
data_file 'AUDIO_SOUNDDATA' 'data/my_sounds.dat'Two details trip almost everyone. AUDIO_WAVEPACK points at the folder, not the .awc file. AUDIO_SOUNDDATA points at the sound-data file without the 54.rel part: the file on disk is my_sounds.dat54.rel, the manifest says data/my_sounds.dat. Audio data files cannot be globbed, so each needs its own data_file line, and both files must also be listed in files so clients download them.
Some resources place these files under stream/ instead; that works too, as long as the files and data_file paths match. Keeping audio out of stream/ makes it clearer which files are loaded through data-file entries.
#Loading the bank and playing sounds
local BANK = 'audiodirectory/my_sounds'
local SOUNDSET = 'my_soundset'
local bankReady = false
CreateThread(function()
local deadline = GetGameTimer() + 5000
while GetGameTimer() < deadline do
if RequestScriptAudioBank(BANK, false) then break end
Wait(100)
end
bankReady = true
end)
local function playOnEntity(name, entity)
if not bankReady then return end
local id = GetSoundId()
PlaySoundFromEntity(id, name, entity, SOUNDSET, false, 0)
return id
end
local function stop(id)
if not id then return end
StopSound(id)
ReleaseSoundId(id)
end
AddEventHandler('onResourceStop', function(res)
if res == GetCurrentResourceName() then
ReleaseNamedScriptAudioBank(BANK)
end
end)The bank name is the wave-pack folder and the .awc name without extension, joined with a slash. Some resources write it with a backslash, as in DLC_MYSIRENS\\SIRENPACK_ONE in Lua source; both forms refer to the same bank.
The deadline loop matters. RequestScriptAudioBank is documented to return true once the bank is loaded, but there is an open FiveM issue where it keeps returning false even though the bank loaded and sounds play. A while not RequestScriptAudioBank(...) do Wait(0) end loop can therefore spin forever. Retry for a few seconds, then carry on.
| Native | Use |
|---|---|
PlaySoundFrontend(-1, name, soundset, true) | Non-positional UI sound |
PlaySoundFromEntity(id, name, entity, soundset, false, 0) | Attached to a ped, vehicle or object |
PlaySoundFromCoord(id, name, x, y, z, soundset, false, range, false) | Fixed world position with a range |
GetSoundId() / ReleaseSoundId(id) | Allocate and free a handle for stoppable sounds |
StopSound(id), HasSoundFinished(id) | Stop a looping sound, check completion |
Pass -1 as the sound id for fire-and-forget sounds. Anything that loops, such as a siren or an alarm, needs a real id from GetSoundId so you can stop it, and the id must be released afterwards or you slowly run out of sound slots.
#Making other players hear it
The isNetwork flag on the play natives exists, but it relies on every client having the same bank loaded at the same time, and it gives you no control over who hears what. The dependable pattern is to let the server fan the event out and have each nearby client play the sound locally from the right entity.
RegisterNetEvent('my_audio:alarm', function(netId)
local src = source
local entity = NetworkGetEntityFromNetworkId(netId)
if entity == 0 then return end
local origin = GetEntityCoords(entity)
for _, id in ipairs(GetPlayers()) do
local ped = GetPlayerPed(id)
if ped ~= 0 and #(GetEntityCoords(ped) - origin) < 120.0 then
TriggerClientEvent('my_audio:playAlarm', id, netId)
end
end
end)RegisterNetEvent('my_audio:playAlarm', function(netId)
local entity = NetToEnt(netId)
if not DoesEntityExist(entity) then return end
playOnEntity('alarm_loop', entity)
end)Validate on the server who may trigger a sound; an event that any client can fire at everyone within 120 metres is an invitation to abuse. Because each client plays from the entity, the engine handles distance, occlusion and Doppler on its own.
#Vehicle and siren sounds
Add-on engine sounds use the same wave-pack mechanism plus game data. A typical vehicle sound resource streams .awc files, a dat54.rel and a dat151.rel, and declares AUDIO_WAVEPACK, AUDIO_SOUNDDATA and AUDIO_GAMEDATA. The car then points at the sound through <audioNameHash> in its vehicles.meta, as described in vehicle meta files.
- Start the sound resource before the vehicle resources that reference it.
- Keep the original file names from the sound pack; the names are baked into the data files.
- Test a sound on any car with
ForceUseAudioGameObject(vehicle, 'soundname')before editing metas. - Siren resources usually expose a soundset and sound names, and your emergency-lighting script plays them with
PlaySoundFromEntity.
#Troubleshooting silence
| Check | How |
|---|---|
| Files downloaded | Both listed in files, resource started, no errors in F8 |
| Data-file paths | Folder for AUDIO_WAVEPACK, .dat without 54.rel for AUDIO_SOUNDDATA |
| Bank name | folder/awcname with no extension |
| Sound and soundset names | Exactly as in the sound data; open the .rel as XML in CodeWalker to confirm |
| Wave format | Mono 16-bit; re-export stereo files |
| Game volume | The SFX slider affects native sounds, unlike NUI audio |
Native audio adds download size for every player. Keep effects short, prefer ADPCM, and group related sounds into one bank rather than one bank per sound. A bank only occupies memory while it is loaded, so resources with many situational sounds, such as a heist script, can request their bank when the activity starts and release it with ReleaseNamedScriptAudioBank when it ends.
Questions
What is an .awc file in FiveM?
AUDIO_WAVEPACK data file and played through soundsets defined in a .dat54.rel.Why is AUDIO_SOUNDDATA path missing the 54.rel?
.dat path and the game appends the type suffix itself, so data/my_sounds.dat54.rel is declared as data/my_sounds.dat.What bank name do I pass to RequestScriptAudioBank?
.awc name without extension, for example audiodirectory/my_sounds.Why does RequestScriptAudioBank always return false?
How do other players hear my native sound?
Ready to pick a map?
Twelve themes on three base map styles, $8 each, instant download.
Relevant to what you just read