← All Tutorials

Asterisk Music on Hold — Custom Audio & Per-Queue MOH

Infrastructure & DevOps Intermediate 11 min read #65

Master custom music on hold setup, per-queue MOH routing, and troubleshooting for high-volume ViciDial production systems

Prerequisites

Before implementing custom MOH in Asterisk/ViciDial, verify:

Run asterisk -rx "core show version" to confirm Asterisk is running and accessible.


Understanding Asterisk Music on Hold Architecture

How MOH Works in Asterisk

Music on Hold is triggered when a call enters a queue, is placed on hold, or meets a Wait() application. Asterisk uses:

  1. MOH Classes — defined in musiconhold.conf, each with a directory or file
  2. MOH Selection — via dialplan (SetMusicOnHold()) or queue configuration
  3. MOH Playback — native playback to calling party via audio stream
  4. MOH Generators — optional generator for on-the-fly tone/silence generation

Default MOH directory: /var/lib/asterisk/moh/

ViciDial-Specific MOH Integration

ViciDial uses MOH at these points:


Core MOH Configuration Files

Primary Configuration: musiconhold.conf

Located at /etc/asterisk/musiconhold.conf, this is the master MOH configuration.

[general]
; Default class if none specified
default=default

; Directory scanning mode - rescan disk for new files automatically
; Useful for dynamic queue changes
moh_quiet=yes

[default]
; Standard MP3 playback with quieting
mode=files
directory=/var/lib/asterisk/moh/default
; Quieting level (0-100)
quiet=100

[on-hold]
mode=files
directory=/var/lib/asterisk/moh/on-hold
quiet=100

[queue-sales]
mode=files
directory=/var/lib/asterisk/moh/sales
quiet=100

[queue-support]
mode=files
directory=/var/lib/asterisk/moh/support
quiet=100

[queue-billing]
mode=files
directory=/var/lib/asterisk/moh/billing
quiet=100

[silence]
; Silent MOH - useful for internal transfers
mode=silence

[tone]
; Audible ringback tone
mode=files
directory=/var/lib/asterisk/moh/tone
quiet=0

Key Parameters:

Extensions Dialplan: extensions-vicidial.conf

Add MOH selection logic to your dialplan:

[from-internal]
; Example: set MOH before queue
exten => 100,1,NoOp(Incoming call from ${CALLERID(num)})
exten => 100,n,SetMusicOnHold(queue-sales)
exten => 100,n,Queue(sales-queue,t)
exten => 100,n,Hangup()

exten => 101,1,NoOp(Support queue)
exten => 101,n,SetMusicOnHold(queue-support)
exten => 101,n,Queue(support-queue,t)
exten => 101,n,Hangup()

Queue Configuration: queues.conf

ViciDial typically manages queues via database, but direct queues.conf configuration is still used:

[sales-queue]
strategy=linear
timeout=30
retry=5
maxlen=0
; Music on hold class for this queue
musiconhold=queue-sales
; Announce position every 60 seconds
announce-frequency=60
announce-round-seconds=10
setinterfacevar=yes
setqueuevar=yes

[support-queue]
strategy=linear
timeout=30
retry=5
maxlen=0
musiconhold=queue-support
announce-frequency=120

[callback-queue]
strategy=linear
timeout=300
retry=10
maxlen=100
musiconhold=on-hold

Setting Up Per-Queue Music on Hold

Step 1: Create Per-Queue MOH Directories

Establish separate directories for each queue:

