Akom's Tech Ruminations

Various tech outbursts - code and solutions to practical problems
Asterisk

Teaching your Asterisk phone system to control your music

Posted by Admin • Saturday, January 17. 2009 • Category: Asterisk

Ever since I built my Asterisk-based VOIP phone system I've been finding more and more interesting ways to put it to work. I mean, it's a business phone server, but it's got plenty of resources as it sits around waiting for a phone call. So I figured... why not be able to pick up the nearest phone, dial an extension, and be able to stop/start/skip my whole-house music? How about doing this from anywhere? OK this may seem odd to you, but you'd be surprised how often it saves you from having to get up and go find the remote or having to wake up a computer :-). OK you're not convinced that this is very useful. How about pausing music for a phone call? Automatically?



For those who don't know, Asterisk is a open source (and free) software PBX system. You know, the thing that IP phones connect to (you know, that little box you got from Vonage - it connects to something - well in my house, it connects to my server). It provides call routing and management, voicemail, IVR (menus), etc. In short, it's awesome. I mean, yeah, it's a little unintuitive at first, but it's not that complicated once you loosen up your old fixed programmatic thinking a little :-)



So, here is the setup.


Asterisk controlling MPD

I have:

  • Asterisk Server (just an old PC with a NIC)
  • MPD server (happens to be the same PC, but doesn't have to be) with a sound card plugged into receivers in various parts of the house
  • Some IP phones all around the house. Some are analog phones on ATA's like with Vonage, some are real IP phones. Now you don't need these if you just want to call into your system from your cell phone... but you may want to password-protect the extension :-)


My Key Layout

MPD is what I've consistently found to be the best software for playing music if you actually want to be able to control it from more than one place - it's a server process, so it is cleanly separated from the control UI by XML-RPC, and a variety of clients can control it. It does not need X running and starts with /etc/init.d. I'm not sure if it runs on Windows, but I'm sure it's available on every flavor of Linux. Besides, do you really want to entrust something as important as music to your windows machine? In my case, I'll am using mpc (command-line client) to control it. mpc does not need to be on the same machine (XML-RPC, remember?). I manage playlists and stuff using phpmp, but for this exercise I won't be doing that.



So if you have the music working, you should be able to run mpc start and mpc stop from just about any machine on your network. Let's move on to integrating it into Asterisk. Note that if you've got Asterisk@Home (or what do they call it now, TrixBox?) then you'll have to translate my rules into mouse clicks. I prefer to edit files myself :-)




So here is the code from extensions.conf to make it all work. This of course assumes that mpc is installed on this machine, and the asterisk user can invoke it. Adjust the value of MPD_HOST below to the hostname of the machine running MPD. This recipe also makes it very easy to control several MPD boxes - just add 701 and point it at server2.

; This belongs in your internal extensions:
exten => 700,1,Set(MPD_HOST=mymusicserverhostname)
exten => 700,n,Goto(music,s,1)

[macro-mpccontrol]
; ARG1 is MPD Hostname
; ARG2 is MPC Command
; ARG3 is MPC Command Description (for festival)
exten => s,1,NoOp(Running mpc ${ARG3})
exten => s,n,Background(custom/music-${ARG3})
exten => s,n,TrySystem(MPD_HOST=${ARG1} mpc ${ARG2})
; If you are having trouble, uncomment this and look in your /var/log/ to see if it's trying to work at all
;exten => s,n,TrySystem(logger MPD_HOST=${ARG1} mpc ${ARG2})
exten => s,n,Background(custom/music-done)


[music]
; Requires ${MPD_HOST} to be set
exten => s,1,Background(custom/music-greeting-${MPD_HOST})
exten => s,n,Set(TIMEOUT(response)=30)  ; Set Response Timeout 
exten => s,n(musicrestart),Background(custom/music-input)
exten => s,n,WaitExten
exten => 1,1,Macro(mpccontrol,${MPD_HOST},prev,prev)
exten => 1,n,Goto(s,musicrestart)
exten => 2,1,Macro(mpccontrol,${MPD_HOST},play,play)
exten => 2,n,Goto(s,musicrestart)
exten => 3,1,Macro(mpccontrol,${MPD_HOST},next,next)
exten => 3,n,Goto(s,musicrestart)
exten => 4,1,Macro(mpccontrol,${MPD_HOST},volume -10,down)
exten => 4,n,Goto(s,musicrestart)
exten => 5,1,Macro(mpccontrol,${MPD_HOST},stop,stop)
exten => 5,n,Goto(s,musicrestart)
exten => 6,1,Macro(mpccontrol,${MPD_HOST},volume +10,up)
exten => 6,n,Goto(s,musicrestart)
exten => 0,1,Background(custom/music-control-off)
exten => 0,2,Goto(internalextensions,s,1) ; //change this later to vars for where to go
exten => ,1,Background(custom/music-instructions)
exten => ,n,Goto(s,musicrestart)
exten => i,1,Background(custom/music-unknown-command)
exten => i,n,Goto(s,musicrestart)
exten => t,1,Background(custom/music-control-timeout)



