Writing Native Extensions for Asterisk 1.2
May 25, 2006
Table of ContentsIntroductionWhen programming Asterisk dial plans, custom functions and applications are a useful tool for improving speed, reducing complexity, and accomplishing tasks that are not possible with dial plans alone. Chances are that at some point you've run in to situations in which you needed to access external data to improve dial plan logic. Perhaps you'd like to fetch billing information from an ODBC database. Maybe you want to feed voice data through an external program. In such scenarios, most Asterisk users turn to AGI, which in many cases is a reasonable solution; however, launching an external process to parse a script and communicating through pipes can be costly. Regardless of your motives, native Asterisk modules are a excellent and fun way to extend the functionality of your PBX. In this document, you will gain an understanding of how to write function and application modules for Asterisk. You will also gain an understanding of several commonly used facilities provided by the Asterisk core such as reading configuration files and manipulating audio data. This document assumes that you are reasonably familiar with using Asterisk and programming C in a Linux environment. Grab the SourceIn order to make modifications to Asterisk, you're going to need the source code. Download the tarball sources for the latest 1.2 release (at the moment, 1.2.4) from the Asterisk website. Extract the sources to the /usr/src directory on your GNU/Linux system. If you installed Asterisk using a package management system such as RPM or Portage, please uninstall before continuing. (Note: If you are using Zaptel and LibPRI drivers, please install those as well BEFORE installing Asterisk from source.) I highly recommend creating a symbolic link named 'asterisk' in your /usr/src directory to the asterisk sources you're currently using. This makes navigation a bit easier and reduce complication with new releases. Excerpt: Shell Interactive [root@lobster src]# pwd
/usr/src
[root@lobster src]# ln -s asterisk-1.2.4 asterisk
[root@lobster src]# ls -l asterisk
lrwxrwxrwx 1 root root 14 Mar 3 12:39 asterisk -> asterisk-1.2.4
To compile and install Asterisk, type the following commands as root. Please note that it is usually a good idea to make sure the /usr/lib/asterisk/modules directory is clear before installing Asterisk to avoid problems starting. Excerpt: Shell Interactive [root@lobster asterisk]# pwd
/usr/src/asterisk
[root@lobster asterisk]# yes | rm -rf /usr/lib/asterisk/modules/*
[root@lobster asterisk]# make && make install
If you look through the Asterisk Makefile, you will notice that there are other cool make targets for automating some useful tasks.
The following directories in the Asterisk source tree are important and worth mentioning.
Functions and ApplicationsFunctions are one of the best new features in Asterisk 1.2. They are generally used to manipulate data within, and outside of Asterisk. A function's greatest strength are its read and write capabilities. Applications are generally used for performing tasks, such as dialing a number or jumping to another location in your dial plan. At the current stages of development, each line of dial plan code must call an application. Function calls and variables may be embedded within an application call. Variables and function calls are magically parsed by Asterisk and the resulting string is passed to the application. Here is an example of a function being used within application calls in an Asterisk dial plan: Excerpt: extensions.conf exten => 888,1,Answer
exten => 888,n,NoOp(The length of the string 'hello kitty' is ${LEN(hello kitty)})
exten => 888,n,Hangup
CLI Output -- Executing Answer("SIP/105-15aa", "") in new stack
-- Executing NoOp("SIP/105-15aa", "The length of the string hello kitty is 11") in new stack
-- Executing Hangup("SIP/105-15aa", "") in new stack
Certain functions such as DB() and CALLERID() allow write access. In the following example, we will write a value to Asterisk's built in Berkeley DB. Excerpt: extensions.conf exten => 888,1,Answer
exten => 888,n,Set(DB(dead/stars)=Keep it open source pigs)
exten => 888,n,NoOp(${DB(dead/stars)})
exten => 888,n,Hangup
Excerpt: CLI Output -- Executing Answer("SIP/105-ddb1", "") in new stack
-- Executing Set("SIP/105-ddb1", "DB(dead/stars)=Keep it open source pigs") in new stack
-- Executing NoOp("SIP/105-ddb1", "Keep it open source pigs") in new stack
-- Executing Hangup("SIP/105-ddb1", "") in new stack
As you may have noticed, arguments passed to the function DB() are delimited by the slash character. You may be wondering why function arguments don't use a pipe like most applications do. The reason the pipe character isn't used is because a pipe might confuse the Asterisk parser. This issue will be discussed in more detail later. Your First Native FunctionGo in to the funcs/ folder inside your Asterisk sources directory. This is where all the C code for dial plan functions is stored. There are currently two ways to implement a function. You can write an independent module, or you can piggy-back on to the pbx_functions module. Because we'll be writing such a simple function, we will be choosing the latter because a separate module for a one line of code function would be overkill. File: asterisk-src/funcs/func_hello.c #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "asterisk.h"
#include "asterisk/channel.h"
#include "asterisk/logger.h"
static char *builtin_function_hello(struct ast_channel *chan, char *cmd,
char *data, char *buf, size_t len)
{
ast_log(LOG_NOTICE, "Running %s()\n", cmd);
snprintf(buf, len, "hello kitty!");
return buf;
}
struct ast_custom_function hello_function = {
.name = "HELLO",
.synopsis = "Says 'hello kitty'",
.syntax = "HELLO()",
.desc = "This function is the best ever.\n",
.read = builtin_function_hello,
};
You may be wondering how the above example could possibly have enough code to get it working and registered with Asterisk. Well, the folks at Digium made your life easier by adding code to the funcs Makefile that uses awk to scan through the source for the built-in functions and pull out ast_custom_function definitions. A file named pbx_functions.h is then generated that tells the pbx_functions.c module what to load. Because of this, you can add as many functions to func_hello.c as you'd like; and each one will be magically loaded in to Asterisk. In the above code, builtin_function_hello() will be called by Asterisk whenever the dial plan attempts to execute HELLO(). Here is a break-down of the arguments passed to our callback:
Now in order for this function to be compiled properly, you need to add it to the func Makefile. If you fail to add your file to BUILTINS, the Makefile will think you want to compile func_hello.c as its own module. Because we aren't defining the proper export functions to make a standalone module work, you will get errors when loading Asterisk and you will need to clear /usr/lib/asterisk/modules and recompile. Excerpt: asterisk-src/funcs/Makefile BUILTINS=func_md5.o \
func_math.o \
func_groupcount.o \
func_strings.o \
func_cdr.o \
func_logic.o \
func_env.o \
func_db.o \
func_timeout.o \
func_language.o \
func_hello.o \ # here it is :)
func_moh.o
Your modifications should now be ready to compile. Make sure Asterisk isn't running, clear our the /usr/lib/asterisk/modules directory, and run the command 'make upgrade' in the Asterisk sources folder, (not the functions folder!) and start up Asterisk again. Now you can use a simple CLI command to see if your function was loaded. (Note: You don't need to restart Asterisk every time you want to add a module, we will cover more efficient ways later.) Excerpt: CLI Interactive *CLI> show function HELLO
*CLI>
-= Info about function 'HELLO' =-
[Syntax]
HELLO()
[Synopsis]
Says 'hello kitty'
[Description]
This function is the best ever.
*CLI>
Now let's try out our new function. Below is the code to add to your extensions.conf file in the context to which your phones are associated. (For example, [incoming]) Once you've added the following code, type 'extensions reload' on the Asterisk CLI and then dial '888' on your phone. Excerpt: extensions.conf exten => 888,1,Answer
exten => 888,n,NoOp(${HELLO()})
exten => 888,n,Hangup
Excerpt: CLI Output -- Executing Answer("SIP/105-28e1", "") in new stack
2006-02-23 16:36:41 NOTICE[27137]: func_hello.c:10 builtin_function_hello: Running HELLO()
-- Executing NoOp("SIP/105-28e1", "<b>hello kitty!</b>") in new stack
-- Executing Hangup("SIP/105-28e1", "") in new stack</pre>
Congratulation, you've created a working native Asterisk function! Before you move on to the next section, I recommend you indulge in a short celebration. Go to Taco Bell, dance around the room, or drink a cold pint of blood. You are now among the prestigious ranks of Asterisk Hackers. A Read/Write Capable FunctionThere are certain situations in which you'll want to be able to alter your data source the same manner in which functions such as DB() and CALLERID() allow. The following example will read a file from the file system, and allow you to alter the contents of the file by overwriting or appending. Because we'll be accepting a filename as the first argument and can't really use slash as a delimiter, we will use a circumflex (^) instead. Please also note that for the sake of simplicity, there are no locking functions in the below example. File: asterisk-src/funcs/func_file.c #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "asterisk.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/logger.h"
#include "asterisk/utils.h"
#include "asterisk/app.h"
#include "asterisk/options.h"
static char *function_file_read(struct ast_channel *chan, char *cmd, char *data,
char *buf, size_t len)
{
FILE *f;
char *args;
int argc;
char *argv[2];
char *file;
int flen = 0, rdamt = 0;
/* parse out arguments by ^ delimiter, ignoring second arg if present */
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "FILE (read) requires an argument, FILE(<file>)\n");
return buf;
}
args = ast_strdupa(data);
argc = ast_app_separate_args(args, '^', argv, sizeof(argv) / sizeof(argv[0]));
if (argc == 0)
return buf;
if (argc == 2)
ast_log(LOG_NOTICE, "Ignoring second argument to FILE() in read mode\n");
file = argv[0];
/* slurp up the file */
if (!(f = fopen(file, "r"))) {
ast_log(LOG_WARNING, "Failed to open file '%s' for reading\n", file);
return buf;
}
if (fseek(f, 0, SEEK_END)) {
ast_log(LOG_WARNING, "seek(%s) failed\n", file);
return buf;
}
if ((flen = ftell(f)) == -1) {
ast_log(LOG_WARNING, "ftell(%s) failed\n", file);
return buf;
}
if (flen + 1 > len) {
ast_log(LOG_NOTICE, "File '%s' is too long for buffer, truncating...", file);
rdamt = len - 1;
} else
rdamt = flen;
if (fseek(f, 0, SEEK_SET)) {
ast_log(LOG_WARNING, "seek(%s) failed\n", file);
return buf;
}
if (fread(buf, rdamt, 1, f) != 1) {
ast_log(LOG_WARNING, "fread(%s) failed\n", file);
return buf;
}
fclose(f);
return buf;
}
static void function_file_write(struct ast_channel *chan, char *cmd, char *data, const char *value)
{
FILE *f;
char *args;
int argc;
char *argv[2];
char *file, *mode, *opnmode;
if (!value)
return;
/* parse out arguments by ^ delimiter, ignoring second arg if present */
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "FILE (write) args missing, FILE(<file>[^<append|overwrite>])\n");
return;
}
args = ast_strdupa(data);
argc = ast_app_separate_args(args, '^', argv, sizeof(argv) / sizeof(argv[0]));
if (argc == 0)
return;
file = argv[0];
mode = argv[1];
if (mode != NULL && strcasecmp(mode, "overwrite") == 0) {
opnmode = "w";
} else if (mode != NULL && strcasecmp(mode, "append") == 0) {
opnmode = "a";
} else {
opnmode = "w";
ast_log(LOG_NOTICE, "Unknown write mode, defaulting to overwrite\n");
}
if (!(f = fopen(file, opnmode))) {
ast_log(LOG_WARNING, "fopen('%s', '%s') failed!\n", file, opnmode);
return;
}
if (fwrite(value, strlen(value), 1, f) != 1) {
ast_log(LOG_WARNING, "fwrite() failed");
return;
}
fflush(f);
fclose(f);
}
struct ast_custom_function file_function = {
.name = "FILE",
.synopsis = "Write/Read to a file",
.syntax = "FILE(<file>[^<append|overwrite>])",
.desc = "Allows you to read, overwrite, and append to a file.\n",
.read = function_file_read,
.write = function_file_write,
};
Excerpt: extensions.conf exten => 888,1,Answer
exten => 888,n,Set(FILE(/tmp/lols^overwrite)=Hey Ronald Regan! )
exten => 888,n,Set(FILE(/tmp/lols^append)=What are you doing in my house?)
exten => 888,n,NoOp(${FILE(/tmp/lols)})
exten => 888,n,Hangup
Excerpt: CLI Output -- Executing Answer("SIP/105-ed79", "") in new stack
-- Executing Set("SIP/105-ed79", "FILE(/tmp/lols^overwrite)=Hey Ronald Regan! ") in new stack
-- Executing Set("SIP/105-ed79", "FILE(/tmp/lols^append)=What are you doing in my house?") in new stack
-- Executing NoOp("SIP/105-ed79", "Hey Ronald Regan! What are you doing in my house?") in new stack
-- Executing Hangup("SIP/105-ed79", "") in new stack
In the above code, the function_file_write() function has been added to our ast_custom_function definition to let Asterisk know that FILE() accepts write access. The write callback takes the string value as input. Because value is a c-string, Asterisk expressions are not binary safe; therefore, FILE() should only be used with ASCII text files. Please also note that value is given to you after dial-plan variable and function substitution. When writing function callbacks, your first responsibility is to parse the arguments from data. In the past this was generally done using strsep() but ast_app_separate_args() was introduced to make argument parsing easier, and (to some extent) more consistent. As mentioned earlier, because strsep() and ast_app_separate_args() overwrite delimiting tokens with NULL characters, it is important to create a local copy of data using ast_strdupa(). Writing a ModuleSometimes, building your functions directly in to pbx_functions.so isn't the best solution. (When writing other types of extensions such as codecs, this isn't an options at all.) For example, you may need to read a configuration file in /etc/asterisk when your module gets loaded in to Asterisk. You may also want to cache data you expect to be accessed frequently. Modules let you do all this and a lot more. Please note that all modules, whether they are used for functions, apps, codecs, etc., all follow the same general format. The differences lie in the Makefiles and code you write. It is possible, however stupid, to write a module in the funcs/ directory that introduces a new dial plan application in to Asterisk. Modules are compiled in to shared object (.so) files. They are installed to /usr/lib/asterisk/modules and can be turned on and off from loading by editing /etc/asterisk/modules.conf. Modules must include asterisk/modules.h. Modules must also export several functions. The following functions generally return 0 on success and non-zero on failure. Do not define any of these functions as static.
In this example, the HELLO() dial plan function shown earlier will be converted in to its own module instead of piggy-backing on to the pbx_functions.so module. The first thing you should do is remove func_hello.o from the BUILTINS list in the funcs Makefile and add it to the FUNCS list. This will tell the Makefile that func_hello.c is its own module and should not be linked in to pbx_functions.so. File: asterisk-src/funcs/func_hello.c #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "asterisk.h"
/* all modules must include module.h */
#include "asterisk/module.h"
#include "asterisk/channel.h"
#include "asterisk/logger.h"
static char *tdesc = "Hello Kitty Example Function";
static char *builtin_function_hello(struct ast_channel *chan, char *cmd,
char *data, char *buf, size_t len)
{
ast_log(LOG_NOTICE, "Running %s()\n", cmd);
snprintf(buf, len, "hello kitty!");
return buf;
}
/* hello_function should now be static because we aren't
* exporting it to pbx_functions.o */
static struct ast_custom_function hello_function = {
.name = "HELLO",
.synopsis = "Says 'hello kitty'",
.syntax = "HELLO()",
.desc = "This function is the best ever.\n",
.read = builtin_function_hello,
};
int load_module(void)
{
/* tell Asterisk core about our pimp function */
ast_custom_function_register(&hello_function);
return 0;
}
int unload_module(void)
{
ast_custom_function_unregister(&hello_function);
return 0;
}
char *description(void)
{
return tdesc;
}
int usecount(void)
{
/* we're not going to worry about the user counting system for
* something this simple */
return 0;
}
char *key(void)
{
return ASTERISK_GPL_KEY;
}
Once you've done a 'make upgrade' and run Asterisk, you'll notice the following message: Excerpt: CLI Output [func_hello.so] => (Hello Kitty Example Function)
== Registered custom function HELLO
The HELLO() function should now function just as it did when it was built in to pbx_functions.so. A Bare-bones ApplicationBelow is a simple Asterisk application module named app_hello.c. Because applications tend to take control of a channel for a much longer period of time than functions, it is best to implement standard user tracking. This way, your application can hang up any channels using your module if it should become terminated. Applications should be named 'app_NAME.c' and be placed in the apps/ folder within the Asterisk source tree. We also need to add app_hello.so to the APPS list in apps/Makefile. File: apps/app_hello.c #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "asterisk.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/channel.h"
#include "asterisk/logger.h"
#include "asterisk/options.h" /* for verbose vars/consts */
static char *tdesc = "Hello Kitty Example App";
static char *app = "Hello";
static char *synopsis = "Writes Hello Kitty to screen";
static char *descrip =
" Hello(): Doesn't take any args, just writes hello\n"
" kitty to the screen. Simple stuff.\n";
/* fancy module.h macros for counting who's using this module */
STANDARD_LOCAL_USER;
LOCAL_USER_DECL;
static int hello_exec(struct ast_channel *chan, void *data)
{
struct localuser *u;
/* add this channel to the user linked list so if we need to
* suddenly unload, we know who to gracefully hangup. The
* contents of this macro reference the chan variable as well
* as variables defined by LOCAL_USER_DECL */
LOCAL_USER_ADD(u);
/* you need to have run asterisk with at least 2 v's to see
* this message. you can always adjust verbosity with the
* command 'set verbosity x' */
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Hello Kitty!\n");
/* we're no longer going to be the caretaker for this
* channel */
LOCAL_USER_REMOVE(u);
return 0;
}
int load_module(void)
{
int res;
res = ast_register_application(app, hello_exec, synopsis, descrip);
return res;
}
int unload_module(void)
{
int res;
res = ast_unregister_application(app);
/* if there are any channels still using this module, send
* them soft hangup signals. */
STANDARD_HANGUP_LOCALUSERS;
return res;
}
char *description(void)
{
return tdesc;
}
int usecount(void)
{
int res;
STANDARD_USECOUNT(res);
return res;
}
char *key(void)
{
return ASTERISK_GPL_KEY;
}
Excerpt: /etc/asterisk/extensions.conf exten => 888,1,Answer
exten => 888,n,Hello()
exten => 888,n,Hangup
Excerpt: CLI Output -- Executing Answer("SIP/105-5b14", "") in new stack
-- Executing Hello("SIP/105-5b14", "") in new stack
-- Hello Kitty!
-- Executing Hangup("SIP/105-5b14", "") in new stack
Excerpt: CLI Interactive *CLI> show application Hello
-= Info about application 'Hello' =-
[Synopsis]
Writes Hello Kitty to screen
[Description]
Hello(): Doesn't take any args, just writes hello
kitty to the screen. Simple stuff.</pre>
As you can see, the implementation of an application is very similar to the implementation of a function due to the fact that they both use the same module structure. Asterisk will not restrict your module from loading multiple applications, functions, or even your own custom CLI commands. Speeding Up DevelopmentUp until now, you've probably been shutting down Asterisk, running make upgrade, and restarting Asterisk every time you wanted to make a change to your code. This can be a VERY tedious process; and don't worry, there is a better way to test your changes. Because Asterisk modules are shared libraries, you don't need to shut down the entire Asterisk server to load in your modifications. This is a very important aspect of Asterisk's design because, chances are you don't ever want your phone system to go down for a second. The Asterisk CLI provides commands for dynamically loading modules: Excerpt: CLI/Shell Interactive *CLI> load app_hello.so
Loaded /usr/lib/asterisk/modules/app_hello.so => (Hello Kitty Example App)
== Registered application 'Hello'
*CLI> unload app_hello.so
== Unregistered application 'Hello'
[root@lobster asterisk]# asterisk -r -x "load app_hello.so"
Loaded /usr/lib/asterisk/modules/app_hello.so => (Hello Kitty Example App)
== Registered application 'Hello'
[root@lobster asterisk]# asterisk -r -x "unload app_hello.so"
== Unregistered application 'Hello'
So a likely alternative to the tedious aforementioned compile process, would be to unload your module, make upgrade, and then load your module again. The only is that Asterisk's make scripts are huge and take forever, even when they aren't compiling anything! Fortunately, there is a quicker way to compile JUST your module, unload, install, and load in one fell swoop. Anthony Minessale AKA anthm (one of the more prolific Asterisk contributors) wrote a Perl script named astxs which can be found in the contrib/scripts directory of the Asterisk sources. Copy this script to /usr/bin and give it execute permissions with chmod. astxs can only be run from the base of the Asterisk sources directory (Unless you want to specify a lot of variables manually). Being run from the Asterisk folder it can auto-detect most of your settings and compile your module correctly. Excerpt: Shell Interactive [root@lobster asterisk]# ps | grep asterisk
25729 pts/5 00:00:00 safe_asterisk
25750 pts/5 00:00:13 asterisk
[root@lobster asterisk]# pwd
/usr/src/asterisk
[root@lobster asterisk]# <b>astxs -install -autoload apps/app_hello.c</b>
gcc -I/usr/src/asterisk -I/usr/src/asterisk/include -pipe -Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -g3 -Iinclude -I../include -D_REENTRANT -D_GNU_SOURCE -O6 -march=i686 -DZAPTEL_OPTIMIZATIONS -fomit-frame-pointer -fPIC -DUSE_ODBC_STORAGE -DEXTENDED_ODBC_STORAGE -c apps/app_hello.c -o apps/app_hello.o
gcc -shared -Xlinker -x -o apps/app_hello.so apps/app_hello.o
/usr/sbin/asterisk -rx 'unload app_hello.so'
== Unregistered application 'Hello'
/bin/cp -p apps/app_hello.so /usr/lib/asterisk/modules
/usr/sbin/asterisk -rx 'load app_hello.so'
Loaded /usr/lib/asterisk/modules/app_hello.so => (Hello Kitty Example App)
== Registered application 'Hello'
In the above shell session, Asterisk is running and we are located in the sources folder. When running astxs, the -install option tells astxs to copy our library /usr/lib/asterisk/modules upon compilation. If we specify -autoload, astxs will also load the new version of our module in to Asterisk using the CLI commands you learned earlier. By this point, you should have a fairly good understanding of the manner in which function and application modules are structured and compiled in to Asterisk. In the next section, you will take a look at cool things you can do with this new knowledge. A Tone Generating ApplicationThis section provides a crash course in digital sound and an example tone generating application. When run, the tone application will generate a wave at a given frequency using basic trigonometry. What is Sound?Every good Asterisk hacker should know a thing or two about the math behind sound. Sound mathematics is a crucial part of telephony. Telephony is the art of moving sound from between points A and B. In order to do accomplish this feat, it helps to understand a bit about the nature of sound. The following is very brief overview of some fundamental concepts. If you are an experienced telecommunications engineer, feel free to skip this and the next section. Sound[see] occurs in nature when matter vibrates. These vibrations can be interpreted mathematically and graphed as waveforms[see]. All sounds you hear in nature are simply combinations of different waveforms, at different frequencies and amplitudes. The most common type of waveform is the sine wave. Sine waves repeat cycles in which the result of the sine function moves from 0, to 1, to 0, to -1, and back to 0. This is known as a cycle. (See graph) Frequency is measured by how many sine cycles occur in a single second, notated as hertz (hz). For example, the female voice has a frequency range of about 165 to 255 hz. So when your wife is yelling at you to get off the computer and paint the garage, your ears are being barraged by approximately 200 sine cycles every second. If you are a girl, you only need to tolerate about 85-155 sine cycles per second of cheesy pickup lines from lonely Asterisk hackers at VoIP conferences such as, "Hey baby, let me be your derivative so I can stay tangent to your curves". If you were to create a machine that produced a tone at a given frequency, you would also need to know the speed at which sound travels. At 20°C, sound travels 343 meters a second. Therefore, to produce a 1000 hz tone, the length of a cycle in meters (λ or wavelength) would need to be 343 meters divided by 1000. This concept is expressed in the below functions: ![]() where f is frequency in hertz, c is the speed at which the sound travels in meters per second, and A is amplitude (volume). The equation on the left is the same as the one on the right and is only provided for clarity. How Computers Understand SoundComputers are not capable of fully comprehending sound, because in nature, sound is infinitely precise, and has infinite range. In order to digitize sound, you need to read samples. Sampling is when you read the y value of a wave a given number of times a second. Using sampled data, the computer can make a guess as to how the wave form appeared when reproducing the digitized sound. The more frequently you read samples, the more precisely your computer can reproduce the recorded sound. (Note: Sampling by simply reading the y value of the wave without any manipulation is known as linear encoding. The act of digitizing sound is often referred to as Pulse Code Modulation [PCM].) So the question becomes, how many times per second should you sample the wave in order to make the digital reproduction sound good? According to the Nyquist-Shannon sampling[see] theorem, your sampling rate must be twice the maximum expected frequency in order to not destroy the audio wave. For example, AT&T standardized on sampling voice 8000 times a second because they didn't expect to use any frequency above 4000. Compact discs sample the audio from your favorite bands 44,100 times a second because music requires much higher quality and frequency ranges than a telephone conversation. For more information on why this theorem makes sense, check out this page. When digitizing sound, you also need to decide how much data you are going to allocate to each sample. If you are using 8-bit samples, you can only have 256 different Y values which gives you a rather limited volume range. 16-bit samples will offer you nearly 65536 different y values. 16-bit samples are a bit large for a telephone conversation because that would require 128Kbps of data (8000 * 16)! However, dividing the entire natural volume range in to just 256 values would sound awful. To be able to use smaller samples to reduce bandwidth requirements, non-linear encodings such as μ-law were invented. The engineers who invented μ-law realized that our ears perceive sound logarithmically. When we hear something quiet, it sounds quiet. If you cut the volume in half again, it still just sounds quiet. When we hear something loud, it sounds loud, and as it continues to get louder the only thing that really changes is how much our ears bleed. Our hearing tends to more accurately determine changes in volume in the middle ranges of sound. So a logarithmic algorithm for converting 16-bit samples to 8-bit samples was created. The μ-law algorithm allocates most of the limited range of volume towards the middle ground of the volume spectrum. This allows a more accurate volume range for speaking volumes, and less accuracy to very loud and very soft noises. Unlike other compressed codecs such as G.729, μ-law is very simple because it does not need to know information about other samples to do calculations. This makes it a very efficient codec. For example, the Asterisk implementation of μ-law (when not in low-memory mode) will pre-calculate conversation tables and perform encoding by dereferencing an array. app_tone.cIf you found the above crash-course in sound sampling a bit intimidating, take comfort in the fact that Asterisk does most of the hard work for you. When reading and writing data to a channel, you can call ast_set_write_format() and ast_set_write_format() to have Asterisk automatically transcode from the channel's native format to whatever format you like. In the below example, we will be manipulating data in SLINEAR format, which is 8000 signed short samples per second. The Tone application will take three arguments and one option. F(x) to specify frequency in hertz, the T(x) argument to specify play time in milliseconds, the V(x) argument for volume on a scale of 0.0 to 1.0, and 'e' option to specify whether or not to allow the caller to stop the tone by dialing '#'. Asterisk has a suite of macros to make parsing these argument lists easier. To start, we need to specify what we plan to use in the declarations section of our source. The below code uses macros found in include/asterisk/app.h that do most of the dirty work for declaring an array of 128 ast_app_option structures. The character you specify to identify the option, such as 'F', becomes the index. Therefore, you should not use any extended ASCII or UNICODE characters. For more information, see include/asterisk/app.h. Excerpt: apps/app_tone.c - Declarations enum {
OPT_FREQUENCY = (1 << 0),
OPT_VOLUME = (1 << 1),
OPT_MILLISECONDS = (1 << 2),
OPT_POUNDEXIT = (1 << 4),
} tone_exec_option_flags;
enum {
OPT_ARG_FREQUENCY = 0,
OPT_ARG_VOLUME,
OPT_ARG_MILLISECONDS,
OPT_ARG_ARRAY_SIZE, /* leave this alone! used to determine number of args */
} tone_exec_option_args;
AST_APP_OPTIONS(tone_exec_options, {
AST_APP_OPTION('e', OPT_POUNDEXIT),
AST_APP_OPTION_ARG('F', OPT_FREQUENCY, OPT_ARG_FREQUENCY),
AST_APP_OPTION_ARG('V', OPT_VOLUME, OPT_ARG_VOLUME),
AST_APP_OPTION_ARG('T', OPT_MILLISECONDS, OPT_ARG_MILLISECONDS),
});
In our exec function, we need to parse the options and arguments from the data variable. This can be done with ast_app_parse_options(). Be sure to pass a ast_strdupa() version of data to this function. Excerpt: apps/app_tone.c - tone_exec() char *args; /* ast_strdupa()'d version of data */
struct ast_flags opts = { 0, };
char *opt_args[OPT_ARG_ARRAY_SIZE];
...
if (ast_app_parse_options(tone_exec_options, &opts, opt_args, args)) {
LOCAL_USER_REMOVE(u);
return -1;
}
Assuming ast_app_parse_options() was successful, opts and opt_args should now contain information about your options and arguments. ast_test_flag() will test if one of your options or arguments was set or specified. If so, you can retrieve argument values from the opt_args array. Now we will pull our arguments from the args arrays and put them in to local variables. Note: Be sure to use the ast_strlen_zero() shortcut to test if a string is blank. This function will not segfault Asterisk if your string turns out to be a NULL pointer and helps to reduce the bug-load. Remember, you're not developing open source accounting software; PBX systems need to be as crash-free as possible! Excerpt: apps/app_tone.c - tone_exec() int freq = 0;
double vol = 0.1;
int milsec = 0;
int poundexit = 0;
...
if (ast_test_flag(&opts, OPT_FREQUENCY) && !ast_strlen_zero(opt_args[OPT_ARG_FREQUENCY])) {
freq = strtol(opt_args[OPT_ARG_FREQUENCY], NULL, 10);
if (freq > 4000)
ast_log(LOG_NOTICE, "Frequencies above 4000 can not be reproduced properly with any codec\n");
if (freq <= 0) {
ast_log(LOG_WARNING, "Frequencies of 0 or less hertz not allowed\n");
LOCAL_USER_REMOVE(u);
return -1;
}
}
if (ast_test_flag(&opts, OPT_VOLUME) && !ast_strlen_zero(opt_args[OPT_ARG_VOLUME])) {
vol = strtod(opt_args[OPT_ARG_VOLUME], NULL);
if (isnan(vol) || vol <= 0.0 || vol > 1.0) {
ast_log(LOG_NOTICE, "Invalid volume (%f), reverting to 0.1\n", vol);
vol = 0.1;
}
}
if (ast_test_flag(&opts, OPT_MILLISECONDS) && !ast_strlen_zero(opt_args[OPT_ARG_MILLISECONDS])) {
milsec = strtol(opt_args[OPT_ARG_MILLISECONDS], NULL, 10);
}
if (ast_test_flag(&opts, OPT_POUNDEXIT)) {
poundexit = 1;
}
if (option_verbose > 4)
ast_verbose(VERBOSE_PREFIX_4 "Preparing to play tone: hz=%d, vol=%f, len_ms=%d\n", freq, vol, milsec);
And here is the full app_tone.c file for you to easily copy/paste: File: apps/app_tone.c #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: 7221 $")
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/options.h"
#include "asterisk/lock.h"
#include "asterisk/app.h"
#include "asterisk/frame.h"
static char *tdesc = "Tone Generating Application";
static char *app = "Tone";
static char *synopsis = "Generates a tone at a specified frequncy";
static char *descrip =
" Tone(F(hertz)[V(vol)T(milsec)]):\n"
"\n"
" Options:\n"
" F(x) - (REQUIRED) The frequency of the tone in hertz. Must be\n"
" a whole number.\n"
" T(x) - Time to play the tone in milliseconds. If this is not set\n"
" or set to zero or less, tone will be played until hangup.\n"
" V(x) - The volume on a scale of 0.0 to 1.0. Defaults to 0.1.\n"
" e - Allow caller to exit by pressing pound\n"
"\n"
"This application will generate a tone at a given freuqueny for a given\n"
"number of milliseconds at a given volume. If milliseconds <= 0, tone\n"
"will be played until hangup.\n"
"\n"
" Example:\n"
" exten => 666,1,Tone(F(2600)T(1000)e) ; The infamous 2600 tone for a second\n";
/* we're going to follow the standard argument parsing style
* implemented by applications such as Dial because app_skel.c is a
* bit out of date. */
enum {
OPT_FREQUENCY = (1 << 0),
OPT_VOLUME = (1 << 1),
OPT_MILLISECONDS = (1 << 2),
OPT_POUNDEXIT = (1 << 4),
} tone_exec_option_flags;
enum {
OPT_ARG_FREQUENCY = 0,
OPT_ARG_VOLUME,
OPT_ARG_MILLISECONDS,
OPT_ARG_ARRAY_SIZE, /* leave this alone! used to determine number of args */
} tone_exec_option_args;
AST_APP_OPTIONS(tone_exec_options, {
AST_APP_OPTION('e', OPT_POUNDEXIT),
AST_APP_OPTION_ARG('F', OPT_FREQUENCY, OPT_ARG_FREQUENCY),
AST_APP_OPTION_ARG('V', OPT_VOLUME, OPT_ARG_VOLUME),
AST_APP_OPTION_ARG('T', OPT_MILLISECONDS, OPT_ARG_MILLISECONDS),
});
STANDARD_LOCAL_USER;
LOCAL_USER_DECL;
static inline short get_sample(int sample, int freq, double vol)
{
/* this is based off the aforementioned sine wave formula
* where c = 8000 or the sampling rate */
return (short)(sin((double)sample * (2.0 * M_PI * (double)freq / 8000.0)) * (double)SHRT_MAX * vol);
}
static int echo_back_tone(struct ast_channel *chan, int freq, double vol, int milsec, int poundexit)
{
struct ast_frame *f;
short *sdata;
int cursamp = 0, nsamp;
milsec *= 8; /* get time length in samples */
/* despite the channels current codec, tell Asterisk to
* convert sound data to something we can understand */
ast_set_write_format(chan, AST_FORMAT_SLINEAR);
ast_set_read_format(chan, AST_FORMAT_SLINEAR);
/* read in audio on the channel and pump it back out changed
* to our tone */
while (ast_waitfor(chan, -1) > -1) {
f = ast_read(chan);
if (!f)
break;
f->delivery.tv_sec = 0;
f->delivery.tv_usec = 0;
if (f->frametype == AST_FRAME_DTMF) {
if (poundexit && f->subclass == '#') {
if (option_verbose > 4)
ast_verbose(VERBOSE_PREFIX_4 "Caller exited with #\n");
break;
}
}
if (f->frametype != AST_FRAME_VOICE || f->subclass != AST_FORMAT_SLINEAR)
continue;
/* overwrite frame with tone data for sending back to caller */
nsamp = f->samples;
sdata = (short *)f->data;
while (nsamp--) {
*sdata++ = get_sample(cursamp++, freq, vol);
if (cursamp == milsec) {
/* out of time, cut frame short */
f->samples = f->samples - nsamp;
ast_write(chan, f);
ast_frfree(f);
return 0;
}
}
if (ast_write(chan, f))
break;
ast_frfree(f);
}
return 0;
}
static int tone_exec(struct ast_channel *chan, void *data)
{
int res = 0;
struct localuser *u;
char *args;
struct ast_flags opts = { 0, };
char *opt_args[OPT_ARG_ARRAY_SIZE];
int freq = 0;
double vol = 0.1;
int milsec = 0;
int poundexit = 0;
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "Tone requires at least freq specified: Tone(F(hertz)[V(vol)T(milsec)])\n");
return -1;
}
LOCAL_USER_ADD(u);
if (!(args = ast_strdupa(data))) {
ast_log(LOG_WARNING, "I just blew your stack, sorry\n");
LOCAL_USER_REMOVE(u);
return -1;
}
if (ast_app_parse_options(tone_exec_options, &opts, opt_args, args)) {
LOCAL_USER_REMOVE(u);
return -1;
}
if (ast_test_flag(&opts, OPT_FREQUENCY) && !ast_strlen_zero(opt_args[OPT_ARG_FREQUENCY])) {
freq = strtol(opt_args[OPT_ARG_FREQUENCY], NULL, 10);
if (freq > 4000)
ast_log(LOG_NOTICE, "Frequencies above 4000 can not be reproduced properly with any codec\n");
if (freq <= 0) {
ast_log(LOG_WARNING, "Frequencies of 0 or less hertz not allowed\n");
LOCAL_USER_REMOVE(u);
return -1;
}
}
if (ast_test_flag(&opts, OPT_VOLUME) && !ast_strlen_zero(opt_args[OPT_ARG_VOLUME])) {
vol = strtod(opt_args[OPT_ARG_VOLUME], NULL);
if (isnan(vol) || vol <= 0.0 || vol > 1.0) {
ast_log(LOG_NOTICE, "Invalid volume (%f), reverting to 0.1\n", vol);
vol = 0.1;
}
}
if (ast_test_flag(&opts, OPT_MILLISECONDS) && !ast_strlen_zero(opt_args[OPT_ARG_MILLISECONDS])) {
milsec = strtol(opt_args[OPT_ARG_MILLISECONDS], NULL, 10);
}
if (ast_test_flag(&opts, OPT_POUNDEXIT)) {
poundexit = 1;
}
if (option_verbose > 4)
ast_verbose(VERBOSE_PREFIX_4 "Preparing to play tone: hz=%d, vol=%f, len_ms=%d\n", freq, vol, milsec);
res = echo_back_tone(chan, freq, vol, milsec, poundexit);
LOCAL_USER_REMOVE(u);
return res;
}
int unload_module(void)
{
int res;
res = ast_unregister_application(app);
STANDARD_HANGUP_LOCALUSERS;
return res;
}
int load_module(void)
{
return ast_register_application(app, tone_exec, synopsis, descrip);
}
char *description(void)
{
return tdesc;
}
int usecount(void)
{
int res;
STANDARD_USECOUNT(res);
return res;
}
char *key()
{
return ASTERISK_GPL_KEY;
}
And the Asterisk output from running it: Excerpt: CLI Output -- Executing Answer("Zap/1-1", "") in new stack
-- Executing Tone("Zap/1-1", "F(2600)T(4000)e") in new stack
> Preparing to play tone: hz=2600, vol=0.100000, len_ms=4000
> Caller exited with #
-- Executing Hangup("Zap/1-1", "") in new stack
|
Tag Cloudaccounting assembly asterisk c django erlang games hacking i18n latin1 linux mysql networking python qos speaking tc travel tutorial unicode utf8 web Archive
July 2009 Popular Content
Asterisk Voice Changer
(6906 Views) Recent Comments
Asterisk Voice Changer
on Feb 17 by Yesh |
![One sine cycle [img: one sine cycle]](/media/img/sine.png)
![y = sin(x * (2 * pi * f / c)) [img: y = sin(x * (2 * pi * f / c))]](/media/img/eq_sine.png)
![mulaw logarithmic encoding [img: mulaw logarithmic encoding]](/media/img/mulaw.png)
![F(x) = \sgn(x) \frac{\ln(1+ \mu |x|)}{\ln(1+\mu)}~~~~-1 \leq x \leq 1 [img: F(x) = \sgn(x) \frac{\ln(1+ \mu |x|)}{\ln(1+\mu)}~~~~-1 \leq x \leq 1]](/media/img/eq_mulaw.png)

Comments
No comments found.
Post Comment