Hey all, I'm working on writing a channel driver for a program called asterisk (It's an open source PBX) that the driver will use alsa.
It's been an interesting experience learning alsa, thanks to all who've worked on it; It's API is pretty straightforward, though I haven't done any sound programming before this. The circumstance is this: It's a full duplex application so to speak, but I don't have control over what order it does reads/writes, etc. I'm only basically writing plugin functions for the asterisk channel interface. Writes and reads are done in mostly 160 byte (80 sample, 16bit S-linear) chunks. Also, it's very possible that sometimes writes and reads don't come in for extended periods of time (VoIP packeted data), so it's not a situation where I know I'm going to get data immediately, as opposed to if I was just reading a file or something like that. The first thing I came up with sounded really bad. XRUNs all over the place, etc. I started playing around with various parameters, buffer size, start threshold, stuff like that, and it's improved a little bit, but I'm not exactly sure why playing around with them has improved it. The Problem: Now I'm at a point where it sounds really good for a few minutes, a couple of XRUNS every now and then, and then just about perfect for a while. After it has done this, it gets to a point where I rapidly start getting more and more XRUNS, and eventually that's about the only thing it gets. Boom. No more audio. Arrghhh! :-D (Actually, I'm only having problems with the read side, so that I seem to be getting XRUNs from just the reading, but I suspect if my read period size and my write period size I obtained from the card was the same, I would get them both to bomb out at the same time). Some weird stuff I've noticed: When I have something else play concurrently (like a KDE artsd sound effect), it seems to cause an XRUN in the stream too, and can change how long my time is of good audio. It kind of feels like I'm able to keep data in my write buffer (Which is a multiple of 384 samples(write period size) long, depending on what I set the tweak parameter to), but I'm not able to get enough data in the read buffer (Which is a multiple of 80 samples(read period size) long). Maybe my start threshold is too low, but I find if I increase it to >= 1 period size, I can't get any kind of audio. Would someone mind giving some pointers as to what I could be doing wrong? And also if there are any pointers for portability between cards, those would be much appreciated as well. I attached the code in question to this email. FWIW: I am using an SB Live! Value for development. There already exists an OSS driver that works for asterisk, so I know I'm not breaking any kind of asterisk architectural limits. ---------------------------------------------------------- On a side note, what exactly do the snd_pcm_hw_params_set_periods_[min,max] functions do? Thanks for the good API, Matthew Fredrickson
/* * Asterisk -- A telephony toolkit for Linux. * * Copyright (C) 2002, Linux Support Services * * By Matthew Fredrickson <[EMAIL PROTECTED]> * * This program is free software, distributed under the terms of * the GNU General Public License */ #include <asterisk/frame.h> #include <asterisk/logger.h> #include <asterisk/channel.h> #include <asterisk/module.h> #include <asterisk/channel_pvt.h> #include <asterisk/options.h> #include <asterisk/pbx.h> #include <asterisk/config.h> #include <asterisk/cli.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/time.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <alsa/asoundlib.h> #if 0 #include "busy.h" #include "ringtone.h" #include "ring10.h" #include "answer.h" #endif /* Which device to use */ #define ALSA_DEV "default" #define DESIRED_RATE 8000 /* Lets use 160 sample frames, just like GSM. */ #define FRAME_SIZE 160 #define PERIOD_FRAMES 80 /* 80 Frames, at 2 bytes each */ /* When you set the frame size, you have to come up with the right buffer format as well. */ /* 5 64-byte frames = one frame */ #define BUFFER_FMT ((buffersize * 10) << 16) | (0x0006); /* Don't switch between read/write modes faster than every 300 ms */ #define MIN_SWITCH_TIME 600 static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; //static int block = O_NONBLOCK; static char devname[50] = ALSA_DEV; static struct timeval lasttime; static int usecnt; static int needanswer = 0; static int needringing = 0; static int needhangup = 0; static int silencesuppression = 0; static int silencethreshold = 1000; static char digits[80] = ""; static char text2send[80] = ""; static pthread_mutex_t usecnt_lock = PTHREAD_MUTEX_INITIALIZER; static char *type = "Console"; static char *desc = "ALSA Console Channel Driver"; static char *tdesc = "ALSA Console Channel Driver"; static char *config = "alsa.conf"; static char context[AST_MAX_EXTENSION] = "default"; static char language[MAX_LANGUAGE] = ""; static char exten[AST_MAX_EXTENSION] = "s"; /* Command pipe */ static int cmd[2]; int hookstate=0; //static short silence[FRAME_SIZE] = {0, }; struct sound { int ind; short *data; int datalen; int samplen; int silencelen; int repeat; }; #if 0 static struct sound sounds[] = { { AST_CONTROL_RINGING, ringtone, sizeof(ringtone)/2, 16000, 32000, 1 }, { AST_CONTROL_BUSY, busy, sizeof(busy)/2, 4000, 4000, 1 }, { AST_CONTROL_CONGESTION, busy, sizeof(busy)/2, 2000, 2000, 1 }, { AST_CONTROL_RING, ring10, sizeof(ring10)/2, 16000, 32000, 1 }, { AST_CONTROL_ANSWER, answer, sizeof(answer)/2, 2200, 0, 0 }, }; #endif /* Sound command pipe */ static int sndcmd[2]; static struct chan_alsa_pvt { /* We only have one ALSA structure -- near sighted perhaps, but it keeps this driver as simple as possible -- as it should be. */ struct ast_channel *owner; char exten[AST_MAX_EXTENSION]; char context[AST_MAX_EXTENSION]; #if 0 snd_pcm_t *card; #endif snd_pcm_t *icard, *ocard; } alsa; static int time_has_passed() { struct timeval tv; int ms; gettimeofday(&tv, NULL); ms = (tv.tv_sec - lasttime.tv_sec) * 1000 + (tv.tv_usec - lasttime.tv_usec) / 1000; if (ms > MIN_SWITCH_TIME) return -1; return 0; } /* Number of buffers... Each is FRAMESIZE/8 ms long. For example with 160 sample frames, and a buffer size of 3, we have a 60ms buffer, usually plenty. */ pthread_t sthread; #define MAX_BUFFER_SIZE 100 //static int buffersize = 3; //static int full_duplex = 0; /* Are we reading or writing (simulated full duplex) */ //static int readmode = 1; /* File descriptor for sound device */ static int sounddev = -1; static int autoanswer = 1; static int calc_loudness(short *frame) { int sum = 0; int x; for (x=0;x<FRAME_SIZE;x++) { if (frame[x] < 0) sum -= frame[x]; else sum += frame[x]; } sum = sum/FRAME_SIZE; return sum; } static int cursound = -1; static int sampsent = 0; static int silencelen=0; static int offset=0; static int nosound=0; #if 0 static int send_sound(void) { short myframe[FRAME_SIZE]; int total = FRAME_SIZE; short *frame = NULL; int amt=0; int res; int myoff; audio_buf_info abi; if (cursound > -1) { res = ioctl(sounddev, SNDCTL_DSP_GETOSPACE ,&abi); if (res) { ast_log(LOG_WARNING, "Unable to read output space\n"); return -1; } /* Calculate how many samples we can send, max */ if (total > (abi.fragments * abi.fragsize / 2)) total = abi.fragments * abi.fragsize / 2; res = total; if (sampsent < sounds[cursound].samplen) { myoff=0; while(total) { amt = total; if (amt > (sounds[cursound].datalen - offset)) amt = sounds[cursound].datalen - offset; memcpy(myframe + myoff, sounds[cursound].data + offset, amt * 2); total -= amt; offset += amt; sampsent += amt; myoff += amt; if (offset >= sounds[cursound].datalen) offset = 0; } /* Set it up for silence */ if (sampsent >= sounds[cursound].samplen) silencelen = sounds[cursound].silencelen; frame = myframe; } else { if (silencelen > 0) { frame = silence; silencelen -= res; } else { if (sounds[cursound].repeat) { /* Start over */ sampsent = 0; offset = 0; } else { cursound = -1; nosound = 0; } } } res = write(sounddev, frame, res * 2); if (res > 0) return 0; return res; } return 0; } #endif static void *sound_thread(void *unused) { fd_set rfds; fd_set wfds; int max; int res; for(;;) { FD_ZERO(&rfds); FD_ZERO(&wfds); max = sndcmd[0]; FD_SET(sndcmd[0], &rfds); if (cursound > -1) { FD_SET(sounddev, &wfds); if (sounddev > max) max = sounddev; } res = select(max + 1, &rfds, &wfds, NULL, NULL); if (res < 1) { ast_log(LOG_WARNING, "select failed: %s\n", strerror(errno)); continue; } if (FD_ISSET(sndcmd[0], &rfds)) { read(sndcmd[0], &cursound, sizeof(cursound)); silencelen = 0; offset = 0; sampsent = 0; } #if 0 if (FD_ISSET(sounddev, &wfds)) if (send_sound()) ast_log(LOG_WARNING, "Failed to write sound\n"); #endif } /* Never reached */ return NULL; } #if 0 static int setformat(void) { int fmt, desired, res, fd = sounddev; static int warnedalready = 0; static int warnedalready2 = 0; fmt = AFMT_S16_LE; res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt); if (res < 0) { ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n"); return -1; } res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); if (res >= 0) { if (option_verbose > 1) ast_verbose(VERBOSE_PREFIX_2 "Console is full duplex\n"); full_duplex = -1; } fmt = 0; res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt); if (res < 0) { ast_log(LOG_WARNING, "Failed to set audio device to mono\n"); return -1; } /* 8000 Hz desired */ desired = 8000; fmt = desired; res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt); if (res < 0) { ast_log(LOG_WARNING, "Failed to set audio device to mono\n"); return -1; } if (fmt != desired) { if (!warnedalready++) ast_log(LOG_WARNING, "Requested %d Hz, got %d Hz -- sound may be choppy\n", desired, fmt); } #if 1 fmt = BUFFER_FMT; res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt); if (res < 0) { if (!warnedalready2++) ast_log(LOG_WARNING, "Unable to set fragment size -- sound may be choppy\n"); } #endif return 0; } #endif static snd_pcm_t *alsa_card_init(char *dev, snd_pcm_stream_t stream) { int err; snd_pcm_t *handle = NULL; snd_pcm_hw_params_t *hwparams = NULL; snd_pcm_sw_params_t *swparams = NULL; struct pollfd pfd; int period_size = PERIOD_FRAMES; //int period_bytes = 0; int buffer_size = 0; unsigned int rate = DESIRED_RATE; unsigned int per_min = 1, per_max = 2; snd_pcm_uframes_t start_threshold, stop_threshold; if ((err = snd_pcm_open(&handle, dev, stream, O_NONBLOCK)) < 0) { //if ((err = snd_pcm_open(&handle, dev, stream, 0)) < 0) { ast_log(LOG_ERROR, "snd_pcm_open failed: %s\n", snd_strerror(err)); return NULL; } else { ast_log(LOG_DEBUG, "Opening device %s in %s mode\n", dev, (stream == SND_PCM_STREAM_CAPTURE) ? "read" : "write"); } snd_pcm_hw_params_alloca(&hwparams); snd_pcm_hw_params_any(handle, hwparams); err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) { ast_log(LOG_ERROR, "set_access failed: %s\n", snd_strerror(err)); } err = snd_pcm_hw_params_set_format(handle, hwparams, format); if (err < 0) { ast_log(LOG_ERROR, "set_format failed: %s\n", snd_strerror(err)); } err = snd_pcm_hw_params_set_channels(handle, hwparams, 1); if (err < 0) { ast_log(LOG_ERROR, "set_channels failed: %s\n", snd_strerror(err)); } rate = snd_pcm_hw_params_set_rate_near(handle, hwparams, rate, 0); if (rate != DESIRED_RATE) { ast_log(LOG_WARNING, "Rate not correct, requested %d, got %d\n", DESIRED_RATE, rate); } err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, period_size, 0); if (err < 0) { ast_log(LOG_ERROR, "period_size(%d frames) is bad: %s\n", period_size, snd_strerror(err)); } else { ast_log(LOG_DEBUG, "Period size is %d\n", err); } period_size = err; buffer_size = period_size * 4; err = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, buffer_size); if (err < 0) { ast_log(LOG_WARNING, "Problem setting buffer size of %d: %s\n", buffer_size, snd_strerror(err)); } else { ast_log(LOG_DEBUG, "Buffer size is set to %d frames\n", err); } buffer_size = err; // period_size = buffer_size / 3; err = snd_pcm_hw_params_set_periods_min(handle, hwparams, &per_min, 0); if (err < 0) { ast_log(LOG_ERROR, "periods_min: %s\n", snd_strerror(err)); } err = snd_pcm_hw_params_set_periods_max(handle, hwparams, &per_max, 0); if (err < 0) { ast_log(LOG_ERROR, "periods_max: %s\n", snd_strerror(err)); } err = snd_pcm_hw_params(handle, hwparams); if (err < 0) { ast_log(LOG_ERROR, "Couldn't set the new hw params: %s\n", snd_strerror(err)); } ast_log(LOG_DEBUG, "Bang!\n"); snd_pcm_sw_params_alloca(&swparams); snd_pcm_sw_params_current(handle, swparams); #if 1 start_threshold = period_size / 4; err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold); if (err < 0) { ast_log(LOG_ERROR, "start threshold: %s\n", snd_strerror(err)); } stop_threshold = buffer_size; err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold); if (err < 0) { ast_log(LOG_ERROR, "stop threshold: %s\n", snd_strerror(err)); } #endif err = snd_pcm_sw_params_set_xfer_align(handle, swparams, PERIOD_FRAMES); if (err < 0) { ast_log(LOG_ERROR, "Unable to set xfer alignment: %s\n", snd_strerror(err)); } err = snd_pcm_sw_params_set_silence_threshold(handle, swparams, period_size); if (err < 0) { ast_log(LOG_ERROR, "Unable to set silence threshold: %s\n", snd_strerror(err)); } err = snd_pcm_sw_params(handle, swparams); if (err < 0) { ast_log(LOG_ERROR, "sw_params: %s\n", snd_strerror(err)); } err = snd_pcm_poll_descriptors_count(handle); if (err <= 0) { ast_log(LOG_ERROR, "Unable to get a poll descriptors count, error is %s\n", snd_strerror(err)); } if (err != 1) { ast_log(LOG_DEBUG, "Can't handle more than one device\n"); } snd_pcm_poll_descriptors(handle, &pfd, err); ast_log(LOG_DEBUG, "Acquired fd %d from the poll descriptor\n", pfd.fd); if (stream == SND_PCM_STREAM_CAPTURE) sounddev = pfd.fd; ast_log(LOG_DEBUG, "Bang2\n"); return handle; } static int soundcard_init() { alsa.icard = alsa_card_init(devname, SND_PCM_STREAM_CAPTURE); alsa.ocard = alsa_card_init(devname, SND_PCM_STREAM_PLAYBACK); return sounddev; } static int alsa_digit(struct ast_channel *c, char digit) { ast_verbose( " << Console Received digit %c >> \n", digit); return 0; } static int alsa_text(struct ast_channel *c, char *text) { ast_verbose( " << Console Received text %s >> \n", text); return 0; } static int alsa_call(struct ast_channel *c, char *dest, int timeout) { int res = 3; ast_verbose( " << Call placed to '%s' on console >> \n", dest); if (autoanswer) { ast_verbose( " << Auto-answered >> \n" ); needanswer = 1; } else { ast_verbose( " << Type 'answer' to answer, or use 'autoanswer' for future calls >> \n"); needringing = 1; write(sndcmd[1], &res, sizeof(res)); } return 0; } static void answer_sound(void) { int res; nosound = 1; res = 4; write(sndcmd[1], &res, sizeof(res)); } static int alsa_answer(struct ast_channel *c) { ast_verbose( " << Console call has been answered >> \n"); answer_sound(); c->state = AST_STATE_UP; cursound = -1; return 0; } static int alsa_hangup(struct ast_channel *c) { int res; cursound = -1; c->pvt->pvt = NULL; alsa.owner = NULL; ast_verbose( " << Hangup on console >> \n"); ast_pthread_mutex_lock(&usecnt_lock); usecnt--; ast_pthread_mutex_unlock(&usecnt_lock); needhangup = 0; needanswer = 0; if (hookstate) { res = 2; write(sndcmd[1], &res, sizeof(res)); } return 0; } #if 0 static int soundcard_writeframe(short *data) { /* Write an exactly FRAME_SIZE sized of frame */ static int bufcnt = 0; static short buffer[FRAME_SIZE * MAX_BUFFER_SIZE * 5]; struct audio_buf_info info; int res; int fd = sounddev; static int warned=0; if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info)) { if (!warned) ast_log(LOG_WARNING, "Error reading output space\n"); bufcnt = buffersize; warned++; } if ((info.fragments >= buffersize * 5) && (bufcnt == buffersize)) { /* We've run out of stuff, buffer again */ bufcnt = 0; } if (bufcnt == buffersize) { /* Write sample immediately */ res = write(fd, ((void *)data), FRAME_SIZE * 2); } else { /* Copy the data into our buffer */ res = FRAME_SIZE * 2; memcpy(buffer + (bufcnt * FRAME_SIZE), data, FRAME_SIZE * 2); bufcnt++; if (bufcnt == buffersize) { res = write(fd, ((void *)buffer), FRAME_SIZE * 2 * buffersize); } } return res; } #endif static int alsa_write(struct ast_channel *chan, struct ast_frame *f) { int res; static char sizbuf[8000]; static int sizpos = 0; int len = sizpos; int pos; //size_t frames = 0; snd_pcm_state_t state; /* Immediately return if no sound is enabled */ if (nosound) return 0; /* Stop any currently playing sound */ cursound = -1; /* We have to digest the frame in 160-byte portions */ if (f->datalen > sizeof(sizbuf) - sizpos) { ast_log(LOG_WARNING, "Frame too large\n"); return -1; } memcpy(sizbuf + sizpos, f->data, f->datalen); len += f->datalen; pos = 0; state = snd_pcm_state(alsa.ocard); if (state == SND_PCM_STATE_XRUN) { snd_pcm_prepare(alsa.ocard); } res = snd_pcm_writei(alsa.ocard, sizbuf, len/2); if (res == -EPIPE) { ast_log(LOG_ERROR, "XRUN write\n"); snd_pcm_prepare(alsa.ocard); res = snd_pcm_writei(alsa.ocard, sizbuf, len/2); if (res != len/2) { ast_log(LOG_ERROR, "Write error: %s\n", snd_strerror(res)); return -1; } else if (res < 0) { ast_log(LOG_ERROR, "Write error %s\n", snd_strerror(res)); return -1; } } else { if (res == -ESTRPIPE) { ast_log(LOG_ERROR, "You've got some big problems\n"); } } return 0; } static struct ast_frame *alsa_read(struct ast_channel *chan) { static struct ast_frame f; static char buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET]; static int readpos = 0; int res; int b; int nonull=0; int frame_bytes = (snd_pcm_format_width(format) / 8); // size_t frames = FRAME_SIZE; snd_pcm_state_t state; int r = 0; int off = 0; /* Acknowledge any pending cmd */ res = read(cmd[0], &b, sizeof(b)); if (res > 0) nonull = 1; f.frametype = AST_FRAME_NULL; f.subclass = 0; f.timelen = 0; f.datalen = 0; f.data = NULL; f.offset = 0; f.src = type; f.mallocd = 0; if (needringing) { f.frametype = AST_FRAME_CONTROL; f.subclass = AST_CONTROL_RINGING; needringing = 0; return &f; } if (needhangup) { needhangup = 0; return NULL; } if (strlen(text2send)) { f.frametype = AST_FRAME_TEXT; f.subclass = 0; f.data = text2send; f.datalen = strlen(text2send); strcpy(text2send,""); return &f; } if (strlen(digits)) { f.frametype = AST_FRAME_DTMF; f.subclass = digits[0]; for (res=0;res<strlen(digits);res++) digits[res] = digits[res + 1]; return &f; } if (needanswer) { needanswer = 0; f.frametype = AST_FRAME_CONTROL; f.subclass = AST_CONTROL_ANSWER; chan->state = AST_STATE_UP; return &f; } if (nonull) return &f; //r = readbuf(alsa.icard, buf + AST_FRIENDLY_OFFSET, FRAME_SIZE, &frames); { int r = 0; snd_pcm_avail_update(alsa.icard); ast_log(LOG_DEBUG, "%d samples available for read\n", r); } state = snd_pcm_state(alsa.ocard); if (state == SND_PCM_STATE_XRUN) { snd_pcm_prepare(alsa.ocard); } #if 1 off = FRAME_SIZE; do { r = snd_pcm_readi(alsa.icard, buf+AST_FRIENDLY_OFFSET, off); if (r == -EPIPE) { ast_log(LOG_ERROR, "XRUN read\n"); snd_pcm_prepare(alsa.icard); continue; } else if (r < 0) { ast_log(LOG_ERROR, "Read error: %s\n", snd_strerror); return NULL; } else if (r >= 0) { off -= r; } } while (r == -EAGAIN || r < FRAME_SIZE); #endif res = frame_bytes * r; readpos += res; if (readpos >= FRAME_SIZE * frame_bytes) { /* A real frame */ readpos = 0; if (chan->state != AST_STATE_UP) { /* Don't transmit unless it's up */ return &f; } f.frametype = AST_FRAME_VOICE; f.subclass = AST_FORMAT_SLINEAR; f.timelen = FRAME_SIZE / 8; f.datalen = FRAME_SIZE * 2; f.data = buf + AST_FRIENDLY_OFFSET; f.offset = AST_FRIENDLY_OFFSET; f.src = type; f.mallocd = 0; #if 0 { static int fd = -1; if (fd < 0) fd = open("output.raw", O_RDWR | O_TRUNC | O_CREAT); write(fd, f.data, f.datalen); } #endif } return &f; } static int alsa_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct chan_alsa_pvt *p = newchan->pvt->pvt; p->owner = newchan; return 0; } static int alsa_indicate(struct ast_channel *chan, int cond) { int res; switch(cond) { case AST_CONTROL_BUSY: res = 1; break; case AST_CONTROL_CONGESTION: res = 2; break; case AST_CONTROL_RINGING: res = 0; break; default: ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, chan->name); return -1; } if (res > -1) { write(sndcmd[1], &res, sizeof(res)); } return 0; } static struct ast_channel *alsa_new(struct chan_alsa_pvt *p, int state) { struct ast_channel *tmp; tmp = ast_channel_alloc(); if (tmp) { snprintf(tmp->name, sizeof(tmp->name), "ALSA/%s", devname); tmp->type = type; tmp->fds[0] = sounddev; tmp->fds[1] = cmd[0]; tmp->nativeformats = AST_FORMAT_SLINEAR; tmp->pvt->pvt = p; tmp->pvt->send_digit = alsa_digit; tmp->pvt->send_text = alsa_text; tmp->pvt->hangup = alsa_hangup; tmp->pvt->answer = alsa_answer; tmp->pvt->read = alsa_read; tmp->pvt->call = alsa_call; tmp->pvt->write = alsa_write; tmp->pvt->indicate = alsa_indicate; tmp->pvt->fixup = alsa_fixup; if (strlen(p->context)) strncpy(tmp->context, p->context, sizeof(tmp->context)-1); if (strlen(p->exten)) strncpy(tmp->exten, p->exten, sizeof(tmp->exten)-1); if (strlen(language)) strncpy(tmp->language, language, sizeof(tmp->language)-1); p->owner = tmp; tmp->state = state; ast_pthread_mutex_lock(&usecnt_lock); usecnt++; ast_pthread_mutex_unlock(&usecnt_lock); ast_update_use_count(); if (state != AST_STATE_DOWN) { if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name); ast_hangup(tmp); tmp = NULL; } } } return tmp; } static struct ast_channel *alsa_request(char *type, int format, void *data) { int oldformat = format; struct ast_channel *tmp; format &= AST_FORMAT_SLINEAR; if (!format) { ast_log(LOG_NOTICE, "Asked to get a channel of format '%d'\n", oldformat); return NULL; } if (alsa.owner) { ast_log(LOG_NOTICE, "Already have a call on the ALSA channel\n"); return NULL; } tmp= alsa_new(&alsa, AST_STATE_DOWN); if (!tmp) { ast_log(LOG_WARNING, "Unable to create new ALSA channel\n"); } return tmp; } static int console_autoanswer(int fd, int argc, char *argv[]) { if ((argc != 1) && (argc != 2)) return RESULT_SHOWUSAGE; if (argc == 1) { ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off"); return RESULT_SUCCESS; } else { if (!strcasecmp(argv[1], "on")) autoanswer = -1; else if (!strcasecmp(argv[1], "off")) autoanswer = 0; else return RESULT_SHOWUSAGE; } return RESULT_SUCCESS; } static char *autoanswer_complete(char *line, char *word, int pos, int state) { #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif switch(state) { case 0: if (strlen(word) && !strncasecmp(word, "on", MIN(strlen(word), 2))) return strdup("on"); case 1: if (strlen(word) && !strncasecmp(word, "off", MIN(strlen(word), 3))) return strdup("off"); default: return NULL; } return NULL; } static char autoanswer_usage[] = "Usage: autoanswer [on|off]\n" " Enables or disables autoanswer feature. If used without\n" " argument, displays the current on/off status of autoanswer.\n" " The default value of autoanswer is in 'alsa.conf'.\n"; static int console_answer(int fd, int argc, char *argv[]) { if (argc != 1) return RESULT_SHOWUSAGE; if (!alsa.owner) { ast_cli(fd, "No one is calling us\n"); return RESULT_FAILURE; } hookstate = 1; cursound = -1; needanswer++; answer_sound(); return RESULT_SUCCESS; } static char sendtext_usage[] = "Usage: send text <message>\n" " Sends a text message for display on the remote terminal.\n"; static int console_sendtext(int fd, int argc, char *argv[]) { int tmparg = 1; if (argc < 1) return RESULT_SHOWUSAGE; if (!alsa.owner) { ast_cli(fd, "No one is calling us\n"); return RESULT_FAILURE; } if (strlen(text2send)) ast_cli(fd, "Warning: message already waiting to be sent, overwriting\n"); strcpy(text2send, ""); while(tmparg <= argc) { strncat(text2send, argv[tmparg++], sizeof(text2send) - strlen(text2send)); strncat(text2send, " ", sizeof(text2send) - strlen(text2send)); } needanswer++; return RESULT_SUCCESS; } static char answer_usage[] = "Usage: answer\n" " Answers an incoming call on the console (ALSA) channel.\n"; static int console_hangup(int fd, int argc, char *argv[]) { if (argc != 1) return RESULT_SHOWUSAGE; cursound = -1; if (!alsa.owner && !hookstate) { ast_cli(fd, "No call to hangup up\n"); return RESULT_FAILURE; } hookstate = 0; if (alsa.owner) needhangup++; return RESULT_SUCCESS; } static char hangup_usage[] = "Usage: hangup\n" " Hangs up any call currently placed on the console.\n"; static int console_dial(int fd, int argc, char *argv[]) { char tmp[256], *tmp2; char *mye, *myc; int b = 0; if ((argc != 1) && (argc != 2)) return RESULT_SHOWUSAGE; if (alsa.owner) { if (argc == 2) { strncat(digits, argv[1], sizeof(digits) - strlen(digits)); /* Wake up the polling thread */ write(cmd[1], &b, sizeof(b)); } else { ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n"); return RESULT_FAILURE; } return RESULT_SUCCESS; } mye = exten; myc = context; if (argc == 2) { strncpy(tmp, argv[1], sizeof(tmp)-1); strtok(tmp, "@"); tmp2 = strtok(NULL, "@"); if (strlen(tmp)) mye = tmp; if (tmp2 && strlen(tmp2)) myc = tmp2; } if (ast_exists_extension(NULL, myc, mye, 1, NULL)) { strncpy(alsa.exten, mye, sizeof(alsa.exten)-1); strncpy(alsa.context, myc, sizeof(alsa.context)-1); hookstate = 1; alsa_new(&alsa, AST_STATE_UP); } else ast_cli(fd, "No such extension '%s' in context '%s'\n", mye, myc); return RESULT_SUCCESS; } static char dial_usage[] = "Usage: dial [extension[@context]]\n" " Dials a given extensison ("; static struct ast_cli_entry myclis[] = { { { "answer", NULL }, console_answer, "Answer an incoming console call", answer_usage }, { { "hangup", NULL }, console_hangup, "Hangup a call on the console", hangup_usage }, { { "dial", NULL }, console_dial, "Dial an extension on the console", dial_usage }, { { "send text", NULL }, console_sendtext, "Send text to the remote device", sendtext_usage }, { { "autoanswer", NULL }, console_autoanswer, "Sets/displays autoanswer", autoanswer_usage, autoanswer_complete } }; int load_module() { int res; int x; int flags; struct ast_config *cfg = ast_load(config); struct ast_variable *v; res = pipe(cmd); res = pipe(sndcmd); if (res) { ast_log(LOG_ERROR, "Unable to create pipe\n"); return -1; } flags = fcntl(cmd[0], F_GETFL); fcntl(cmd[0], F_SETFL, flags | O_NONBLOCK); flags = fcntl(cmd[1], F_GETFL); fcntl(cmd[1], F_SETFL, flags | O_NONBLOCK); res = soundcard_init(); if (res < 0) { close(cmd[1]); close(cmd[0]); if (option_verbose > 1) { ast_verbose(VERBOSE_PREFIX_2 "No sound card detected -- console channel will be unavailable\n"); ast_verbose(VERBOSE_PREFIX_2 "Turn off ALSA support by adding 'noload=chan_alsa.so' in /etc/asterisk/modules.conf\n"); } return 0; } #if 0 if (!full_duplex) ast_log(LOG_WARNING, "XXX I don't work right with non-full duplex sound cards XXX\n"); #endif res = ast_channel_register(type, tdesc, AST_FORMAT_SLINEAR, alsa_request); if (res < 0) { ast_log(LOG_ERROR, "Unable to register channel class '%s'\n", type); return -1; } for (x=0;x<sizeof(myclis)/sizeof(struct ast_cli_entry); x++) ast_cli_register(myclis + x); if (cfg) { v = ast_variable_browse(cfg, "general"); while(v) { if (!strcasecmp(v->name, "autoanswer")) autoanswer = ast_true(v->value); else if (!strcasecmp(v->name, "silencesuppression")) silencesuppression = ast_true(v->value); else if (!strcasecmp(v->name, "silencethreshold")) silencethreshold = atoi(v->value); else if (!strcasecmp(v->name, "context")) strncpy(context, v->value, sizeof(context)-1); else if (!strcasecmp(v->name, "language")) strncpy(language, v->value, sizeof(language)-1); else if (!strcasecmp(v->name, "extension")) strncpy(exten, v->value, sizeof(exten)-1); else if (!strcasecmp(v->name, "device")) strncpy(devname, v->value, sizeof(devname)-1); v=v->next; } ast_destroy(cfg); } pthread_create(&sthread, NULL, sound_thread, NULL); return 0; } int unload_module() { int x; for (x=0;x<sizeof(myclis)/sizeof(struct ast_cli_entry); x++) ast_cli_unregister(myclis + x); close(sounddev); if (cmd[0] > 0) { close(cmd[0]); close(cmd[1]); } if (sndcmd[0] > 0) { close(sndcmd[0]); close(sndcmd[1]); } if (alsa.owner) ast_softhangup(alsa.owner); if (alsa.owner) return -1; return 0; } char *description() { return desc; } int usecount() { int res; ast_pthread_mutex_lock(&usecnt_lock); res = usecnt; ast_pthread_mutex_unlock(&usecnt_lock); return res; } char *key() { return ASTERISK_GPL_KEY; }