Jay bad2944496 | 2 years ago | |
---|---|---|
readme.MD | 2 years ago |
readme.MD
Asterisk MusicOnHold Notes (Streaming and Classic Hold)
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.
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
PIPE="/tmp/asterisk-pipe.$$"
mknod $PIPE p
mplayer -playlist http://host:port/playlist.m3u -really-quiet -quiet -ao pcm:file=$PIPE -af resample=8000,pan=1:0.5:0.5,channels=1,format=mulaw 2>/dev/null | cat $PIPE 2>/dev/null
rm $PIPE
Translating it in to a step-by-step list:
- 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.
- Delete the named pipe.
Originally this script had things to find existing pipes and delete them. Storing them in /tmp means they'll disappear on reboot. If things go right you won't need to constantly start and stop anything. You can however always look at running processes and see which pipes are being used.
The original version of my Classic Hold used this, in combination with icecast2 and ices2.
But surely we can do this without getting icecast ivolved....directly with applications.
Oh...sure...we 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.
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.