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