mkdir -p /var/lib/asterisk/moh/{default,sales,support,billing,vip}
chown -R asterisk:asterisk /var/lib/asterisk/moh/
chmod 755 /var/lib/asterisk/moh/*

Step 2: Prepare and Validate Audio Files

Audio files must meet specific requirements:

For WAV files:

ffmpeg -i original-music.mp3 -acodec pcm_s16le -ar 8000 -ac 1 /var/lib/asterisk/moh/sales/music.wav

For MP3 files (if using mp3 mode):

ffmpeg -i original-music.mp3 -acodec libmp3lame -ar 8000 -ac 1 -b:a 32k /var/lib/asterisk/moh/sales/music.mp3

Batch conversion for directory:

#!/bin/bash
SOURCE_DIR="/tmp/moh-source"
DEST_DIR="/var/lib/asterisk/moh/sales"

for file in ${SOURCE_DIR}/*; do
  filename=$(basename "$file" | cut -d. -f1)
  ffmpeg -i "$file" -acodec pcm_s16le -ar 8000 -ac 1 "${DEST_DIR}/${filename}.wav"
done

chown -R asterisk:asterisk ${DEST_DIR}
chmod 644 ${DEST_DIR}/*

Validate audio format:

sox /var/lib/asterisk/moh/sales/music.wav -n stat
# Output should show: Sample Rate 8000, Channels 1

Step 3: Update musiconhold.conf with Queue-Specific Entries

[queue-sales]
mode=files
directory=/var/lib/asterisk/moh/sales
quiet=70

[queue-support]
mode=files
directory=/var/lib/asterisk/moh/support
quiet=70

[queue-billing]
mode=files
directory=/var/lib/asterisk/moh/billing
quiet=70

[queue-vip]
mode=files
directory=/var/lib/asterisk/moh/vip
quiet=60

Step 4: Configure Queues with MOH Class Assignment

Edit or create /etc/asterisk/queues.conf:

[general]
shared_lastcall=yes
monitor-type=MixMonitor

[sales-queue]
strategy=linear
timeout=30
retry=5
musiconhold=queue-sales
autofill=yes
setinterfacevar=yes
setqueuevar=yes
announce-frequency=60
announce-round-seconds=10

[support-queue]
strategy=linear
timeout=30
retry=5
musiconhold=queue-support
autofill=yes
setinterfacevar=yes
setqueuevar=yes

[billing-queue]
strategy=ring
timeout=45
retry=10
musiconhold=queue-billing
maxlen=50

[callback-queue]
strategy=linear
timeout=300
retry=15
musiconhold=queue-sales
maxlen=100
joined-sound=queue-youare

Step 5: Reload and Test MOH

After configuration changes, reload Asterisk:

asterisk -rx "reload musiconhold"
asterisk -rx "reload queues"
asterisk -rx "reload res_musiconhold"

Verify MOH classes are loaded:

asterisk -rx "moh show classes"

Expected output:

Class: queue-sales
   Mode: files
   Directory: /var/lib/asterisk/moh/sales
   Quieting: 70

Class: queue-support
   Mode: files
   Directory: /var/lib/asterisk/moh/support
   Quieting: 70

Class: queue-billing
   Mode: files
   Directory: /var/lib/asterisk/moh/billing
   Quieting: 70

ViciDial Database Configuration

Integrating MOH with ViciDial Queue Tables

ViciDial stores queue configuration in the asterisk database. Use these tables:

Table: vicidial_queues

SELECT * FROM vicidial_queues WHERE queue_name='sales-queue'\G

Key fields:

Update ViciDial Queue MOH via SQL:

UPDATE vicidial_queues SET music_on_hold='queue-sales' WHERE queue_name='sales-queue';
UPDATE vicidial_queues SET music_on_hold='queue-support' WHERE queue_name='support-queue';
UPDATE vicidial_queues SET music_on_hold='queue-billing' WHERE queue_name='billing-queue';
COMMIT;

Verify via ViciDial web UI: Navigate to /vicidial/admin.phpQueues → Edit each queue → Set "Music on Hold" dropdown to desired class.

Database Sync Script

Create a script to sync MOH settings from database to dialplan:

#!/bin/bash
# /usr/local/bin/sync-moh-to-dialplan.sh

MYSQL_USER="root"
MYSQL_PASS="your_password"
MYSQL_DB="asterisk"
OUTFILE="/etc/asterisk/queues-vicidial.conf"

cat > ${OUTFILE} << 'EOF'
[general]
shared_lastcall=yes
monitor-type=MixMonitor

EOF

mysql -u${MYSQL_USER} -p${MYSQL_PASS} ${MYSQL_DB} << 'SQLEOF' | while read queue_name moh_class; do
SELECT queue_name, COALESCE(music_on_hold, 'default') FROM vicidial_queues WHERE active='Y' ORDER BY queue_name;
SQLEOF

cat >> ${OUTFILE} << QEOF
[${queue_name}]
strategy=linear
timeout=30
retry=5
musiconhold=${moh_class}
autofill=yes
setinterfacevar=yes
setqueuevar=yes
QEOF

done

chown asterisk:asterisk ${OUTFILE}
asterisk -rx "reload queues"

Schedule with cron:

0 */4 * * * /usr/local/bin/sync-moh-to-dialplan.sh

Advanced MOH Techniques

Dynamic MOH Selection via Channel Variables

Use SetMusicOnHold() in dialplan to override queue-level MOH:

[from-vicidial]
exten => _X.,1,NoOp(Dynamic MOH selection)
exten => _X.,n,Set(CALLER_LANG=${CALLERID(name):0:2})
exten => _X.,n,GotoIf($["${CALLER_LANG}"="ES"]?spanish:english)
exten => _X.,n(english),SetMusicOnHold(queue-sales)
exten => _X.,n,Goto(queue-main)
exten => _X.,n(spanish),SetMusicOnHold(queue-sales-spanish)
exten => _X.,n(queue-main),Queue(sales-queue,t)
exten => _X.,n,Hangup()

