Chapter 3. Audio Manipulation

Introduction

The recipes in this chapter are designed to help you with the injection of audio into and the monitoring of channels in your Asterisk environment. Many of the recipes focus on a particular aspect but can be built up or modified using the skills learned in other recipes in this book.

Monitoring and Barging into Live Calls

Problem

As the manager of a call center, you need to be able to listen in on calls to help with training new employees.

Solution

The most simple solution is to simply connect to any active channel using the ChanSpy() application, which then provides you the ability to flip through active channels using DTMF. The b option means to only listen to actively bridged calls:

[CallCenterTraining]
exten => 500,1,Verbose(2,Listening to live agents)
   same => n,ChanSpy(,b)

If you only want to spy on certain channels, you can use the chanprefix option to control which types of channels you want to listen to. So, if we just want to listen to SIP channels involved in bridged calls, we would do this:

[CallCenterTraining]
exten => 500,1,Verbose(2,Listening to live agents)
   same => n,ChanSpy(SIP,b)

Discussion

The default DTMF keys for controlling ChanSpy() are as follows:

#

Cycles through the volume level

*

Stop listening to the current channel and find another one to listen to

There are a lot more options for ChanSpy() and ways to use it in your dialplan. With some creativity, ChanSpy() can be used in many situations it wasn’t necessarily designed for (see Triggering Audio Playback into a Call Using DTMF). Not only can ChanSpy() be used to listen to the conversation between channels, but you can also speak to a single channel where only one person can hear you, referred to as whispering.

Whispering to a channel is commonplace in situations where a manager of a call center is training an employee, and needs to listen to an agent during the call. To enable whispering to channels that are being spied on, use the w option:

[CallCenterTraining]
exten => 500,1,Verbose(2,Listening to live agents with whisper)
   same => n,ChanSpy(,bw)

Of course, we’re going to be looking for some finer grain control for who we’re listening to. Perhaps we have several groups of people (multiple campaigns, different products, etc.) we want to separate and listen to. One of these groups could simply be the training group. New agents are all placed into the training group which makes it easy to scan and listen to calls while providing help where necessary. To do this, we need to associate channels with a spygroup using the SPYGROUP channel variable. By setting the channel variable for a particular channel, ChanSpy() can then be directed to listen only to channels in a particular spygroup.

As we show in Making Grandma Louder, we need to make sure the SPYGROUP channel variable is set on the channel we want to spy on and whisper to. In order to do this, we need to use the U() option of Dial(), which will execute a subroutine which sets the channel variable on the called channel verses the calling channel. While setting the SPYGROUP channel variable on the calling channel would still get us the ability to listen to calls like we want, the whispering would be performed to the wrong channel (i.e., the customer would be listening to the manager speak, not the agent):

[InboundCallsToAgents]
exten => 100,1,Goto(start,1)

exten => start,1,Verbose(2,Placing a call to an agent)
same => n,Dial(SIP/0000FFFF0001,15,U(subSetSpyGroup^training))
same => n,Hangup()

[subSetSpyGroup]
exten => s,1,Verbose(2,Setting spygroup)
same => n,Set(SPYGROUP=${ARG1})
same => n,Return()

We can make sure the channel variable SPYGROUP was set on the correct channel by using the core show channel CLI command at the Asterisk console. In our example, the device 0000FFFF0004 placed a call to 0000FFFF0001 using the dialplan we just wrote. By checking the channel variables set on the call, we can verify our dialplan is working correctly:

