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)
This is the first major re-write of this document. Previous versions are available in the git repository. Some of this work is the result of paid work; however the information I am sharing here does not under-cut said contracted work in a way I feel I can't share in this document. Most of it is largely just a better understanding of how musiconhold handles external input since my previous motivation was "just get it working" and not "get it working to fit the specifications of a project".
## Streaming MusicOnHold
When it comes to supplying musiconhold, asterisk offers numerous options. The two major ones are:
- file based playback
- custom application supplied playback
File based playback is just that, asterisk itself directly plays back files in to a channel when called. This seems easy, but it comes with a couple of drawbacks. The first is that it provides an expierence nothing like classic music on hold systems, starting every single instance at the beginning of a track. This also means it calls a playback instance for every person placed on hold; which can eat in to system resources if you've got a few hundred threads decoding files for a few hundred callers on hold. You really don't want your PBX machine doing a whole lot because real-time communication protocols aimed at low latency aren't waiting around for your data. In fact this could be an arguement against musiconhold in general; however if you pretend that not having it is not an option, then it's a good idea to reduce the amount of load other processes have to do.
Using a streaming source for musiconhold reduces a couple of these issues while inserting it's own. If configured properly, you can feed Asterisk the native format for your PBX; meaning it literally just has to pull this stream and pipe it in to channels. No conversion required...at least that Asterisk has to do. The audio is still converted somewhere; it just may be outside of Asterisk itself. Asterisk may still process the audio, as in the case with VOLUME, resulting in a conversion. However, when pulling from a streaming source; you aren't generating an extra thread for each caller placed on hold. You have a single source opposed to generating multiple. It does, however, restore that feeling of a traditional hold system; where you may get dumped in to it mid-song.
It does not, however, fix the "bad quality of music on hold music". That is a much more complicated matter.
### Just Tell Asterisk What Codec It Is And Pipe It In
All configuration for musiconhold is done in musiconhold.conf:
```
[default]
mode = custom
application = /path/to/imaginary/binary
application = /path/to/exe -and -additional arguments if necessary
format = slin16
digit = 1
```
While the options can get quite complex; for the sake of an Icecast stream, these are the only lines we have to worry with. It specifies a class-name, mode, the application we call, the format, and the optional digit. When res_musiconhold module is started (or reloaded); asterisk will run the listed application and expect raw samples matching the declared format over stdin; meaning your application has to support stdout. Let's take a look at two entries from my server as an example:
```
[prine]
mode = custom
application = /usr/bin/ogg123 -q -d raw -f - http://127.0.0.1:8000/prine.ogg
format = slin16
digit = 4
[pacr]
mode = custom
application = /usr/bin/ogg123 -q -d raw -f - http://127.0.0.1:8000/xmas.ogg
format = slin
digit = 5
```
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.
In both of these cases; we are directing ogg123 to output raw samples to stdout (`-f -`) from a locally hosted ogg stream. Ogg123 outputs PCM by default at the stream's native sample rate; so we call the `slin` and `slin16` codecs; `slin` is 8khz PCM audio, while `slin16` calls an internal sample rate converter set to 16khz. For the sake of optimization; I should use 8khz across the entire selection of streams. However, figuring out exactly how the format declarations worked in musiconhold.conf is a rather recent thing I bothered doing. I don't remember why I originally went with 16khz on the initial ones anyway. I think I encountered it as a limitation in the very *very* early attempts at getting it to work, likely abandoned whatever had that limitation, and just never changed.
### Caller-Selectable MusicOnHold
@ -24,9 +58,29 @@ 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
### CPU Usage
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.
There is a very slight difference in the amount of CPU usage for an 8khz stream vs a 16khz stream. After an uptime of 23 hours; here is the amount of CPU time consumed by each:
- ogg123 16khz: 2:35.07
- ogg123 8khz: 1:40.30
It's only a difference of a minute in CPU time; however that's a start indicator that feeding it 8khz ogg does result in some CPU savings.
## Using A Script For Application - Original Method
Originally I came up with this roundabout method of getting mplayer involved; as it could literally just output 8khz mulaw directly in to Asterisk. The downside is that mplayer doesn't support stdout. The easy way around this was to just call mplayer with a script, write to a named pipe, and then cat the pipe to stdout.
I ran this for over a year. It worked; hence I never bothered to improve upon it. In fact; the script method was the easiest way to make sure each ices2 had started and was streaming:
```
#!/bin/bash
@ -34,8 +88,6 @@ This would be a lot easier if mplayer supported stdout; **but it doesn't**. So i
if [ `ps -aux | grep -c "/path/to/ices/conf.xml"` -lt 2 ]; then
I changed distributions a while back (and I may switch again) and was greeted with an mplayer that tried to use 100% CPU for each stream. As a quick fix; I replaced the mplayer line with one involving ogg123 and sox. This same combination would be used in an alternate "icecast free" setup that was abandoned.
```
#!/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
ogg123 -q -d au -f - http://127.0.0.1:8000/mountpoint.ogg | sox -r 16000 -t au - -r 8000 -c 1 -t raw - 2>/dev/null
```
However...figuring out how to just tell Asterisk to take in 16khz from ogg123 directly eliminated the need for sox in the chain. The ultimate need for the script was eliminated after I got the replacement distro not just starting ices streams reliably; but it was easy to tell it asterisk depended on ices...which depends on icecast.
However the script remains perfectly valid if you have to use named pipes or really want to ensure you've got a source streaming before Asterisk attempts to run the application.
## Troubleshooting
In just about every case, the following commands are you friends:
`module reload res_musiconhold` in the Asterisk console causes it to reload musiconhold and activate any changes. Running streams aren't affected.
`sudo asterisk -rx 'module reload res_musiconhold'` on the command line accomplishes the same thing, without being in asterisk terminal.
As briefly mentioned; any streams that are running and not changed are not affected, applications no longer used are exited, and applications not running are launched.
### HELP! res_musiconhold.c:701 monmp3thread: poll() failed: Interrupted system call IS FILLING MY ASTERISK CONSOLE!
Blindly type `exit` and hit enter; this should put you back at the shell. Something went fatally wrong with your playback application; it either didn't load, not sending data, or sending really bad data. Go back to `musiconhold.conf`, undo whatever you just changed, save the file, and return to console. Run `sudo asterisk -rx 'module reload res_musiconhold'`. You should get confirmation that the module was reloaded. Return to the asterisk console and it should have stopped. You will need to figure out what failed from there.
In some cases; doing this may cause Asterisk to crash. Sorry. I don't know why that happens.
### No Audio!
"No Audio" is difficult because it can be either due to the lack of an audio stream causing the playback application to fail; or an error in the command you call for the playback application. You'll need to determine which one of the two it is, make corrections, and reload the module.
### Everything sounds like demons/chipmunks.
Playback speed wrong? This is a sample rate error. Check you're using the right slin codec option. It is entirely possible to feed Asterisk 16khz PCM and have it play it back at 8khz; or vise versa. Fix the format and reload.
- 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.
### It's just static!
The previous version borrowed heavily from the "it works" method. A couple things are different now:
This is a non-fatal codec mismatch. I've seen it in times where mplayer was outputting mulaw but the Asterisk server was expecting PCM; or vise-versa.
- 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 Audio On The Phone Is Way Behind What I Get Out Of Icecast
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.
I haven't figured this one out. I don't even know how it would buffer 15 minutes of audio unless it's the result of a dropped sample here or there.
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***.
## Experimental Non-Icecast Method I Don't Recommend
### AKA "Accidental Instructions For System Wide Pulseaudio"
## 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.***
***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. It's only real value is instructions on doing system-wide PulseAudio.***
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.