You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.0 KiB

Asterisk MusicOnHold Notes

Basic Information

The way custom applications work with Asterisk, from my very very basic functional perspective is it expects 8khz audio on stdin. So if you had a binary called "audioplayer" that played audio to stdout as raw 8khz samples; then you'd just have to call that binary as a custom application in your musiconhold configuration: (Actually if you don't use format=mulaw with mplayer...it breaks)

[default]
mode = custom
application = /path/to/imaginary/binary

That's it. Provided it only literally sent data to stdout; this would work. But as I alluded to in the example; such a thing doesn't exist. Thankfully shell scripts count as applications and as long as we ultimately get some audio to stdout; it will work.

Caller-Selectable MusicOnHold

If you have multiple musiconhold streams; you can configure them so the caller can change the current channel from the keypad.

digit=

Add this option to your musiconhold class configuration, and select a single DTMF digit (I've not tested * or # but should work). When the caller is on hold; pressing the appropiate digit will switch the channel.

Icecast/Shoutcast Source Streams

This would be a lot easier if mplayer supported stdout; but it doesn't. So in order to make this work we need to make a named pipe, write to that pipe, and then somehow pipe the pipe to stdout.

#!/bin/bash

if [ `ps -aux | grep -c "/path/to/ices/conf.xml"` -lt 2 ]; then
/usr/bin/ices2  /path/to/ices/conf.xml
fi


PIPE="/tmp/asterisk-cmoh-pipe.$$"
mknod $PIPE p


mplayer http://server:port/mountpoint.ogg -softvol -really-quiet -quiet -ao pcm:file=$PIPE -af resample=8000,channels=1,format=mulaw,volume=-6:0 2>/dev/null | cat $PIPE 2>/dev/null

Translating it in to a step-by-step list:

  • Check if our stream is running and start if not
  • Define the path and name of our pipe using a pre-determined prefix and random number suffix.
  • Create this named pipe with mknod.
  • Play back the stream using mplayer; outputting PCM to the named pipe, resampled to 8khz, and downmixed.
  • CAT this pipe to stdout.

The previous version borrowed heavily from the "it works" method. A couple things are different now:

  • removed mplayer -playlist option since that was never a problem
  • changed to -softvol for no apparent reason
  • added if statement to handle checking of ices2
  • removed the statement to delete the pipe as it wasn't needed

The addition of the if statement solved a problem of making sure ices2 is feeding my icecast. I attempted to do this in systemd and it just failed. I may have figured out why, but doing it inside the script doesn't hurt and is easier than mucking around with systemd.

But if you're just feeding a bunch ogg files to icecast, can't you do it with without icecast and using a playlist?

Oh...sure...you can...but holy cow.

Classic Hold Version 2: Less Complex, More Headache

This entire thing winds up using entirely too much CPU. It's not just more than the above method, it's entirely too much for the task (in my opinion). The above method uses less than 1% CPU per stream; the below method uses about 15% per stream.

Let me start by saying this: getting this to work started out as medium-hard; before going full fledged pull-my-hair-out. This is because there's a number of additional steps we have to do that are much more involved than setting up and configuring icecast and ices2. Most of these steps weren't documented and I had to piece them together. This was on top of the fact that debugging on a headless machine for sound problems posed limitations. I had to test things on a local VM before attempting to implement them on the remote machine.

The concept sounds simple; just play some files from a playlist to a pipe; pipe it around; pipe it in to Asterisk. Okay, hold up. It sounds easy; but is it? There's a little issue in that if you tell most players you want to play back to a file and not a sound device; they decode, not play. We don't want, need, or can handle this. We need to simulate real-time playback. The only way to get this done effectively...was pulseaudio. I tried ALSA but this broke on ubuntu; they don't actually include the stuff to configure it. So you'll have a working alsa system; install alsa-utils, and lose it all.

But while we can create null sinks in PulseAudio stupid easily; there's a major drawback. It usually runs at the userlevel. Why does this matter? The asterisk user never actually logs in to the system...and therefore pulseaudio server never gets executed. So log in and activate? I tried that. So what you have to do...is run pulseaudio as system-wide and then configure it so the non-authenticated asterisk account can actually use it.

Install pulseaudio: sudo apt install pulseaudio

Create /etc/systemd/system/pulseaudio.service and fill it with this:

[Unit]
Description=PulseAudio Daemon

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
PrivateTmp=true
ExecStart=/usr/bin/pulseaudio --system --realtime --disallow-exit --no-cpu-limit

Open /etc/pulse/system.pa in an edior and modify the load-module module-native-protocol-unix line:

load-module module-native-protocol-unix auth-anonymous=1

(Yes, you are just adding auth-anonymous=1 to it)

Now you'll want the null-sinks to persist; so add this line to the bottom of system.pa:

load-module module-null-sink sink_name=moh1

You can duplicate this line for as many null-sinks as you want. You don't need a sink for each stream.

Now refresh services and boot everything up.

sudo systemctl daemon reload
sudo systemctl enable pulseaudio
sudo systemctl start pulseaudio

Got it? Good. That's it. That took me forever to figure out. You're welcome.

For the rest of the stuff we're using ogg123 and sox; so install vorbis-tools and sox.

Create a shell script, somewhere asterisk can read it:

#!/bin/bash
ogg123 -qZ -d pulse -o sink:moh1 -d au -f - -@ /etc/asterisk/list.m3u 2>/dev/null | sox -r 16000 -t au - -r 8000 -c 1 -t raw - 2>/dev/null

That's it. It's a one-liner; but since it's piped we can't just stick it in musiconold.conf. We don't need named pipes beause ogg123 and sox both support stdin/stdout. I'm probably wrong. Being a single line it probably will work directly as an application call in musiconhold.conf. If it wasn't such a lousy method I'd verify...but who cares. This method sucks.

ogg123 randomly plays from the playlist to sink:moh as well as stdout. The playback to the sink is just to limit the thing to realtime playback. It's literally the only reason we need Pulse. After that we pipe it in to sox, which resamples it from it's 16khz to 8khz and spits it to stdout. You can totally do this with just mplayer in a similar manner as above, just swapping your icecast playlist for the local one; keeping the same named pipe method. The problem I had with this (and went down this cliff of a different method); really nasty clicks between tracks. It was something between one player and the pipe.

When I originally came up with this ogg123/sox pipe; I had the hopes that it would be less CPU intensive. Fewer processes running, less complex, more streamline. But the reality was entirely different. After a couple of days I checked my load averages; they were somewhere between .38 and .48 over that period. This was pretty high, espeically compared to what the icecast/ices/mplayer method pulled...even with that one process constantly slamming for 100% CPU. Without running any of the MOH streams, the system load was back down to .01 over the course of about a half hour. I did some debugging and fixed that misbehaving mplayer setup, loaded it all up, and checked the load average after a few hours. It was only .02.

The Icecast2/ices2/mplayer method mentioned above is much more efficient CPU wise than the other method. But it's still something I'll keep up as an option.

Your MOH streams may "skip" or have other minor defects for the first few minutes. Mine cleared up after a few minutes and sounded like they had less buffer issues than the icecast.