Custom Voicemail Greeting Integration

Combine MOH with IVR greetings:

[from-queue-announcement]
exten => s,1,NoOp(Queue announcement with MOH)
exten => s,n,SetMusicOnHold(queue-sales)
exten => s,n,Playback(queue/position-in-queue)
exten => s,n,Playback(queue/est-wait-time)
exten => s,n,Queue(sales-queue,t)
exten => s,n,Hangup()

Silence on Hold for Internal Transfers

For agent-to-agent transfers, use silent MOH:

[agent-transfer]
exten => *1,1,NoOp(Blind transfer to ${ARG1})
exten => *1,n,SetMusicOnHold(silence)
exten => *1,n,Blind Transfer(${ARG1})
exten => *1,n,Hangup()

Conditional MOH Based on Queue Depth

[from-vicidial]
exten => 100,1,NoOp(Queue ${QUEUENAME} check)
exten => 100,n,Queue(sales-queue,t)
exten => 100,n,GotoIf($["${QUEUE_MEMBER_COUNT}">"5"]?busy:available)
exten => 100,n(available),SetMusicOnHold(queue-sales)
exten => 100,n,Goto(end)
exten => 100,n(busy),SetMusicOnHold(queue-vip)
exten => 100,n(end),Hangup()

Looped MOH with Periodic Announcements

Create a custom application that plays MOH, then an announcement, repeatedly:

[queue-with-announcements]
exten => s,1,NoOp(MOH + periodic announcement)
exten => s,n,SetMusicOnHold(queue-sales)
exten => s,n,WaitExten(30)
exten => s,n,Playback(queue/estimated-wait)
exten => s,n,WaitExten(30)
exten => s,n,Queue(sales-queue,t)
exten => s,n,Hangup()

Monitoring and Performance Tuning

Check MOH Playback in Real-Time

View active calls using MOH:

asterisk -rx "core show calls" | grep -i "music\|Queue"

Sample output:

SIP/2001-00000001     Queue(sales-queue,t)    0:00:15
SIP/2002-00000002     Queue(support-queue,t)  0:00:42

Monitor MOH File Usage

Track which MOH classes are actively used:

asterisk -rx "moh show classes"
asterisk -rx "core show channels concise" | awk -F'!' '{print $10}' | sort | uniq -c

Audio Quality Diagnostics

Verify audio playback quality:

sox /var/lib/asterisk/moh/sales/music.wav -n remix - | sox - -n stat
# Check: RMS amplitude, Peak amplitude, Crest factor

Optimize quiet Parameter

Test MOH with different quieting levels:

# Lower quiet value = louder
# Higher quiet value = quieter
# Typical range: 60-80

[queue-sales]
quiet=70  # Balanced
quiet=40  # Louder
quiet=100 # Quieter/silence preservation

CPU Usage Monitoring

MOH playback CPU impact is minimal, but monitor with:

top -b -n 1 | grep asterisk
ps aux | grep asterisk | grep -v grep

Troubleshooting

MOH Not Playing When Call Enters Queue

Symptom: Dead silence on hold instead of music.

Diagnosis:

asterisk -rx "moh show classes"

If MOH classes don't appear or are empty:

  1. Check musiconhold.conf syntax:

    asterisk -m -c "moh reload" 2>&1 | grep -i error
    
  2. Verify audio files exist and are readable:

    ls -la /var/lib/asterisk/moh/queue-sales/
    chown -R asterisk:asterisk /var/lib/asterisk/moh/
    chmod 755 /var/lib/asterisk/moh/*
    chmod 644 /var/lib/asterisk/moh/*/*
    
  3. Reload MOH module:

    asterisk -rx "module reload res_musiconhold.so"
    asterisk -rx "reload musiconhold"
    

MOH Class Not Found for Queue

Symptom: Asterisk CLI shows error: Unable to locate MOH class queue-sales

Solution:

  1. Verify queue name matches musiconhold.conf:

    SELECT queue_name, music_on_hold FROM vicidial_queues;
    
  2. Check queues.conf has matching musiconhold entry:

    grep -A 5 "^\[sales-queue\]" /etc/asterisk/queues.conf | grep musiconhold
    
  3. Reload both configs:

    asterisk -rx "reload musiconhold"
    asterisk -rx "reload queues"
    

Audio Distortion or Quality Issues

Symptom: MOH playback sounds garbled, clipped, or distorted.