*CLI> core show channels
Channel              Location             State   Application(Data)
SIP/0000FFFF0004-000 start@InboundCalls:2 Up      Dial(SIP/0000FFFF0001,15,U(sub
SIP/0000FFFF0001-000 s@app_dial_gosub_vir Up      AppDial((Outgoing Line))
2 active channels
1 active call
2 calls processed

Having verified the direction of the call (0000FFFF0004 used the Dial() application which is connected to 0000FFFF0001), lets look at the channel variables set for the call:

*CLI> core show channel SIP/0000FFFF0004-TAB
...[snip]...
[email protected]:5060
BRIDGEPEER=SIP/0000FFFF0001-00000001
DIALEDPEERNUMBER=0000FFFF0001
DIALEDPEERNAME=SIP/0000FFFF0001-00000001
DIALSTATUS=ANSWER
...[snip]...

We’ve snipped out a lot of text, but what we’re looking for is to make sure the calling channel doesn’t have the SPYGROUP set for it as we’re expecting it to be enabled on the channel being called (the agent). Using the same technique we can verify that:

*CLI> core show channel SIP/0000FFFF0001-TAB
...[snip]...
BRIDGEPVTCALLID=ODIxNTAyNGUyNWViZmM5NGIyOGY1ZTVjYTQ1N2ExNTI.
BRIDGEPEER=SIP/0000FFFF0004-00000000
GOSUB_RETVAL=
SPYGROUP=training
...[snip]...

Having verified our channel variable has been set on the correct channel, lets create the dialplan that will allow our manager to listen and whisper to the channels in the training group:

[CallCenterTraining]
exten => 500,1,Verbose(2,Listening to live agents)
   same => n,ChanSpy(SIP,bwg(training))

With minor modifications to our existing ChanSpy() code, we can now listen to only channels in the training group (option g(training)) and whisper to them (option w). This same technique could be applied to only listening to specified extensions or callers by placing only a single channel into the spygroup.

Some additional options which you may find useful include (to see all available options, use core show application ChanSpy from the Asterisk CLI):

d

Override the standard DTMF actions and instead use DTMF to switch between the following modes:

4

Spy mode

5

Whisper mode

6

Barge mode

E

Exit when the spied on channel is disconnected.

q

Quiet. Don’t play a beep or speak the channel being spied on prior to listening.

S

Stop when there are no more channels to spy on.

Growing Your Company With PITCH_SHIFT()

Problem

You want to appear as a larger company to the outside world by manipulating the pitch of audio when dialing certain extensions.

Solution

exten => 100,1,Verbose(2,${CALLERID(all)} is calling reception)
   same => n,Set(CALLERID(name)=RCP:${CALLERID(name)})
   same => n,Set(PITCH_SHIFT(tx)=high)
   same => n,Dial(SIP/0000FFFF0001,30)
   same => n,Voicemail(reception@company,u)
   same => n,Hangup()

exten => 200,1,Verbose(2,${CALLERID(all)} is calling sales)
   same => n,Set(CALLERID(name)=SLS:${CALLERID(name)})
   same => n,Set(PITCH_SHIFT(tx)=low)
   same => n,Dial(SIP/0000FFFF0001,30)
   same => n,Voicemail(sales@company,u)
   same => n,Hangup()

Discussion

Using PITCH_SHIFT() you can modify the transmitted and/or received audio pitch either up or down. By changing the pitch of audio, you can sound like different people of the company. In our example we’ve used the transmission modifier (tx) of PITCH_SHIFT() to raise the pitch of the transmitted voice when extension 100 is dialed. Similarly we’ve modified the transmitted pitch to be lower when dialing extension 200. In order to know which department is being called, we’ve modified the callerID name by prepending either RCP for reception or SLS for sales.

For both extensions we’ve dialed the same device within the company. However, depending on which extension was dialed, we’ve configured separate voicemail boxes. When setting up the voicemail greetings, you’ll need to modify the transmitted audio using PITCH_SHIFT() prior to calling VoicemailMain() so that your voice sounds the same across answered calls and the voicemail greeting.

PITCH_SHIFT() contains several shorthands for modifying pitch:

highest

Pitch is raised one full octave.

higher

Pitch is raised higher.

high

Pitch is raised.

low

Pitch is lowered.

lower

Pitch is lowered more.

lowest

Pitch is lowered one full octave.

In addition to the shorthands, you can pass the floating point number between 0.1 and 4.0. A value of 1.0 has no effect on the pitch. A number lower than 1.0 lowers the pitch and number greater than 1.0 will raise the pitch. This can provide some fine grained tuning of the pitch so that you don’t sound like a robot or Mickey Mouse (which simply has the effect of being funny, and also makes you difficult to understand).

Testing can be done either by dialing another extension at your desk, or making use of the Record() application and then listening to the audio recorded. With some fine tuning you can grow your company without hiring additional employees!

Injecting Audio into a Conference Bridge

Problem

You need to play an audio file into a conference room.

Solution

[ConferenceAudio]
; Users would join the conference at extension 100
exten => 100,1,Goto(start,1)

; Trigger audio playback with extension 200
exten => 200,1,Originate(Local/inject@ConferenceAudio/n,exten,
ConferenceAudio,quiet_join,1)

; Users join the conference here
exten => start,1,NoOp()
   same => n,Answer()
   same => n,MeetMe(31337,d)
   same => n,Hangup()

; Use a couple flags to quietly enter the conference
exten => quiet_join,1,NoOp()
   same => n,Answer()
   same => n,MeetMe(31337,dtq)

; This triggers the file to be played into the conference
exten => inject,1,NoOp()
   same => n,Playback(silence/1&tt-weasels)
   same => n,Hangup()

Discussion

Playing audio into a conference is really quite a straightforward process. While there are several ways of triggering the audio playback through a call origination process (as shown in the See Also section) we’ve utilized the dialplan origination method for our example. Using the Originate() application, we’ve used a Local channel that plays back an audio file and connected it to the conference bridge using MeetMe() by connecting via a dialplan extension[4]. When we dial extension 200, the Originate() application creates a Local channel that executes the inject extension in the ConferenceAudio context. Once the Local channel is created, it is connected to the quiet_join extension located in the ConferenceAudio context which then connects to the MeetMe() application.

Once all these pieces are connected together, the audio is played into the conference bridge. We’ve set 3 flags on MeetMe() within the quiet_join extension. They are as follows:

d

Dynamically create the conference (don’t use meetme.conf).

t

Talk-only—don’t listen to audio. Not necessary, but could save on resources.

q

Quiet join—don’t play join sounds when connecting to the conference (suppresses the beep audio file).

Our example is a barebones implementation, but with further development, this could be expanded to limit conference lengths with audio being injected periodically to let the participants know how long is left. Or for weekly meetings, agenda items could be injected into the conference to make sure things progress during the allocated meeting time much like is done on shows like Pardon The Interruption.

Triggering Audio Playback into a Call Using DTMF

Problem

During a call you want to play back audio to a caller when they send a particular sequence of DTMF tones.

Solution

First we start with the creation of a new applicationmap:

; features.conf
[applicationmap]
play_message => #1,self/caller,Macro(PlayMessage)

Then we create our Macro() for triggering the message injection in extensions.conf:

[macro-PlayMessage]
exten => s,1,NoOp()
   same => n,Set(EncodedChannelToPass=${URIENCODE(${DYNAMIC_PEERNAME})})
   same => n,Originate(Local/spy-${EncodedChannelToPass}@whisper-channel/n,
exten,whisper-channel,audio,1)

Our Macro() calls via a Local channel another context which does the audio injection:

[whisper-channel]
exten => _spy-.,1,NoOp()
   same => n,Set(EncodedChannel=${CUT(EXTEN,-,2-3)})
   same => n,Set(GROUP(whisper-channel)=${EncodedChannel})
   same => n,ExecIf($[${GROUP_COUNT(${EncodedChannel}@
whisper-channel)} > 1]?Hangup())
   same => n,Set(ChannelToSpy=${URIDECODE(${EncodedChannel})})
   same => n,ChanSpy(${ChannelToSpy},wsqEB)
   same => n,Hangup()

exten => audio,1,NoOp()
   same => n,Answer()
   same => n,Wait(0.4)
   same => n,Set(VOLUME(TX)=-4)

; One example is to return current cost of the call. A lookup to a database or
; webservice would be required to make the data dynamic.
;
   same => n,SayNumber(7)
; letters/dollar vs. digits/dollars (plural)
   same => n,Playback(digits/dollars)
   same => n,SayNumber(48)
   same => n,Playback(cents)
   same => n,Hangup()

And then to enable it we need to add the following line into the dialplan that is executed by the channel that will be triggering the audio playback:

exten => _2XX,1,NoOp()
   same => n,Set(DYNAMIC_FEATURES=play_message)
   same => n,Dial(SIP/${EXTEN},30)
...

Discussion

In this solution we’ve developed a system using the features.conf file to trigger playback of audio via DTMF[5] to a channel without disconnecting the existing call. We’ve configured the feature called play_message (which could be any name not already in use), and assigned the #1 DTMF key combination to be the trigger. The option defined as self/caller means that the channel placing the call will be the one able to trigger the playback, and will also be the channel that hears the audio that is injected into the call. This is all done via the PlayMessage macro we’ll be defining in the dialplan.

In the macro-PlayMessage context, we’ve assigned the name of the channel we’ll be injecting audio to the EncodedChannelToPass channel variable. The channel name is obtained from the DYNAMIC_PEERNAME channel variable which was set when the play_message feature was trigged via the #1 DTMF sequence. We’ve used the URIENCODE() dialplan function to make it easier to pass the value of the channel using the Local channel inside the Originate() application. URIENCODE() is required as there will be front-slash (/) in the channel name, which would break the format expected by the Originate() command.

The Originate() application is used to trigger dialplan for playing back the audio prompt. The values for where to play the audio are passed via the spy-<channel name> extension in the whisper-channel context. The value passed to us through the extension was URIENCODE()’d, so we need to decode it. The value passed to URIENCODE() comes from the CUT() function, which takes the extension and separates on the hyphen, only passing the second and third fields, and assigning them to the ChannelToSpy variable. We are essentially cutting off the “spy-” part of the extension, as it contains no useful information. The rest of the extension is the channel name we need to spy on, as passed to us from the Originate() command in the PlayMessage macro.

Using the ChannelToSpy channel variable, we then inject audio into the channel using the ChanSpy() application to whisper to the channel. Here we look back at the Originate() line which had a Local channel passed to it as one of the values, with the remaining values being exten, whisper-channel, audio, and 1. The remaining values are telling the Originate() command to connect the Local channel with the extension audio within the whisper-channel context, starting at the first priority. The audio extension is what will contain the instructions for what audio to be played into the channel.

The flags passed to the ChanSpy() application are as follows:

w

Whisper to the channel (allow us to listen and speak)

s

Silent; don’t play the beep

q

Quiet; don’t play the channel type

E

Exit after the bridge is closed

B

Barge in

Once the Originate() application has connected the ChanSpy() application to the requested channel, then we need to play back some audio. The audio to be played into the channel is handled via the audio extension within the whisper-channel context. Because we want to play the audio over the top to give the caller some information, but don’t want to interrupt the call, we’ve lowered the volume of the audio using the VOLUME() function prior to playing any audio.

We’ve defined some static data as an example of what audio playback might be like. Of course, you’d need to add a call above that to make the data dynamic, which could be done via func_odbc (if the data was in a relational database), via CURL() (if returned from a webservice), or any other number of ways in which the data could be looked up and returned.

Recording Calls from the Dialplan

Problem

You would like to enable call recording from the Asterisk dialplan.

Solution

Use the MixMonitor() application:

exten => 7001,1,MixMonitor(${UNIQUEID}.ulaw)
    same => n,Dial(SIP/myphone)

Discussion

MixMonitor() records the audio from both directions of the phone call and writes it to a file on disk in one of the audio formats that Asterisk supports. You can see a list of the file formats that your version of Asterisk supports at the Asterisk CLI. The Extensions column identifies which file format extensions can be used in the recording filename:

*CLI> core show file formats

Format     Name       Extensions
------     ----       ----------
gsm        wav49      WAV|wav49
slin16     wav16      wav16
slin       wav        wav
adpcm      vox        vox
slin16     sln16      sln16
slin       sln        sln|raw
siren7     siren7     siren7
siren14    siren14    siren14
g722       g722       g722
ulaw       au         au
alaw       alaw       alaw|al|alw
ulaw       pcm        pcm|ulaw|ul|mu|ulw
ilbc       iLBC       ilbc
h264       h264       h264
h263       h263       h263
gsm        gsm        gsm
g729       g729       g729
g726       g726-16    g726-16
g726       g726-24    g726-24
g726       g726-32    g726-32
g726       g726-40    g726-40
g723       g723sf     g723|g723sf
g719       g719       g719
23 file formats registered.

The syntax for MixMonitor() in the dialplan is as follows:

MixMonitor(filename.extension[,options[,command]])

The options string can contain any of the following options:

a

If the specified filename already exists, append to it instead of overwriting it.

b

Delay the start of the recording until the call has been bridged. Otherwise the recording will start during call setup. If you only want to record the parts of the call once both sides have been answered and are talking, use this option.

v(x)

Adjust the volume of the audio heard by the channel that is executing MixMonitor(). The range is -4 to 4.

V(x)

Adjust the volume of the audio spoken by the channel that is executing MixMonitor(). The range is -4 to 4.

W(x)

Adjust the audio coming from both directions. The range is -4 to 4.

The final parameter for MixMonitor() is a command. This is a custom command that will be executed when the recording is complete. This can be useful for doing post-processing of recordings.

See Also

See Triggering Call Recording Using DTMF for another recipe that is related to call recording. To see the built-in documentation that Asterisk has for MixMonitor(), execute this command at the Asterisk CLI:

*CLI> core show application MixMonitor

Triggering Call Recording Using DTMF

Problem

You would like to give some users the ability to enable call recording by pressing a key sequence during a call.

Solution

Use the automixmon feature. This feature is built into Asterisk. You just have to enable it in a couple of configuration files. First, set the automixmon option in features.conf to the key sequence you would like to use for enabling or disabling recording:

[featuremap]
automixmon = *3

Now that the key sequence has been set, you must modify extensions.conf to allow callers to use this feature. This is done by setting the x and/or X options in the Dial() or Queue() application:

x

Give the called party the ability to toggle call recording using the automixmon feature.

X

Give the calling party the ability to toggle call recording using the automixmon feature:

;
; When someone calls my extension, dial my phone and give
; me the ability to enable recording on the fly.
;
exten => 7001,1,Dial(SIP/myphone,30,x)

Discussion

The featuremap section of features.conf has some other features that are configured and enabled in this same manner. You configure the key sequence for the feature in features.conf and then enable the feature on a per-call basis using arguments to the Dial() or Queue() applications. Here is a list of all of the features in the featuremap section:

blindxfer

DTMF triggered blind transfers. Enable it using the t and/or T options to Dial() or Queue().

atxfer

DTMF triggered attended transfers. Enable it using the t and/or T options to Dial() or Queue().

disconnect

DTMF triggered call hangup. Enable it using the h and/or H options to Dial() or Queue().

parkcall

DTMF triggered call parking. Enable it using the k and/or K options to Dial() or Queue().

automixmon

DTMF triggered call recording using the MixMonitor() application. Enable it using the x and/or X options to Dial() or Queue().

automon

DTMF triggered call recording using the Monitor() application. We recommend using the automixmon feature instead of this one unless you have a specific need for using Monitor() instead of MixMonitor(). Enable this feature by specifying the w and/or W options to Dial() or Queue().

See Also

For another recipe that uses MixMonitor(), see Recording Calls from the Dialplan.

Making Grandma Louder

Problem

You have a particular caller who is soft spoken, and need to increase the volume of her speech.

Solution

[VolumeAdjustment]
exten => 100,1,Verbose(2,Incoming call from ${CALLERID(all)})
   same => n,GoSubIf($[${CALLERID(num)} = 12565551111]?VolumeAdj,1)
   same => n,Dial(SIP/0000FFFF0001,30)
   same => n,Hangup()

exten => VolumeAdj,1,Verbose(2,Adjusting volume for grandma)
   same => n,Set(VOLUME(TX)=3)
   same => n,Return()

Discussion

In our solution, we’ve used the CALLERID() function to match on a particular callerID number and, if matching, to execute the VolumeAdj extension which increases the receive volume. We’re making use of a GoSubIf() to execute the VolumeAdj extension and to return when the volume adjustment is completed, and then dialing the device 0000FFFF0001. We could then create a list of callerID numbers to adjust the volume for if we wished. Because we’re expecting the other side to be quieter than we would like, we are increasing the volume of the transmitted audio by specifying VOLUME(TX). If we wanted to increase the volume for the audio received by that channel, we would use VOLUME(RX).

Note

The VOLUME() function must be viewed from the aspect of the channel that is executing the function. Because there are two channels in every call, if the channel initiating the Dial() application executed the VOLUME() function, then the RX option adjusts their receive volume, and TX adjusts their transmit volume.

In addition to changing the volume prior to calling an application, with the p option we can permit the VOLUME() function to listen for DTMF to adjust the volume of the channel. To increase volume, use the * key. To lower the volume, use the # key. With our current dialplan, the DTMF can be adjusted by the caller, which may not necessarily be what we want. If we want the VOLUME() to be adjustable on the called channel, we need to execute a subroutine on the other channel just prior to bridging. We can do this with the U() option to the Dial() application:

exten => 100,1,Verbose(2,Incoming call from ${CALLERID(all)})
same => n,Dial(SIP/0000FFFF0001,30,U(VolumeAdjustment^3))
same => n,Hangup()

exten => s,1,Verbose(2,Adjusting volume for other channel)
same => n,Set(VOLUME(RX,p)=${ARG1})
same => n,Return()

Tip

We could use the IF() function to control whether the subroutine is executed by the Dial() application:

 same => n,Dial(SIP/0000FFFF0001,30,${IF($[${CALLERID(num) = 12565551212]?
U(VolumeAdjustment^3))})

The nice thing about adjusting the volume with the VOLUME() function is that you can apply this not just to dialing end points, but also to adjust volume prior to sending calls to other applications which may be recording audio, such as Voicemail() or Record(). In this manner, you can adjust the volume for people individually but continue utilizing the same dialplan logic.

If you had a large list of people with different volumes you needed to adjust, then making use of the AstDB or func_odbc methods of looking up information in a database would be the better way to go. By making the data dynamic, you can handle tens or hundreds of different volume configurations without much more dialplan. If using func_obdc and a custom function, the dialplan may look something like the following:

[VolumeAdjustment]
exten => 100,1,Verbose(2,Incoming call from ${CALLERID(all)})
   same => n,Set(ARRAY(VolLevel,VolDirection)=
${ODBC_GET_VOLUME_LEVEL(${CALLERID(num)})})
   same => n,GoSubIf($[${EXISTS(${VolumeLevel})}]?VolumeAdj,1)
   same => n,Dial(SIP/0000FFFF0001,30)
   same => n,Hangup()

exten => VolumeAdj,1,Verbose(2,Adjusting volume for grandma)
   same => n,Set(VOLUME(${VolDirection})=${VolLevel})
   same => n,Return()

Once you start abstracting the data with things like func_odbc then you can even control volume based on who is being called (perhaps someone has impaired hearing and requires the volume to be adjusted) and all sorts of other situations.



[4] We could have just as easily used ConfBridge() as well. It just depends on your requirements.

[5] Dual-Tone Multi-Frequency, aka Touch-Tone.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset