Skip to main content

Audio files

Everything Lyno plays — greetings, announcements, music on hold — must be:

8 kHz, mono, 16-bit PCM WAV

That is not a preference. It is the format that can be encoded to G.711 alaw/ulaw on the wire without resampling, and anything else is rejected.

Validation is at startup

Files are parsed, validated and cached in memory when the PBX starts, so a bad file fails fast instead of mid-call. The checks and their messages:

ProblemMessage
Not a WAV containerwav: not a RIFF/WAVE file
Compressed audiowav: audio format N is not PCM
Stereowav: N channels, expected mono
Wrong sample ratewav: sample rate N, expected 8000
Wrong bit depthwav: N bits per sample, expected 16

Caching in memory also means the files are read once: replacing a WAV on disk has no effect until the PBX restarts.

Converting

ffmpeg -i input.mp3 -ar 8000 -ac 1 -acodec pcm_s16le output.wav
FlagWhy
-ar 80008 kHz sample rate
-ac 1mono
-acodec pcm_s16le16-bit signed little-endian PCM

Check a file you did not produce yourself:

ffprobe -v error -show_entries stream=codec_name,sample_rate,channels,bits_per_raw_sample output.wav

Two practical notes on the source material: normalise before downsampling, since 8 kHz mono leaves no headroom to fix a quiet recording, and low-pass anything with music in it — content above 4 kHz cannot survive the sample rate and only adds aliasing.

Where files live

Paths inside a tenant's files resolve against the tenant directory:

configs/tenants/acme/extensions/200.yaml
- type: play
file: ../../../sounds/welcome.wav # → sounds/ at the repository root

Paths in lyno.yaml resolve against the directory holding it:

configs/lyno.yaml
moh:
default: ../sounds/moh/default.wav

See Files and paths for the full rule.

Music on hold classes

Music on hold is referenced by class name, never by path, so the file behind a class can differ per tenant:

configs/lyno.yaml
moh:
default: ../sounds/moh/default.wav
classes:
jazz: ../sounds/moh/jazz.wav
corporate: ../sounds/moh/corporate.wav
configs/tenants/acme/tenant.yaml
moh:
default: ../../sounds/moh/acme.wav # acme's own default
classes:
jazz: ../../sounds/moh/acme-jazz.wav # overrides the global jazz

Resolution for a class name:

  1. the tenant's moh.classes;
  2. the global moh.classes;
  3. for the special name default, the tenant's moh.default then the global moh.default.

A step naming a class no level provides is a validation error, as is using default when neither level defines one.

Using a class:

- id: hold
type: moh
moh: jazz

- id: ring
type: dial
moh: default # caller hears music instead of ringback
target: {kind: ring_group, group: support}

Omitting moh on a dial step leaves the caller with normal ringback.

Length and looping

Music-on-hold classes loop until the call moves on. Announcements play once and then the plan continues, so an announcement that should be the last thing a caller hears needs an explicit hangup after it:

- id: closed
type: play
file: ../../../sounds/closed.wav
- id: done
type: hangup