Solutions:

  1. Check audio file format:

    ffprobe -v error -show_entries stream=sample_rate,channels,codec_name \
      -of default=noprint_wrappers=1 /var/lib/asterisk/moh/sales/music.wav
    # Should output: sample_rate=8000, channels=1
    
  2. Re-encode to correct format:

    ffmpeg -i /var/lib/asterisk/moh/sales/music.wav -acodec pcm_s16le \
      -ar 8000 -ac 1 /var/lib/asterisk/moh/sales/music-fixed.wav
    mv /var/lib/asterisk/moh/sales/music-fixed.wav \
      /var/lib/asterisk/moh/sales/music.wav
    
  3. Adjust quiet parameter (reduce to increase volume):

    [queue-sales]
    quiet=50  # Increase volume (was 70)
    
  4. Check Asterisk log for codec errors:

    tail -100 /var/log/asterisk/messages | grep -i "moh\|music\|codec"
    

Wrong MOH Playing for Queue

Symptom: Queue plays default MOH instead of queue-specific MOH.

Diagnosis:

  1. Verify queue configuration in database:

    SELECT queue_name, music_on_hold FROM vicidial_queues WHERE queue_name='sales-queue';
    
  2. Check dialplan override:

    grep -n "SetMusicOnHold" /etc/asterisk/extensions-vicidial.conf
    
  3. Verify queues.conf musiconhold assignment:

    grep -A 10 "^\[sales-queue\]" /etc/asterisk/queues.conf
    
  4. Force reload:

    asterisk -rx "reload musiconhold"
    asterisk -rx "reload queues"
    asterisk -rx "reload res_musiconhold.so"
    

MOH Stops After Few Seconds

Symptom: Music plays briefly, then cuts out; silence follows.

Causes:

  1. Audio file too short or corrupt:

    sox /var/lib/asterisk/moh/sales/music.wav -n stat
    # Check duration; should be 2+ minutes
    
  2. Directory permission issue:

    ls -la /var/lib/asterisk/moh/sales/
    # Should show: drwxr-xr-x asterisk:asterisk
    
  3. Asterisk process permissions:

    ps aux | grep asterisk | grep -v grep
    # Should run as asterisk user
    

Fix:

chown -R asterisk:asterisk /var/lib/asterisk/moh/
chmod -R 755 /var/lib/asterisk/moh/
systemctl restart asterisk

High CPU Usage with MOH

Symptom: Asterisk CPU usage spikes when MOH is active.

Solutions:

  1. Use MP3 or WAV, not generator-based MOH:

    [queue-sales]
    mode=files  # Not mode=mp3 or mode=DAHDI
    
  2. Verify audio codec is efficient:

    ffprobe /var/lib/asterisk/moh/sales/music.wav
    # Should show PCM 8-bit or 16-bit, 8000 Hz
    
  3. Reduce number of concurrent MOH streams if necessary:

    asterisk -rx "moh show files"
    

Logs Not Showing MOH Activity

Enable MOH debug logging:

asterisk -rx "core set debug 3"
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages | grep -i moh

Check for specific errors:

grep -i "moh\|music\|hold" /var/log/asterisk/messages | tail -50

Summary

Implementing custom per-queue Music on Hold in Asterisk/ViciDial requires:

  1. Audio Preparation: Convert media to 8 kHz, 16-bit mono WAV files for optimal quality and compatibility
  2. Directory Structure: Organize MOH files in /var/lib/asterisk/moh/ with separate subdirectories per queue
  3. Configuration: Define MOH classes in musiconhold.conf and assign to queues via queues.conf or ViciDial database
  4. Dialplan Integration: Use SetMusicOnHold() for dynamic selection and combine with queue applications
  5. Database Sync: Link ViciDial vicidial_queues table music_on_hold field to Asterisk configurations
  6. Validation & Testing: Reload modules, verify with CLI commands, test with live calls
  7. Monitoring: Use asterisk -rx commands to monitor active MOH playback and troubleshoot issues
  8. Performance: Ensure proper file permissions, audio quality, and system resource allocation

Key commands for day-to-day operations:

# Reload after configuration changes
asterisk -rx "reload musiconhold"
asterisk -rx "reload queues"

# Verify MOH is loaded
asterisk -rx "moh show classes"

# Monitor active queues with MOH
asterisk -rx "core show calls" | grep Queue

# Restart Asterisk if issues persist
systemctl restart asterisk

With proper setup, ViciDial agents and customers will experience professional, branded music on hold tailored to each queue, improving perceived call quality and reducing perceived wait times.

Stuck on something specific?

Book a free 30-minute call. I run ViciDial centers across 3 countries and can usually unblock your setup in one session — or build it for you.

Book a Free Consultation