As you can see, I recorded some custom sounds to give me prompts, and you can do that too pretty easily as well, though they are of course optional. The last parameters to mpcontrol macro is the suffix of the sound file it should play to confirm what it's done. Alternatively, you can change the Background to Festival(${ARG3}) in the macro if you don't want to record these and you have Festival.

Here are the filenames of the custom sounds:

music-control-off.gsm
music-control-timeout.gsm
music-done.gsm
music-down.gsm
music-greeting-musicbox.gsm
music-greeting-gremlin.gsm
music-greeting-potato.gsm
music-input.gsm
music-instructions.gsm
music-next.gsm
music-play.gsm
music-prev.gsm
music-stop.gsm
music-unknown-command.gsm
music-up.gsm



It's pretty simple otherwise.



AEL2 Syntax

Since I upgraded to Asterisk 1.6 I've gradually reworked most of my dialplan using the AEL language. The code is more concise and certainly more readable. Here is the same thing in AEL:



                                                                              
context mpd {                                                                               
    s => {                                                                                  
        Background(custom/music-greeting-${MPD_HOST});                                      
        Set(TIMEOUT(response)=30);                                                          
        musicrestart:                                                                       
        Background(custom/music-input);                                                     
        WaitExten();                                                                        
        goto musicrestart;                                                                  
    };                                                                                      
                                                                                            
    1 => &mpdcontrol(${MPD_HOST},prev,prev);                                                
    2 => &mpdcontrol(${MPD_HOST},play,play);                                                
    3 => &mpdcontrol(${MPD_HOST},next,next);                                                
    4 => &mpdcontrol(${MPD_HOST},volume -10,down);                                          
    5 => &mpdcontrol(${MPD_HOST},stop,stop);                                                
    6 => &mpdcontrol(${MPD_HOST},volume +10,up);                                            
                                                                                            
    //disconnect                                                                            
    0 => Background(custom/music-control-off);                                              
                                                                                            
};                                                                                          
                                                                                            
macro mpdcontrol(host, command, label) {                                                    
    NoOp(Running mpc ${label});                                                             
    Background(custom/music-${label});                                                      
    TrySystem(MPD_HOST=${host} mpc ${command});                                             
    Background(custom/music-done);                                                          
    goto mpd,s,musicrestart;                                                                
                                                                                            
    return;                                                                                 
};                                                         



Entering the context: (Passing MPD_HOST allows me to control MPD on multiple machines)

    701 => {
        Set(MPD_HOST=musicbox); //the hostname of the machine
        goto mpd,s,1;
    };

Pausing Music for a phone call

I don't want music paused during the call as much as I want it paused when the call comes in so that I can hear it. No, not hear it ringing... Hear the caller id. Huh???



OK yeah so I have Festival (text-to-speech) announce "Incoming call from So-And-So" when a call comes in, which gives the inhabitants some extra time to go find the phone, while the caller navigates the IVR menus and enters an extension.



But if music is playing, I won't hear this at all. So I wrote a little shell script which I invoke like this:

exten => s,n,TrySystem(/usr/local/bin/speak-local-ensure-audible.sh ${ARG1} &)

({$ARG1} has the string I want to announce at this time}



Actually, this script does the speaking and the pausing all-in-one for simplicity:

#!/bin/sh


if mpc status | grep "\[playing\]" ; then
  playing=true
  mpc pause
fi

curl http://SOME-BOX/localtools/speech.php -s --retry 0 --connect-timeout 1 --max-time 1 --form-string "string=$*" 
sleep 15

if [ "$playing" == "true" ] ; then
  mpc play
fi



Long story on why I use HTTP to access the speech engine, but the important thing is what this does:

  1. Pauses if playing
  2. Speaks what needs to be spoken
  3. Gives it 15 seconds
  4. Resumes playback if it was playing



Clearly not a foolproof concept, but it's worked fine for a year so far.

1 Trackbacks

  1. As I was going into this project knowing exactly nothing about commercial audio, it seemed reasonable to survey how things are usually set up. Most commercial spaces have mediocre sound - at least so it would seem, as their system may simply be improper

0 Comments

Display comments as (Linear | Threaded)
  1. No comments

Add Comment


You can use [geshi lang=lang_name [,ln={y|n}]][/geshi] tags to embed source code snippets.
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
Markdown format allowed


Submitted comments will be subject to moderation before being displayed.