00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 #include "asterisk.h"
00035
00036 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 118953 $")
00037
00038 #include <unistd.h>
00039 #include <fcntl.h>
00040 #include <errno.h>
00041 #include <sys/ioctl.h>
00042 #include <sys/time.h>
00043 #include <string.h>
00044 #include <stdlib.h>
00045 #include <stdio.h>
00046
00047 #define ALSA_PCM_NEW_HW_PARAMS_API
00048 #define ALSA_PCM_NEW_SW_PARAMS_API
00049 #include <alsa/asoundlib.h>
00050
00051 #include "asterisk/frame.h"
00052 #include "asterisk/logger.h"
00053 #include "asterisk/channel.h"
00054 #include "asterisk/module.h"
00055 #include "asterisk/options.h"
00056 #include "asterisk/pbx.h"
00057 #include "asterisk/config.h"
00058 #include "asterisk/cli.h"
00059 #include "asterisk/utils.h"
00060 #include "asterisk/causes.h"
00061 #include "asterisk/endian.h"
00062 #include "asterisk/stringfields.h"
00063 #include "asterisk/abstract_jb.h"
00064 #include "asterisk/musiconhold.h"
00065
00066 #include "busy.h"
00067 #include "ringtone.h"
00068 #include "ring10.h"
00069 #include "answer.h"
00070
00071 #ifdef ALSA_MONITOR
00072 #include "alsa-monitor.h"
00073 #endif
00074
00075
00076 static struct ast_jb_conf default_jbconf = {
00077 .flags = 0,
00078 .max_size = -1,
00079 .resync_threshold = -1,
00080 .impl = ""
00081 };
00082 static struct ast_jb_conf global_jbconf;
00083
00084 #define DEBUG 0
00085
00086 #define ALSA_INDEV "default"
00087 #define ALSA_OUTDEV "default"
00088 #define DESIRED_RATE 8000
00089
00090
00091 #define FRAME_SIZE 160
00092 #define PERIOD_FRAMES 80
00093
00094
00095
00096
00097 #define BUFFER_FMT ((buffersize * 10) << 16) | (0x0006);
00098
00099
00100 #define MIN_SWITCH_TIME 600
00101
00102 #if __BYTE_ORDER == __LITTLE_ENDIAN
00103 static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
00104 #else
00105 static snd_pcm_format_t format = SND_PCM_FORMAT_S16_BE;
00106 #endif
00107
00108 static char indevname[50] = ALSA_INDEV;
00109 static char outdevname[50] = ALSA_OUTDEV;
00110
00111 #if 0
00112 static struct timeval lasttime;
00113 #endif
00114
00115 static int silencesuppression = 0;
00116 static int silencethreshold = 1000;
00117
00118 AST_MUTEX_DEFINE_STATIC(alsalock);
00119
00120 static const char tdesc[] = "ALSA Console Channel Driver";
00121 static const char config[] = "alsa.conf";
00122
00123 static char context[AST_MAX_CONTEXT] = "default";
00124 static char language[MAX_LANGUAGE] = "";
00125 static char exten[AST_MAX_EXTENSION] = "s";
00126 static char mohinterpret[MAX_MUSICCLASS];
00127
00128 static int hookstate = 0;
00129
00130 static short silence[FRAME_SIZE] = { 0, };
00131
00132 struct sound {
00133 int ind;
00134 short *data;
00135 int datalen;
00136 int samplen;
00137 int silencelen;
00138 int repeat;
00139 };
00140
00141 static struct sound sounds[] = {
00142 {AST_CONTROL_RINGING, ringtone, sizeof(ringtone) / 2, 16000, 32000, 1},
00143 {AST_CONTROL_BUSY, busy, sizeof(busy) / 2, 4000, 4000, 1},
00144 {AST_CONTROL_CONGESTION, busy, sizeof(busy) / 2, 2000, 2000, 1},
00145 {AST_CONTROL_RING, ring10, sizeof(ring10) / 2, 16000, 32000, 1},
00146 {AST_CONTROL_ANSWER, answer, sizeof(answer) / 2, 2200, 0, 0},
00147 };
00148
00149
00150 static int sndcmd[2];
00151
00152 static struct chan_alsa_pvt {
00153
00154
00155 struct ast_channel *owner;
00156 char exten[AST_MAX_EXTENSION];
00157 char context[AST_MAX_CONTEXT];
00158 #if 0
00159 snd_pcm_t *card;
00160 #endif
00161 snd_pcm_t *icard, *ocard;
00162
00163 } alsa;
00164
00165
00166
00167
00168
00169 pthread_t sthread;
00170
00171 #define MAX_BUFFER_SIZE 100
00172
00173
00174 static int readdev = -1;
00175 static int writedev = -1;
00176
00177 static int autoanswer = 1;
00178
00179 static int cursound = -1;
00180 static int sampsent = 0;
00181 static int silencelen = 0;
00182 static int offset = 0;
00183 static int nosound = 0;
00184
00185
00186 static struct ast_channel *alsa_request(const char *type, int format, void *data, int *cause);
00187 static int alsa_digit(struct ast_channel *c, char digit, unsigned int duration);
00188 static int alsa_text(struct ast_channel *c, const char *text);
00189 static int alsa_hangup(struct ast_channel *c);
00190 static int alsa_answer(struct ast_channel *c);
00191 static struct ast_frame *alsa_read(struct ast_channel *chan);
00192 static int alsa_call(struct ast_channel *c, char *dest, int timeout);
00193 static int alsa_write(struct ast_channel *chan, struct ast_frame *f);
00194 static int alsa_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
00195 static int alsa_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
00196
00197 static const struct ast_channel_tech alsa_tech = {
00198 .type = "Console",
00199 .description = tdesc,
00200 .capabilities = AST_FORMAT_SLINEAR,
00201 .requester = alsa_request,
00202 .send_digit_end = alsa_digit,
00203 .send_text = alsa_text,
00204 .hangup = alsa_hangup,
00205 .answer = alsa_answer,
00206 .read = alsa_read,
00207 .call = alsa_call,
00208 .write = alsa_write,
00209 .indicate = alsa_indicate,
00210 .fixup = alsa_fixup,
00211 };
00212
00213 static int send_sound(void)
00214 {
00215 short myframe[FRAME_SIZE];
00216 int total = FRAME_SIZE;
00217 short *frame = NULL;
00218 int amt = 0, res, myoff;
00219 snd_pcm_state_t state;
00220
00221 if (cursound == -1)
00222 return 0;
00223
00224 res = total;
00225 if (sampsent < sounds[cursound].samplen) {
00226 myoff = 0;
00227 while (total) {
00228 amt = total;
00229 if (amt > (sounds[cursound].datalen - offset))
00230 amt = sounds[cursound].datalen - offset;
00231 memcpy(myframe + myoff, sounds[cursound].data + offset, amt * 2);
00232 total -= amt;
00233 offset += amt;
00234 sampsent += amt;
00235 myoff += amt;
00236 if (offset >= sounds[cursound].datalen)
00237 offset = 0;
00238 }
00239
00240 if (sampsent >= sounds[cursound].samplen)
00241 silencelen = sounds[cursound].silencelen;
00242 frame = myframe;
00243 } else {
00244 if (silencelen > 0) {
00245 frame = silence;
00246 silencelen -= res;
00247 } else {
00248 if (sounds[cursound].repeat) {
00249
00250 sampsent = 0;
00251 offset = 0;
00252 } else {
00253 cursound = -1;
00254 nosound = 0;
00255 }
00256 return 0;
00257 }
00258 }
00259
00260 if (res == 0 || !frame)
00261 return 0;
00262
00263 #ifdef ALSA_MONITOR
00264 alsa_monitor_write((char *) frame, res * 2);
00265 #endif
00266 state = snd_pcm_state(alsa.ocard);
00267 if (state == SND_PCM_STATE_XRUN)
00268 snd_pcm_prepare(alsa.ocard);
00269 res = snd_pcm_writei(alsa.ocard, frame, res);
00270 if (res > 0)
00271 return 0;
00272 return 0;
00273 }
00274
00275 static void *sound_thread(void *unused)
00276 {
00277 fd_set rfds;
00278 fd_set wfds;
00279 int max, res;
00280
00281 for (;;) {
00282 FD_ZERO(&rfds);
00283 FD_ZERO(&wfds);
00284 max = sndcmd[0];
00285 FD_SET(sndcmd[0], &rfds);
00286 if (cursound > -1) {
00287 FD_SET(writedev, &wfds);
00288 if (writedev > max)
00289 max = writedev;
00290 }
00291 #ifdef ALSA_MONITOR
00292 if (!alsa.owner) {
00293 FD_SET(readdev, &rfds);
00294 if (readdev > max)
00295 max = readdev;
00296 }
00297 #endif
00298 res = ast_select(max + 1, &rfds, &wfds, NULL, NULL);
00299 if (res < 1) {
00300 ast_log(LOG_WARNING, "select failed: %s\n", strerror(errno));
00301 continue;
00302 }
00303 #ifdef ALSA_MONITOR
00304 if (FD_ISSET(readdev, &rfds)) {
00305
00306 snd_pcm_state_t state;
00307 short buf[FRAME_SIZE];
00308 int r;
00309
00310 state = snd_pcm_state(alsa.ocard);
00311 if (state == SND_PCM_STATE_XRUN) {
00312 snd_pcm_prepare(alsa.ocard);
00313 }
00314 r = snd_pcm_readi(alsa.icard, buf, FRAME_SIZE);
00315 if (r == -EPIPE) {
00316 #if DEBUG
00317 ast_log(LOG_ERROR, "XRUN read\n");
00318 #endif
00319 snd_pcm_prepare(alsa.icard);
00320 } else if (r == -ESTRPIPE) {
00321 ast_log(LOG_ERROR, "-ESTRPIPE\n");
00322 snd_pcm_prepare(alsa.icard);
00323 } else if (r < 0) {
00324 ast_log(LOG_ERROR, "Read error: %s\n", snd_strerror(r));
00325 } else
00326 alsa_monitor_read((char *) buf, r * 2);
00327 }
00328 #endif
00329 if (FD_ISSET(sndcmd[0], &rfds)) {
00330 read(sndcmd[0], &cursound, sizeof(cursound));
00331 silencelen = 0;
00332 offset = 0;
00333 sampsent = 0;
00334 }
00335 if (FD_ISSET(writedev, &wfds))
00336 if (send_sound())
00337 ast_log(LOG_WARNING, "Failed to write sound\n");
00338 }
00339
00340 return NULL;
00341 }
00342
00343 static snd_pcm_t *alsa_card_init(char *dev, snd_pcm_stream_t stream)
00344 {
00345 int err;
00346 int direction;
00347 snd_pcm_t *handle = NULL;
00348 snd_pcm_hw_params_t *hwparams = NULL;
00349 snd_pcm_sw_params_t *swparams = NULL;
00350 struct pollfd pfd;
00351 snd_pcm_uframes_t period_size = PERIOD_FRAMES * 4;
00352
00353 snd_pcm_uframes_t buffer_size = 0;
00354
00355 unsigned int rate = DESIRED_RATE;
00356 #if 0
00357 unsigned int per_min = 1;
00358 #endif
00359
00360 snd_pcm_uframes_t start_threshold, stop_threshold;
00361
00362 err = snd_pcm_open(&handle, dev, stream, SND_PCM_NONBLOCK);
00363 if (err < 0) {
00364 ast_log(LOG_ERROR, "snd_pcm_open failed: %s\n", snd_strerror(err));
00365 return NULL;
00366 } else
00367 ast_log(LOG_DEBUG, "Opening device %s in %s mode\n", dev, (stream == SND_PCM_STREAM_CAPTURE) ? "read" : "write");
00368
00369 hwparams = alloca(snd_pcm_hw_params_sizeof());
00370 memset(hwparams, 0, snd_pcm_hw_params_sizeof());
00371 snd_pcm_hw_params_any(handle, hwparams);
00372
00373 err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
00374 if (err < 0)
00375 ast_log(LOG_ERROR, "set_access failed: %s\n", snd_strerror(err));
00376
00377 err = snd_pcm_hw_params_set_format(handle, hwparams, format);
00378 if (err < 0)
00379 ast_log(LOG_ERROR, "set_format failed: %s\n", snd_strerror(err));
00380
00381 err = snd_pcm_hw_params_set_channels(handle, hwparams, 1);
00382 if (err < 0)
00383 ast_log(LOG_ERROR, "set_channels failed: %s\n", snd_strerror(err));
00384
00385 direction = 0;
00386 err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, &direction);
00387 if (rate != DESIRED_RATE)
00388 ast_log(LOG_WARNING, "Rate not correct, requested %d, got %d\n", DESIRED_RATE, rate);
00389
00390 direction = 0;
00391 err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, &direction);
00392 if (err < 0)
00393 ast_log(LOG_ERROR, "period_size(%ld frames) is bad: %s\n", period_size, snd_strerror(err));
00394 else
00395 ast_log(LOG_DEBUG, "Period size is %d\n", err);
00396
00397 buffer_size = 4096 * 2;
00398 err = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &buffer_size);
00399 if (err < 0)
00400 ast_log(LOG_WARNING, "Problem setting buffer size of %ld: %s\n", buffer_size, snd_strerror(err));
00401 else
00402 ast_log(LOG_DEBUG, "Buffer size is set to %d frames\n", err);
00403
00404 #if 0
00405 direction = 0;
00406 err = snd_pcm_hw_params_set_periods_min(handle, hwparams, &per_min, &direction);
00407 if (err < 0)
00408 ast_log(LOG_ERROR, "periods_min: %s\n", snd_strerror(err));
00409
00410 err = snd_pcm_hw_params_set_periods_max(handle, hwparams, &per_max, 0);
00411 if (err < 0)
00412 ast_log(LOG_ERROR, "periods_max: %s\n", snd_strerror(err));
00413 #endif
00414
00415 err = snd_pcm_hw_params(handle, hwparams);
00416 if (err < 0)
00417 ast_log(LOG_ERROR, "Couldn't set the new hw params: %s\n", snd_strerror(err));
00418
00419 swparams = alloca(snd_pcm_sw_params_sizeof());
00420 memset(swparams, 0, snd_pcm_sw_params_sizeof());
00421 snd_pcm_sw_params_current(handle, swparams);
00422
00423 #if 1
00424 if (stream == SND_PCM_STREAM_PLAYBACK)
00425 start_threshold = period_size;
00426 else
00427 start_threshold = 1;
00428
00429 err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold);
00430 if (err < 0)
00431 ast_log(LOG_ERROR, "start threshold: %s\n", snd_strerror(err));
00432 #endif
00433
00434 #if 1
00435 if (stream == SND_PCM_STREAM_PLAYBACK)
00436 stop_threshold = buffer_size;
00437 else
00438 stop_threshold = buffer_size;
00439
00440 err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold);
00441 if (err < 0)
00442 ast_log(LOG_ERROR, "stop threshold: %s\n", snd_strerror(err));
00443 #endif
00444 #if 0
00445 err = snd_pcm_sw_params_set_xfer_align(handle, swparams, PERIOD_FRAMES);
00446 if (err < 0)
00447 ast_log(LOG_ERROR, "Unable to set xfer alignment: %s\n", snd_strerror(err));
00448 #endif
00449
00450 #if 0
00451 err = snd_pcm_sw_params_set_silence_threshold(handle, swparams, silencethreshold);
00452 if (err < 0)
00453 ast_log(LOG_ERROR, "Unable to set silence threshold: %s\n", snd_strerror(err));
00454 #endif
00455 err = snd_pcm_sw_params(handle, swparams);
00456 if (err < 0)
00457 ast_log(LOG_ERROR, "sw_params: %s\n", snd_strerror(err));
00458
00459 err = snd_pcm_poll_descriptors_count(handle);
00460 if (err <= 0)
00461 ast_log(LOG_ERROR, "Unable to get a poll descriptors count, error is %s\n", snd_strerror(err));
00462 if (err != 1)
00463 ast_log(LOG_DEBUG, "Can't handle more than one device\n");
00464
00465 snd_pcm_poll_descriptors(handle, &pfd, err);
00466 ast_log(LOG_DEBUG, "Acquired fd %d from the poll descriptor\n", pfd.fd);
00467
00468 if (stream == SND_PCM_STREAM_CAPTURE)
00469 readdev = pfd.fd;
00470 else
00471 writedev = pfd.fd;
00472
00473 return handle;
00474 }
00475
00476 static int soundcard_init(void)
00477 {
00478 alsa.icard = alsa_card_init(indevname, SND_PCM_STREAM_CAPTURE);
00479 alsa.ocard = alsa_card_init(outdevname, SND_PCM_STREAM_PLAYBACK);
00480
00481 if (!alsa.icard || !alsa.ocard) {
00482 ast_log(LOG_ERROR, "Problem opening alsa I/O devices\n");
00483 return -1;
00484 }
00485
00486 return readdev;
00487 }
00488
00489 static int alsa_digit(struct ast_channel *c, char digit, unsigned int duration)
00490 {
00491 ast_mutex_lock(&alsalock);
00492 ast_verbose(" << Console Received digit %c of duration %u ms >> \n",
00493 digit, duration);
00494 ast_mutex_unlock(&alsalock);
00495 return 0;
00496 }
00497
00498 static int alsa_text(struct ast_channel *c, const char *text)
00499 {
00500 ast_mutex_lock(&alsalock);
00501 ast_verbose(" << Console Received text %s >> \n", text);
00502 ast_mutex_unlock(&alsalock);
00503 return 0;
00504 }
00505
00506 static void grab_owner(void)
00507 {
00508 while (alsa.owner && ast_mutex_trylock(&alsa.owner->lock)) {
00509 DEADLOCK_AVOIDANCE(&alsalock);
00510 }
00511 }
00512
00513 static int alsa_call(struct ast_channel *c, char *dest, int timeout)
00514 {
00515 int res = 3;
00516 struct ast_frame f = { AST_FRAME_CONTROL };
00517 ast_mutex_lock(&alsalock);
00518 ast_verbose(" << Call placed to '%s' on console >> \n", dest);
00519 if (autoanswer) {
00520 ast_verbose(" << Auto-answered >> \n");
00521 grab_owner();
00522 if (alsa.owner) {
00523 f.subclass = AST_CONTROL_ANSWER;
00524 ast_queue_frame(alsa.owner, &f);
00525 ast_mutex_unlock(&alsa.owner->lock);
00526 }
00527 } else {
00528 ast_verbose(" << Type 'answer' to answer, or use 'autoanswer' for future calls >> \n");
00529 grab_owner();
00530 if (alsa.owner) {
00531 f.subclass = AST_CONTROL_RINGING;
00532 ast_queue_frame(alsa.owner, &f);
00533 ast_mutex_unlock(&alsa.owner->lock);
00534 }
00535 write(sndcmd[1], &res, sizeof(res));
00536 }
00537 snd_pcm_prepare(alsa.icard);
00538 snd_pcm_start(alsa.icard);
00539 ast_mutex_unlock(&alsalock);
00540 return 0;
00541 }
00542
00543 static void answer_sound(void)
00544 {
00545 int res;
00546 nosound = 1;
00547 res = 4;
00548 write(sndcmd[1], &res, sizeof(res));
00549
00550 }
00551
00552 static int alsa_answer(struct ast_channel *c)
00553 {
00554 ast_mutex_lock(&alsalock);
00555 ast_verbose(" << Console call has been answered >> \n");
00556 answer_sound();
00557 ast_setstate(c, AST_STATE_UP);
00558 cursound = -1;
00559 snd_pcm_prepare(alsa.icard);
00560 snd_pcm_start(alsa.icard);
00561 ast_mutex_unlock(&alsalock);
00562 return 0;
00563 }
00564
00565 static int alsa_hangup(struct ast_channel *c)
00566 {
00567 int res;
00568 ast_mutex_lock(&alsalock);
00569 cursound = -1;
00570 c->tech_pvt = NULL;
00571 alsa.owner = NULL;
00572 ast_verbose(" << Hangup on console >> \n");
00573 ast_module_unref(ast_module_info->self);
00574 if (hookstate) {
00575 hookstate = 0;
00576 if (!autoanswer) {
00577
00578 res = 2;
00579 write(sndcmd[1], &res, sizeof(res));
00580 }
00581 }
00582 snd_pcm_drop(alsa.icard);
00583 ast_mutex_unlock(&alsalock);
00584 return 0;
00585 }
00586
00587 static int alsa_write(struct ast_channel *chan, struct ast_frame *f)
00588 {
00589 static char sizbuf[8000];
00590 static int sizpos = 0;
00591 int len = sizpos;
00592 int pos;
00593 int res = 0;
00594
00595 snd_pcm_state_t state;
00596
00597
00598 if (nosound)
00599 return 0;
00600
00601 ast_mutex_lock(&alsalock);
00602
00603 if (cursound != -1) {
00604 snd_pcm_drop(alsa.ocard);
00605 snd_pcm_prepare(alsa.ocard);
00606 cursound = -1;
00607 }
00608
00609
00610
00611 if (f->datalen > sizeof(sizbuf) - sizpos) {
00612 ast_log(LOG_WARNING, "Frame too large\n");
00613 res = -1;
00614 } else {
00615 memcpy(sizbuf + sizpos, f->data, f->datalen);
00616 len += f->datalen;
00617 pos = 0;
00618 #ifdef ALSA_MONITOR
00619 alsa_monitor_write(sizbuf, len);
00620 #endif
00621 state = snd_pcm_state(alsa.ocard);
00622 if (state == SND_PCM_STATE_XRUN)
00623 snd_pcm_prepare(alsa.ocard);
00624 res = snd_pcm_writei(alsa.ocard, sizbuf, len / 2);
00625 if (res == -EPIPE) {
00626 #if DEBUG
00627 ast_log(LOG_DEBUG, "XRUN write\n");
00628 #endif
00629 snd_pcm_prepare(alsa.ocard);
00630 res = snd_pcm_writei(alsa.ocard, sizbuf, len / 2);
00631 if (res != len / 2) {
00632 ast_log(LOG_ERROR, "Write error: %s\n", snd_strerror(res));
00633 res = -1;
00634 } else if (res < 0) {
00635 ast_log(LOG_ERROR, "Write error %s\n", snd_strerror(res));
00636 res = -1;
00637 }
00638 } else {
00639 if (res == -ESTRPIPE)
00640 ast_log(LOG_ERROR, "You've got some big problems\n");
00641 else if (res < 0)
00642 ast_log(LOG_NOTICE, "Error %d on write\n", res);
00643 }
00644 }
00645 ast_mutex_unlock(&alsalock);
00646 if (res > 0)
00647 res = 0;
00648 return res;
00649 }
00650
00651
00652 static struct ast_frame *alsa_read(struct ast_channel *chan)
00653 {
00654 static struct ast_frame f;
00655 static short __buf[FRAME_SIZE + AST_FRIENDLY_OFFSET / 2];
00656 short *buf;
00657 static int readpos = 0;
00658 static int left = FRAME_SIZE;
00659 snd_pcm_state_t state;
00660 int r = 0;
00661 int off = 0;
00662
00663 ast_mutex_lock(&alsalock);
00664
00665 f.frametype = AST_FRAME_NULL;
00666 f.subclass = 0;
00667 f.samples = 0;
00668 f.datalen = 0;
00669 f.data = NULL;
00670 f.offset = 0;
00671 f.src = "Console";
00672 f.mallocd = 0;
00673 f.delivery.tv_sec = 0;
00674 f.delivery.tv_usec = 0;
00675
00676 state = snd_pcm_state(alsa.icard);
00677 if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING)) {
00678 snd_pcm_prepare(alsa.icard);
00679 }
00680
00681 buf = __buf + AST_FRIENDLY_OFFSET / 2;
00682
00683 r = snd_pcm_readi(alsa.icard, buf + readpos, left);
00684 if (r == -EPIPE) {
00685 #if DEBUG
00686 ast_log(LOG_ERROR, "XRUN read\n");
00687 #endif
00688 snd_pcm_prepare(alsa.icard);
00689 } else if (r == -ESTRPIPE) {
00690 ast_log(LOG_ERROR, "-ESTRPIPE\n");
00691 snd_pcm_prepare(alsa.icard);
00692 } else if (r < 0) {
00693 ast_log(LOG_ERROR, "Read error: %s\n", snd_strerror(r));
00694 } else if (r >= 0) {
00695 off -= r;
00696 }
00697
00698 readpos += r;
00699 left -= r;
00700
00701 if (readpos >= FRAME_SIZE) {
00702
00703 readpos = 0;
00704 left = FRAME_SIZE;
00705 if (chan->_state != AST_STATE_UP) {
00706
00707 ast_mutex_unlock(&alsalock);
00708 return &f;
00709 }
00710 f.frametype = AST_FRAME_VOICE;
00711 f.subclass = AST_FORMAT_SLINEAR;
00712 f.samples = FRAME_SIZE;
00713 f.datalen = FRAME_SIZE * 2;
00714 f.data = buf;
00715 f.offset = AST_FRIENDLY_OFFSET;
00716 f.src = "Console";
00717 f.mallocd = 0;
00718 #ifdef ALSA_MONITOR
00719 alsa_monitor_read((char *) buf, FRAME_SIZE * 2);
00720 #endif
00721
00722 }
00723 ast_mutex_unlock(&alsalock);
00724 return &f;
00725 }
00726
00727 static int alsa_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
00728 {
00729 struct chan_alsa_pvt *p = newchan->tech_pvt;
00730 ast_mutex_lock(&alsalock);
00731 p->owner = newchan;
00732 ast_mutex_unlock(&alsalock);
00733 return 0;
00734 }
00735
00736 static int alsa_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen)
00737 {
00738 int res = 0;
00739
00740 ast_mutex_lock(&alsalock);
00741
00742 switch (cond) {
00743 case AST_CONTROL_BUSY:
00744 res = 1;
00745 break;
00746 case AST_CONTROL_CONGESTION:
00747 res = 2;
00748 break;
00749 case AST_CONTROL_RINGING:
00750 case AST_CONTROL_PROGRESS:
00751 break;
00752 case -1:
00753 res = -1;
00754 break;
00755 case AST_CONTROL_VIDUPDATE:
00756 res = -1;
00757 break;
00758 case AST_CONTROL_HOLD:
00759 ast_verbose(" << Console Has Been Placed on Hold >> \n");
00760 ast_moh_start(chan, data, mohinterpret);
00761 break;
00762 case AST_CONTROL_UNHOLD:
00763 ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
00764 ast_moh_stop(chan);
00765 break;
00766 case AST_CONTROL_SRCUPDATE:
00767 break;
00768 default:
00769 ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, chan->name);
00770 res = -1;
00771 }
00772
00773 if (res > -1)
00774 write(sndcmd[1], &res, sizeof(res));
00775
00776 ast_mutex_unlock(&alsalock);
00777
00778 return res;
00779 }
00780
00781 static struct ast_channel *alsa_new(struct chan_alsa_pvt *p, int state)
00782 {
00783 struct ast_channel *tmp = NULL;
00784
00785 if (!(tmp = ast_channel_alloc(1, state, 0, 0, "", p->exten, p->context, 0, "ALSA/%s", indevname)))
00786 return NULL;
00787
00788 tmp->tech = &alsa_tech;
00789 tmp->fds[0] = readdev;
00790 tmp->nativeformats = AST_FORMAT_SLINEAR;
00791 tmp->readformat = AST_FORMAT_SLINEAR;
00792 tmp->writeformat = AST_FORMAT_SLINEAR;
00793 tmp->tech_pvt = p;
00794 if (!ast_strlen_zero(p->context))
00795 ast_copy_string(tmp->context, p->context, sizeof(tmp->context));
00796 if (!ast_strlen_zero(p->exten))
00797 ast_copy_string(tmp->exten, p->exten, sizeof(tmp->exten));
00798 if (!ast_strlen_zero(language))
00799 ast_string_field_set(tmp, language, language);
00800 p->owner = tmp;
00801 ast_module_ref(ast_module_info->self);
00802 ast_jb_configure(tmp, &global_jbconf);
00803 if (state != AST_STATE_DOWN) {
00804 if (ast_pbx_start(tmp)) {
00805 ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
00806 ast_hangup(tmp);
00807 tmp = NULL;
00808 }
00809 }
00810
00811 return tmp;
00812 }
00813
00814 static struct ast_channel *alsa_request(const char *type, int format, void *data, int *cause)
00815 {
00816 int oldformat = format;
00817 struct ast_channel *tmp = NULL;
00818
00819 format &= AST_FORMAT_SLINEAR;
00820 if (!format) {
00821 ast_log(LOG_NOTICE, "Asked to get a channel of format '%d'\n", oldformat);
00822 return NULL;
00823 }
00824
00825 ast_mutex_lock(&alsalock);
00826
00827 if (alsa.owner) {
00828 ast_log(LOG_NOTICE, "Already have a call on the ALSA channel\n");
00829 *cause = AST_CAUSE_BUSY;
00830 } else if (!(tmp = alsa_new(&alsa, AST_STATE_DOWN)))
00831 ast_log(LOG_WARNING, "Unable to create new ALSA channel\n");
00832
00833 ast_mutex_unlock(&alsalock);
00834
00835 return tmp;
00836 }
00837
00838 static int console_autoanswer_deprecated(int fd, int argc, char *argv[])
00839 {
00840 int res = RESULT_SUCCESS;
00841
00842 if ((argc != 1) && (argc != 2))
00843 return RESULT_SHOWUSAGE;
00844
00845 ast_mutex_lock(&alsalock);
00846
00847 if (argc == 1) {
00848 ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off");
00849 } else {
00850 if (!strcasecmp(argv[1], "on"))
00851 autoanswer = -1;
00852 else if (!strcasecmp(argv[1], "off"))
00853 autoanswer = 0;
00854 else
00855 res = RESULT_SHOWUSAGE;
00856 }
00857
00858 ast_mutex_unlock(&alsalock);
00859
00860 return res;
00861 }
00862
00863 static int console_autoanswer(int fd, int argc, char *argv[])
00864 {
00865 int res = RESULT_SUCCESS;;
00866 if ((argc != 2) && (argc != 3))
00867 return RESULT_SHOWUSAGE;
00868 ast_mutex_lock(&alsalock);
00869 if (argc == 2) {
00870 ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off");
00871 } else {
00872 if (!strcasecmp(argv[2], "on"))
00873 autoanswer = -1;
00874 else if (!strcasecmp(argv[2], "off"))
00875 autoanswer = 0;
00876 else
00877 res = RESULT_SHOWUSAGE;
00878 }
00879 ast_mutex_unlock(&alsalock);
00880 return res;
00881 }
00882
00883 static char *autoanswer_complete(const char *line, const char *word, int pos, int state)
00884 {
00885 #ifndef MIN
00886 #define MIN(a,b) ((a) < (b) ? (a) : (b))
00887 #endif
00888 switch (state) {
00889 case 0:
00890 if (!ast_strlen_zero(word) && !strncasecmp(word, "on", MIN(strlen(word), 2)))
00891 return ast_strdup("on");
00892 case 1:
00893 if (!ast_strlen_zero(word) && !strncasecmp(word, "off", MIN(strlen(word), 3)))
00894 return ast_strdup("off");
00895 default:
00896 return NULL;
00897 }
00898 return NULL;
00899 }
00900
00901 static const char autoanswer_usage[] =
00902 "Usage: console autoanswer [on|off]\n"
00903 " Enables or disables autoanswer feature. If used without\n"
00904 " argument, displays the current on/off status of autoanswer.\n"
00905 " The default value of autoanswer is in 'alsa.conf'.\n";
00906
00907 static int console_answer_deprecated(int fd, int argc, char *argv[])
00908 {
00909 int res = RESULT_SUCCESS;
00910
00911 if (argc != 1)
00912 return RESULT_SHOWUSAGE;
00913
00914 ast_mutex_lock(&alsalock);
00915
00916 if (!alsa.owner) {
00917 ast_cli(fd, "No one is calling us\n");
00918 res = RESULT_FAILURE;
00919 } else {
00920 hookstate = 1;
00921 cursound = -1;
00922 grab_owner();
00923 if (alsa.owner) {
00924 struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
00925 ast_queue_frame(alsa.owner, &f);
00926 ast_mutex_unlock(&alsa.owner->lock);
00927 }
00928 answer_sound();
00929 }
00930
00931 snd_pcm_prepare(alsa.icard);
00932 snd_pcm_start(alsa.icard);
00933
00934 ast_mutex_unlock(&alsalock);
00935
00936 return RESULT_SUCCESS;
00937 }
00938
00939 static int console_answer(int fd, int argc, char *argv[])
00940 {
00941 int res = RESULT_SUCCESS;
00942
00943 if (argc != 2)
00944 return RESULT_SHOWUSAGE;
00945
00946 ast_mutex_lock(&alsalock);
00947
00948 if (!alsa.owner) {
00949 ast_cli(fd, "No one is calling us\n");
00950 res = RESULT_FAILURE;
00951 } else {
00952 hookstate = 1;
00953 cursound = -1;
00954 grab_owner();
00955 if (alsa.owner) {
00956 struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
00957 ast_queue_frame(alsa.owner, &f);
00958 ast_mutex_unlock(&alsa.owner->lock);
00959 }
00960 answer_sound();
00961 }
00962
00963 snd_pcm_prepare(alsa.icard);
00964 snd_pcm_start(alsa.icard);
00965
00966 ast_mutex_unlock(&alsalock);
00967
00968 return RESULT_SUCCESS;
00969 }
00970
00971 static char sendtext_usage[] =
00972 "Usage: console send text <message>\n"
00973 " Sends a text message for display on the remote terminal.\n";
00974
00975 static int console_sendtext_deprecated(int fd, int argc, char *argv[])
00976 {
00977 int tmparg = 2;
00978 int res = RESULT_SUCCESS;
00979
00980 if (argc < 2)
00981 return RESULT_SHOWUSAGE;
00982
00983 ast_mutex_lock(&alsalock);
00984
00985 if (!alsa.owner) {
00986 ast_cli(fd, "No one is calling us\n");
00987 res = RESULT_FAILURE;
00988 } else {
00989 struct ast_frame f = { AST_FRAME_TEXT, 0 };
00990 char text2send[256] = "";
00991 text2send[0] = '\0';
00992 while (tmparg < argc) {
00993 strncat(text2send, argv[tmparg++], sizeof(text2send) - strlen(text2send) - 1);
00994 strncat(text2send, " ", sizeof(text2send) - strlen(text2send) - 1);
00995 }
00996 text2send[strlen(text2send) - 1] = '\n';
00997 f.data = text2send;
00998 f.datalen = strlen(text2send) + 1;
00999 grab_owner();
01000 if (alsa.owner) {
01001 ast_queue_frame(alsa.owner, &f);
01002 f.frametype = AST_FRAME_CONTROL;
01003 f.subclass = AST_CONTROL_ANSWER;
01004 f.data = NULL;
01005 f.datalen = 0;
01006 ast_queue_frame(alsa.owner, &f);
01007 ast_mutex_unlock(&alsa.owner->lock);
01008 }
01009 }
01010
01011 ast_mutex_unlock(&alsalock);
01012
01013 return res;
01014 }
01015
01016 static int console_sendtext(int fd, int argc, char *argv[])
01017 {
01018 int tmparg = 3;
01019 int res = RESULT_SUCCESS;
01020
01021 if (argc < 3)
01022 return RESULT_SHOWUSAGE;
01023
01024 ast_mutex_lock(&alsalock);
01025
01026 if (!alsa.owner) {
01027 ast_cli(fd, "No one is calling us\n");
01028 res = RESULT_FAILURE;
01029 } else {
01030 struct ast_frame f = { AST_FRAME_TEXT, 0 };
01031 char text2send[256] = "";
01032 text2send[0] = '\0';
01033 while (tmparg < argc) {
01034 strncat(text2send, argv[tmparg++], sizeof(text2send) - strlen(text2send) - 1);
01035 strncat(text2send, " ", sizeof(text2send) - strlen(text2send) - 1);
01036 }
01037 text2send[strlen(text2send) - 1] = '\n';
01038 f.data = text2send;
01039 f.datalen = strlen(text2send) + 1;
01040 grab_owner();
01041 if (alsa.owner) {
01042 ast_queue_frame(alsa.owner, &f);
01043 f.frametype = AST_FRAME_CONTROL;
01044 f.subclass = AST_CONTROL_ANSWER;
01045 f.data = NULL;
01046 f.datalen = 0;
01047 ast_queue_frame(alsa.owner, &f);
01048 ast_mutex_unlock(&alsa.owner->lock);
01049 }
01050 }
01051
01052 ast_mutex_unlock(&alsalock);
01053
01054 return res;
01055 }
01056
01057 static char answer_usage[] =
01058 "Usage: console answer\n"
01059 " Answers an incoming call on the console (ALSA) channel.\n";
01060
01061 static int console_hangup_deprecated(int fd, int argc, char *argv[])
01062 {
01063 int res = RESULT_SUCCESS;
01064
01065 if (argc != 1)
01066 return RESULT_SHOWUSAGE;
01067
01068 cursound = -1;
01069
01070 ast_mutex_lock(&alsalock);
01071
01072 if (!alsa.owner && !hookstate) {
01073 ast_cli(fd, "No call to hangup up\n");
01074 res = RESULT_FAILURE;
01075 } else {
01076 hookstate = 0;
01077 grab_owner();
01078 if (alsa.owner) {
01079 ast_queue_hangup(alsa.owner);
01080 ast_mutex_unlock(&alsa.owner->lock);
01081 }
01082 }
01083
01084 ast_mutex_unlock(&alsalock);
01085
01086 return res;
01087 }
01088
01089 static int console_hangup(int fd, int argc, char *argv[])
01090 {
01091 int res = RESULT_SUCCESS;
01092
01093 if (argc != 2)
01094 return RESULT_SHOWUSAGE;
01095
01096 cursound = -1;
01097
01098 ast_mutex_lock(&alsalock);
01099
01100 if (!alsa.owner && !hookstate) {
01101 ast_cli(fd, "No call to hangup up\n");
01102 res = RESULT_FAILURE;
01103 } else {
01104 hookstate = 0;
01105 grab_owner();
01106 if (alsa.owner) {
01107 ast_queue_hangup(alsa.owner);
01108 ast_mutex_unlock(&alsa.owner->lock);
01109 }
01110 }
01111
01112 ast_mutex_unlock(&alsalock);
01113
01114 return res;
01115 }
01116
01117 static char hangup_usage[] =
01118 "Usage: console hangup\n"
01119 " Hangs up any call currently placed on the console.\n";
01120
01121 static int console_dial_deprecated(int fd, int argc, char *argv[])
01122 {
01123 char tmp[256], *tmp2;
01124 char *mye, *myc;
01125 char *d;
01126 int res = RESULT_SUCCESS;
01127
01128 if ((argc != 1) && (argc != 2))
01129 return RESULT_SHOWUSAGE;
01130
01131 ast_mutex_lock(&alsalock);
01132
01133 if (alsa.owner) {
01134 if (argc == 2) {
01135 d = argv[1];
01136 grab_owner();
01137 if (alsa.owner) {
01138 struct ast_frame f = { AST_FRAME_DTMF };
01139 while (*d) {
01140 f.subclass = *d;
01141 ast_queue_frame(alsa.owner, &f);
01142 d++;
01143 }
01144 ast_mutex_unlock(&alsa.owner->lock);
01145 }
01146 } else {
01147 ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n");
01148 res = RESULT_FAILURE;
01149 }
01150 } else {
01151 mye = exten;
01152 myc = context;
01153 if (argc == 2) {
01154 char *stringp = NULL;
01155 ast_copy_string(tmp, argv[1], sizeof(tmp));
01156 stringp = tmp;
01157 strsep(&stringp, "@");
01158 tmp2 = strsep(&stringp, "@");
01159 if (!ast_strlen_zero(tmp))
01160 mye = tmp;
01161 if (!ast_strlen_zero(tmp2))
01162 myc = tmp2;
01163 }
01164 if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
01165 ast_copy_string(alsa.exten, mye, sizeof(alsa.exten));
01166 ast_copy_string(alsa.context, myc, sizeof(alsa.context));
01167 hookstate = 1;
01168 alsa_new(&alsa, AST_STATE_RINGING);
01169 } else
01170 ast_cli(fd, "No such extension '%s' in context '%s'\n", mye, myc);
01171 }
01172
01173 ast_mutex_unlock(&alsalock);
01174
01175 return res;
01176 }
01177
01178 static int console_dial(int fd, int argc, char *argv[])
01179 {
01180 char tmp[256], *tmp2;
01181 char *mye, *myc;
01182 char *d;
01183 int res = RESULT_SUCCESS;
01184
01185 if ((argc != 2) && (argc != 3))
01186 return RESULT_SHOWUSAGE;
01187
01188 ast_mutex_lock(&alsalock);
01189
01190 if (alsa.owner) {
01191 if (argc == 3) {
01192 d = argv[2];
01193 grab_owner();
01194 if (alsa.owner) {
01195 struct ast_frame f = { AST_FRAME_DTMF };
01196 while (*d) {
01197 f.subclass = *d;
01198 ast_queue_frame(alsa.owner, &f);
01199 d++;
01200 }
01201 ast_mutex_unlock(&alsa.owner->lock);
01202 }
01203 } else {
01204 ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n");
01205 res = RESULT_FAILURE;
01206 }
01207 } else {
01208 mye = exten;
01209 myc = context;
01210 if (argc == 3) {
01211 char *stringp = NULL;
01212 ast_copy_string(tmp, argv[2], sizeof(tmp));
01213 stringp = tmp;
01214 strsep(&stringp, "@");
01215 tmp2 = strsep(&stringp, "@");
01216 if (!ast_strlen_zero(tmp))
01217 mye = tmp;
01218 if (!ast_strlen_zero(tmp2))
01219 myc = tmp2;
01220 }
01221 if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
01222 ast_copy_string(alsa.exten, mye, sizeof(alsa.exten));
01223 ast_copy_string(alsa.context, myc, sizeof(alsa.context));
01224 hookstate = 1;
01225 alsa_new(&alsa, AST_STATE_RINGING);
01226 } else
01227 ast_cli(fd, "No such extension '%s' in context '%s'\n", mye, myc);
01228 }
01229
01230 ast_mutex_unlock(&alsalock);
01231
01232 return res;
01233 }
01234
01235 static char dial_usage[] =
01236 "Usage: console dial [extension[@context]]\n"
01237 " Dials a given extension (and context if specified)\n";
01238
01239 static struct ast_cli_entry cli_alsa_answer_deprecated = {
01240 { "answer", NULL },
01241 console_answer_deprecated, NULL,
01242 NULL };
01243
01244 static struct ast_cli_entry cli_alsa_hangup_deprecated = {
01245 { "hangup", NULL },
01246 console_hangup_deprecated, NULL,
01247 NULL };
01248
01249 static struct ast_cli_entry cli_alsa_dial_deprecated = {
01250 { "dial", NULL },
01251 console_dial_deprecated, NULL,
01252 NULL };
01253
01254 static struct ast_cli_entry cli_alsa_send_text_deprecated = {
01255 { "send", "text", NULL },
01256 console_sendtext_deprecated, NULL,
01257 NULL };
01258
01259 static struct ast_cli_entry cli_alsa_autoanswer_deprecated = {
01260 { "autoanswer", NULL },
01261 console_autoanswer_deprecated, NULL,
01262 NULL, autoanswer_complete };
01263
01264 static struct ast_cli_entry cli_alsa[] = {
01265 { { "console", "answer", NULL },
01266 console_answer, "Answer an incoming console call",
01267 answer_usage, NULL, &cli_alsa_answer_deprecated },
01268
01269 { { "console", "hangup", NULL },
01270 console_hangup, "Hangup a call on the console",
01271 hangup_usage, NULL, &cli_alsa_hangup_deprecated },
01272
01273 { { "console", "dial", NULL },
01274 console_dial, "Dial an extension on the console",
01275 dial_usage, NULL, &cli_alsa_dial_deprecated },
01276
01277 { { "console", "send", "text", NULL },
01278 console_sendtext, "Send text to the remote device",
01279 sendtext_usage, NULL, &cli_alsa_send_text_deprecated },
01280
01281 { { "console", "autoanswer", NULL },
01282 console_autoanswer, "Sets/displays autoanswer",
01283 autoanswer_usage, autoanswer_complete, &cli_alsa_autoanswer_deprecated },
01284 };
01285
01286 static int load_module(void)
01287 {
01288 int res;
01289 struct ast_config *cfg;
01290 struct ast_variable *v;
01291
01292
01293 memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
01294
01295 strcpy(mohinterpret, "default");
01296
01297 if ((cfg = ast_config_load(config))) {
01298 v = ast_variable_browse(cfg, "general");
01299 for (; v; v = v->next) {
01300
01301 if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
01302 continue;
01303
01304 if (!strcasecmp(v->name, "autoanswer"))
01305 autoanswer = ast_true(v->value);
01306 else if (!strcasecmp(v->name, "silencesuppression"))
01307 silencesuppression = ast_true(v->value);
01308 else if (!strcasecmp(v->name, "silencethreshold"))
01309 silencethreshold = atoi(v->value);
01310 else if (!strcasecmp(v->name, "context"))
01311 ast_copy_string(context, v->value, sizeof(context));
01312 else if (!strcasecmp(v->name, "language"))
01313 ast_copy_string(language, v->value, sizeof(language));
01314 else if (!strcasecmp(v->name, "extension"))
01315 ast_copy_string(exten, v->value, sizeof(exten));
01316 else if (!strcasecmp(v->name, "input_device"))
01317 ast_copy_string(indevname, v->value, sizeof(indevname));
01318 else if (!strcasecmp(v->name, "output_device"))
01319 ast_copy_string(outdevname, v->value, sizeof(outdevname));
01320 else if (!strcasecmp(v->name, "mohinterpret"))
01321 ast_copy_string(mohinterpret, v->value, sizeof(mohinterpret));
01322 }
01323 ast_config_destroy(cfg);
01324 }
01325 res = pipe(sndcmd);
01326 if (res) {
01327 ast_log(LOG_ERROR, "Unable to create pipe\n");
01328 return -1;
01329 }
01330 res = soundcard_init();
01331 if (res < 0) {
01332 if (option_verbose > 1) {
01333 ast_verbose(VERBOSE_PREFIX_2 "No sound card detected -- console channel will be unavailable\n");
01334 ast_verbose(VERBOSE_PREFIX_2 "Turn off ALSA support by adding 'noload=chan_alsa.so' in /etc/asterisk/modules.conf\n");
01335 }
01336 return 0;
01337 }
01338
01339 res = ast_channel_register(&alsa_tech);
01340 if (res < 0) {
01341 ast_log(LOG_ERROR, "Unable to register channel class 'Console'\n");
01342 return -1;
01343 }
01344 ast_cli_register_multiple(cli_alsa, sizeof(cli_alsa) / sizeof(struct ast_cli_entry));
01345
01346 ast_pthread_create_background(&sthread, NULL, sound_thread, NULL);
01347 #ifdef ALSA_MONITOR
01348 if (alsa_monitor_start())
01349 ast_log(LOG_ERROR, "Problem starting Monitoring\n");
01350 #endif
01351 return 0;
01352 }
01353
01354 static int unload_module(void)
01355 {
01356 ast_channel_unregister(&alsa_tech);
01357 ast_cli_unregister_multiple(cli_alsa, sizeof(cli_alsa) / sizeof(struct ast_cli_entry));
01358
01359 if (alsa.icard)
01360 snd_pcm_close(alsa.icard);
01361 if (alsa.ocard)
01362 snd_pcm_close(alsa.ocard);
01363 if (sndcmd[0] > 0) {
01364 close(sndcmd[0]);
01365 close(sndcmd[1]);
01366 }
01367 if (alsa.owner)
01368 ast_softhangup(alsa.owner, AST_SOFTHANGUP_APPUNLOAD);
01369 if (alsa.owner)
01370 return -1;
01371 return 0;
01372 }
01373
01374 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "ALSA Console Channel Driver");