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: 64306 $")
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 case AST_CONTROL_PROGRESS:
00752 break;
00753 case -1:
00754 res = -1;
00755 break;
00756 case AST_CONTROL_VIDUPDATE:
00757 res = -1;
00758 break;
00759 case AST_CONTROL_HOLD:
00760 ast_verbose(" << Console Has Been Placed on Hold >> \n");
00761 ast_moh_start(chan, data, mohinterpret);
00762 break;
00763 case AST_CONTROL_UNHOLD:
00764 ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
00765 ast_moh_stop(chan);
00766 break;
00767 default:
00768 ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, chan->name);
00769 res = -1;
00770 }
00771
00772 if (res > -1)
00773 write(sndcmd[1], &res, sizeof(res));
00774
00775 ast_mutex_unlock(&alsalock);
00776
00777 return res;
00778 }
00779
00780 static struct ast_channel *alsa_new(struct chan_alsa_pvt *p, int state)
00781 {
00782 struct ast_channel *tmp = NULL;
00783
00784 if (!(tmp = ast_channel_alloc(1, state, 0, 0, "", p->exten, p->context, 0, "ALSA/%s", indevname)))
00785 return NULL;
00786
00787 tmp->tech = &alsa_tech;
00788 tmp->fds[0] = readdev;
00789 tmp->nativeformats = AST_FORMAT_SLINEAR;
00790 tmp->readformat = AST_FORMAT_SLINEAR;
00791 tmp->writeformat = AST_FORMAT_SLINEAR;
00792 tmp->tech_pvt = p;
00793 if (!ast_strlen_zero(p->context))
00794 ast_copy_string(tmp->context, p->context, sizeof(tmp->context));
00795 if (!ast_strlen_zero(p->exten))
00796 ast_copy_string(tmp->exten, p->exten, sizeof(tmp->exten));
00797 if (!ast_strlen_zero(language))
00798 ast_string_field_set(tmp, language, language);
00799 p->owner = tmp;
00800 ast_module_ref(ast_module_info->self);
00801 ast_jb_configure(tmp, &global_jbconf);
00802 if (state != AST_STATE_DOWN) {
00803 if (ast_pbx_start(tmp)) {
00804 ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
00805 ast_hangup(tmp);
00806 tmp = NULL;
00807 }
00808 }
00809
00810 return tmp;
00811 }
00812
00813 static struct ast_channel *alsa_request(const char *type, int format, void *data, int *cause)
00814 {
00815 int oldformat = format;
00816 struct ast_channel *tmp = NULL;
00817
00818 format &= AST_FORMAT_SLINEAR;
00819 if (!format) {
00820 ast_log(LOG_NOTICE, "Asked to get a channel of format '%d'\n", oldformat);
00821 return NULL;
00822 }
00823
00824 ast_mutex_lock(&alsalock);
00825
00826 if (alsa.owner) {
00827 ast_log(LOG_NOTICE, "Already have a call on the ALSA channel\n");
00828 *cause = AST_CAUSE_BUSY;
00829 } else if (!(tmp = alsa_new(&alsa, AST_STATE_DOWN)))
00830 ast_log(LOG_WARNING, "Unable to create new ALSA channel\n");
00831
00832 ast_mutex_unlock(&alsalock);
00833
00834 return tmp;
00835 }
00836
00837 static int console_autoanswer_deprecated(int fd, int argc, char *argv[])
00838 {
00839 int res = RESULT_SUCCESS;
00840
00841 if ((argc != 1) && (argc != 2))
00842 return RESULT_SHOWUSAGE;
00843
00844 ast_mutex_lock(&alsalock);
00845
00846 if (argc == 1) {
00847 ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off");
00848 } else {
00849 if (!strcasecmp(argv[1], "on"))
00850 autoanswer = -1;
00851 else if (!strcasecmp(argv[1], "off"))
00852 autoanswer = 0;
00853 else
00854 res = RESULT_SHOWUSAGE;
00855 }
00856
00857 ast_mutex_unlock(&alsalock);
00858
00859 return res;
00860 }
00861
00862 static int console_autoanswer(int fd, int argc, char *argv[])
00863 {
00864 int res = RESULT_SUCCESS;;
00865 if ((argc != 2) && (argc != 3))
00866 return RESULT_SHOWUSAGE;
00867 ast_mutex_lock(&alsalock);
00868 if (argc == 2) {
00869 ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off");
00870 } else {
00871 if (!strcasecmp(argv[2], "on"))
00872 autoanswer = -1;
00873 else if (!strcasecmp(argv[2], "off"))
00874 autoanswer = 0;
00875 else
00876 res = RESULT_SHOWUSAGE;
00877 }
00878 ast_mutex_unlock(&alsalock);
00879 return res;
00880 }
00881
00882 static char *autoanswer_complete(const char *line, const char *word, int pos, int state)
00883 {
00884 #ifndef MIN
00885 #define MIN(a,b) ((a) < (b) ? (a) : (b))
00886 #endif
00887 switch (state) {
00888 case 0:
00889 if (!ast_strlen_zero(word) && !strncasecmp(word, "on", MIN(strlen(word), 2)))
00890 return ast_strdup("on");
00891 case 1:
00892 if (!ast_strlen_zero(word) && !strncasecmp(word, "off", MIN(strlen(word), 3)))
00893 return ast_strdup("off");
00894 default:
00895 return NULL;
00896 }
00897 return NULL;
00898 }
00899
00900 static const char autoanswer_usage[] =
00901 "Usage: console autoanswer [on|off]\n"
00902 " Enables or disables autoanswer feature. If used without\n"
00903 " argument, displays the current on/off status of autoanswer.\n"
00904 " The default value of autoanswer is in 'alsa.conf'.\n";
00905
00906 static int console_answer_deprecated(int fd, int argc, char *argv[])
00907 {
00908 int res = RESULT_SUCCESS;
00909
00910 if (argc != 1)
00911 return RESULT_SHOWUSAGE;
00912
00913 ast_mutex_lock(&alsalock);
00914
00915 if (!alsa.owner) {
00916 ast_cli(fd, "No one is calling us\n");
00917 res = RESULT_FAILURE;
00918 } else {
00919 hookstate = 1;
00920 cursound = -1;
00921 grab_owner();
00922 if (alsa.owner) {
00923 struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
00924 ast_queue_frame(alsa.owner, &f);
00925 ast_mutex_unlock(&alsa.owner->lock);
00926 }
00927 answer_sound();
00928 }
00929
00930 snd_pcm_prepare(alsa.icard);
00931 snd_pcm_start(alsa.icard);
00932
00933 ast_mutex_unlock(&alsalock);
00934
00935 return RESULT_SUCCESS;
00936 }
00937
00938 static int console_answer(int fd, int argc, char *argv[])
00939 {
00940 int res = RESULT_SUCCESS;
00941
00942 if (argc != 2)
00943 return RESULT_SHOWUSAGE;
00944
00945 ast_mutex_lock(&alsalock);
00946
00947 if (!alsa.owner) {
00948 ast_cli(fd, "No one is calling us\n");
00949 res = RESULT_FAILURE;
00950 } else {
00951 hookstate = 1;
00952 cursound = -1;
00953 grab_owner();
00954 if (alsa.owner) {
00955 struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
00956 ast_queue_frame(alsa.owner, &f);
00957 ast_mutex_unlock(&alsa.owner->lock);
00958 }
00959 answer_sound();
00960 }
00961
00962 snd_pcm_prepare(alsa.icard);
00963 snd_pcm_start(alsa.icard);
00964
00965 ast_mutex_unlock(&alsalock);
00966
00967 return RESULT_SUCCESS;
00968 }
00969
00970 static char sendtext_usage[] =
00971 "Usage: console send text <message>\n"
00972 " Sends a text message for display on the remote terminal.\n";
00973
00974 static int console_sendtext_deprecated(int fd, int argc, char *argv[])
00975 {
00976 int tmparg = 2;
00977 int res = RESULT_SUCCESS;
00978
00979 if (argc < 2)
00980 return RESULT_SHOWUSAGE;
00981
00982 ast_mutex_lock(&alsalock);
00983
00984 if (!alsa.owner) {
00985 ast_cli(fd, "No one is calling us\n");
00986 res = RESULT_FAILURE;
00987 } else {
00988 struct ast_frame f = { AST_FRAME_TEXT, 0 };
00989 char text2send[256] = "";
00990 text2send[0] = '\0';
00991 while (tmparg < argc) {
00992 strncat(text2send, argv[tmparg++], sizeof(text2send) - strlen(text2send) - 1);
00993 strncat(text2send, " ", sizeof(text2send) - strlen(text2send) - 1);
00994 }
00995 text2send[strlen(text2send) - 1] = '\n';
00996 f.data = text2send;
00997 f.datalen = strlen(text2send) + 1;
00998 grab_owner();
00999 if (alsa.owner) {
01000 ast_queue_frame(alsa.owner, &f);
01001 f.frametype = AST_FRAME_CONTROL;
01002 f.subclass = AST_CONTROL_ANSWER;
01003 f.data = NULL;
01004 f.datalen = 0;
01005 ast_queue_frame(alsa.owner, &f);
01006 ast_mutex_unlock(&alsa.owner->lock);
01007 }
01008 }
01009
01010 ast_mutex_unlock(&alsalock);
01011
01012 return res;
01013 }
01014
01015 static int console_sendtext(int fd, int argc, char *argv[])
01016 {
01017 int tmparg = 3;
01018 int res = RESULT_SUCCESS;
01019
01020 if (argc < 3)
01021 return RESULT_SHOWUSAGE;
01022
01023 ast_mutex_lock(&alsalock);
01024
01025 if (!alsa.owner) {
01026 ast_cli(fd, "No one is calling us\n");
01027 res = RESULT_FAILURE;
01028 } else {
01029 struct ast_frame f = { AST_FRAME_TEXT, 0 };
01030 char text2send[256] = "";
01031 text2send[0] = '\0';
01032 while (tmparg < argc) {
01033 strncat(text2send, argv[tmparg++], sizeof(text2send) - strlen(text2send) - 1);
01034 strncat(text2send, " ", sizeof(text2send) - strlen(text2send) - 1);
01035 }
01036 text2send[strlen(text2send) - 1] = '\n';
01037 f.data = text2send;
01038 f.datalen = strlen(text2send) + 1;
01039 grab_owner();
01040 if (alsa.owner) {
01041 ast_queue_frame(alsa.owner, &f);
01042 f.frametype = AST_FRAME_CONTROL;
01043 f.subclass = AST_CONTROL_ANSWER;
01044 f.data = NULL;
01045 f.datalen = 0;
01046 ast_queue_frame(alsa.owner, &f);
01047 ast_mutex_unlock(&alsa.owner->lock);
01048 }
01049 }
01050
01051 ast_mutex_unlock(&alsalock);
01052
01053 return res;
01054 }
01055
01056 static char answer_usage[] =
01057 "Usage: console answer\n"
01058 " Answers an incoming call on the console (ALSA) channel.\n";
01059
01060 static int console_hangup_deprecated(int fd, int argc, char *argv[])
01061 {
01062 int res = RESULT_SUCCESS;
01063
01064 if (argc != 1)
01065 return RESULT_SHOWUSAGE;
01066
01067 cursound = -1;
01068
01069 ast_mutex_lock(&alsalock);
01070
01071 if (!alsa.owner && !hookstate) {
01072 ast_cli(fd, "No call to hangup up\n");
01073 res = RESULT_FAILURE;
01074 } else {
01075 hookstate = 0;
01076 grab_owner();
01077 if (alsa.owner) {
01078 ast_queue_hangup(alsa.owner);
01079 ast_mutex_unlock(&alsa.owner->lock);
01080 }
01081 }
01082
01083 ast_mutex_unlock(&alsalock);
01084
01085 return res;
01086 }
01087
01088 static int console_hangup(int fd, int argc, char *argv[])
01089 {
01090 int res = RESULT_SUCCESS;
01091
01092 if (argc != 2)
01093 return RESULT_SHOWUSAGE;
01094
01095 cursound = -1;
01096
01097 ast_mutex_lock(&alsalock);
01098
01099 if (!alsa.owner && !hookstate) {
01100 ast_cli(fd, "No call to hangup up\n");
01101 res = RESULT_FAILURE;
01102 } else {
01103 hookstate = 0;
01104 grab_owner();
01105 if (alsa.owner) {
01106 ast_queue_hangup(alsa.owner);
01107 ast_mutex_unlock(&alsa.owner->lock);
01108 }
01109 }
01110
01111 ast_mutex_unlock(&alsalock);
01112
01113 return res;
01114 }
01115
01116 static char hangup_usage[] =
01117 "Usage: console hangup\n"
01118 " Hangs up any call currently placed on the console.\n";
01119
01120 static int console_dial_deprecated(int fd, int argc, char *argv[])
01121 {
01122 char tmp[256], *tmp2;
01123 char *mye, *myc;
01124 char *d;
01125 int res = RESULT_SUCCESS;
01126
01127 if ((argc != 1) && (argc != 2))
01128 return RESULT_SHOWUSAGE;
01129
01130 ast_mutex_lock(&alsalock);
01131
01132 if (alsa.owner) {
01133 if (argc == 2) {
01134 d = argv[1];
01135 grab_owner();
01136 if (alsa.owner) {
01137 struct ast_frame f = { AST_FRAME_DTMF };
01138 while (*d) {
01139 f.subclass = *d;
01140 ast_queue_frame(alsa.owner, &f);
01141 d++;
01142 }
01143 ast_mutex_unlock(&alsa.owner->lock);
01144 }
01145 } else {
01146 ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n");
01147 res = RESULT_FAILURE;
01148 }
01149 } else {
01150 mye = exten;
01151 myc = context;
01152 if (argc == 2) {
01153 char *stringp = NULL;
01154 ast_copy_string(tmp, argv[1], sizeof(tmp));
01155 stringp = tmp;
01156 strsep(&stringp, "@");
01157 tmp2 = strsep(&stringp, "@");
01158 if (!ast_strlen_zero(tmp))
01159 mye = tmp;
01160 if (!ast_strlen_zero(tmp2))
01161 myc = tmp2;
01162 }
01163 if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
01164 ast_copy_string(alsa.exten, mye, sizeof(alsa.exten));
01165 ast_copy_string(alsa.context, myc, sizeof(alsa.context));
01166 hookstate = 1;
01167 alsa_new(&alsa, AST_STATE_RINGING);
01168 } else
01169 ast_cli(fd, "No such extension '%s' in context '%s'\n", mye, myc);
01170 }
01171
01172 ast_mutex_unlock(&alsalock);
01173
01174 return res;
01175 }
01176
01177 static int console_dial(int fd, int argc, char *argv[])
01178 {
01179 char tmp[256], *tmp2;
01180 char *mye, *myc;
01181 char *d;
01182 int res = RESULT_SUCCESS;
01183
01184 if ((argc != 2) && (argc != 3))
01185 return RESULT_SHOWUSAGE;
01186
01187 ast_mutex_lock(&alsalock);
01188
01189 if (alsa.owner) {
01190 if (argc == 3) {
01191 d = argv[2];
01192 grab_owner();
01193 if (alsa.owner) {
01194 struct ast_frame f = { AST_FRAME_DTMF };
01195 while (*d) {
01196 f.subclass = *d;
01197 ast_queue_frame(alsa.owner, &f);
01198 d++;
01199 }
01200 ast_mutex_unlock(&alsa.owner->lock);
01201 }
01202 } else {
01203 ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n");
01204 res = RESULT_FAILURE;
01205 }
01206 } else {
01207 mye = exten;
01208 myc = context;
01209 if (argc == 3) {
01210 char *stringp = NULL;
01211 ast_copy_string(tmp, argv[2], sizeof(tmp));
01212 stringp = tmp;
01213 strsep(&stringp, "@");
01214 tmp2 = strsep(&stringp, "@");
01215 if (!ast_strlen_zero(tmp))
01216 mye = tmp;
01217 if (!ast_strlen_zero(tmp2))
01218 myc = tmp2;
01219 }
01220 if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
01221 ast_copy_string(alsa.exten, mye, sizeof(alsa.exten));
01222 ast_copy_string(alsa.context, myc, sizeof(alsa.context));
01223 hookstate = 1;
01224 alsa_new(&alsa, AST_STATE_RINGING);
01225 } else
01226 ast_cli(fd, "No such extension '%s' in context '%s'\n", mye, myc);
01227 }
01228
01229 ast_mutex_unlock(&alsalock);
01230
01231 return res;
01232 }
01233
01234 static char dial_usage[] =
01235 "Usage: console dial [extension[@context]]\n"
01236 " Dials a given extension (and context if specified)\n";
01237
01238 static struct ast_cli_entry cli_alsa_answer_deprecated = {
01239 { "answer", NULL },
01240 console_answer_deprecated, NULL,
01241 NULL };
01242
01243 static struct ast_cli_entry cli_alsa_hangup_deprecated = {
01244 { "hangup", NULL },
01245 console_hangup_deprecated, NULL,
01246 NULL };
01247
01248 static struct ast_cli_entry cli_alsa_dial_deprecated = {
01249 { "dial", NULL },
01250 console_dial_deprecated, NULL,
01251 NULL };
01252
01253 static struct ast_cli_entry cli_alsa_send_text_deprecated = {
01254 { "send", "text", NULL },
01255 console_sendtext_deprecated, NULL,
01256 NULL };
01257
01258 static struct ast_cli_entry cli_alsa_autoanswer_deprecated = {
01259 { "autoanswer", NULL },
01260 console_autoanswer_deprecated, NULL,
01261 NULL, autoanswer_complete };
01262
01263 static struct ast_cli_entry cli_alsa[] = {
01264 { { "console", "answer", NULL },
01265 console_answer, "Answer an incoming console call",
01266 answer_usage, NULL, &cli_alsa_answer_deprecated },
01267
01268 { { "console", "hangup", NULL },
01269 console_hangup, "Hangup a call on the console",
01270 hangup_usage, NULL, &cli_alsa_hangup_deprecated },
01271
01272 { { "console", "dial", NULL },
01273 console_dial, "Dial an extension on the console",
01274 dial_usage, NULL, &cli_alsa_dial_deprecated },
01275
01276 { { "console", "send", "text", NULL },
01277 console_sendtext, "Send text to the remote device",
01278 sendtext_usage, NULL, &cli_alsa_send_text_deprecated },
01279
01280 { { "console", "autoanswer", NULL },
01281 console_autoanswer, "Sets/displays autoanswer",
01282 autoanswer_usage, autoanswer_complete, &cli_alsa_autoanswer_deprecated },
01283 };
01284
01285 static int load_module(void)
01286 {
01287 int res;
01288 struct ast_config *cfg;
01289 struct ast_variable *v;
01290
01291
01292 memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
01293
01294 strcpy(mohinterpret, "default");
01295
01296 if ((cfg = ast_config_load(config))) {
01297 v = ast_variable_browse(cfg, "general");
01298 for (; v; v = v->next) {
01299
01300 if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
01301 continue;
01302
01303 if (!strcasecmp(v->name, "autoanswer"))
01304 autoanswer = ast_true(v->value);
01305 else if (!strcasecmp(v->name, "silencesuppression"))
01306 silencesuppression = ast_true(v->value);
01307 else if (!strcasecmp(v->name, "silencethreshold"))
01308 silencethreshold = atoi(v->value);
01309 else if (!strcasecmp(v->name, "context"))
01310 ast_copy_string(context, v->value, sizeof(context));
01311 else if (!strcasecmp(v->name, "language"))
01312 ast_copy_string(language, v->value, sizeof(language));
01313 else if (!strcasecmp(v->name, "extension"))
01314 ast_copy_string(exten, v->value, sizeof(exten));
01315 else if (!strcasecmp(v->name, "input_device"))
01316 ast_copy_string(indevname, v->value, sizeof(indevname));
01317 else if (!strcasecmp(v->name, "output_device"))
01318 ast_copy_string(outdevname, v->value, sizeof(outdevname));
01319 else if (!strcasecmp(v->name, "mohinterpret"))
01320 ast_copy_string(mohinterpret, v->value, sizeof(mohinterpret));
01321 }
01322 ast_config_destroy(cfg);
01323 }
01324 res = pipe(sndcmd);
01325 if (res) {
01326 ast_log(LOG_ERROR, "Unable to create pipe\n");
01327 return -1;
01328 }
01329 res = soundcard_init();
01330 if (res < 0) {
01331 if (option_verbose > 1) {
01332 ast_verbose(VERBOSE_PREFIX_2 "No sound card detected -- console channel will be unavailable\n");
01333 ast_verbose(VERBOSE_PREFIX_2 "Turn off ALSA support by adding 'noload=chan_alsa.so' in /etc/asterisk/modules.conf\n");
01334 }
01335 return 0;
01336 }
01337
01338 res = ast_channel_register(&alsa_tech);
01339 if (res < 0) {
01340 ast_log(LOG_ERROR, "Unable to register channel class 'Console'\n");
01341 return -1;
01342 }
01343 ast_cli_register_multiple(cli_alsa, sizeof(cli_alsa) / sizeof(struct ast_cli_entry));
01344
01345 ast_pthread_create_background(&sthread, NULL, sound_thread, NULL);
01346 #ifdef ALSA_MONITOR
01347 if (alsa_monitor_start())
01348 ast_log(LOG_ERROR, "Problem starting Monitoring\n");
01349 #endif
01350 return 0;
01351 }
01352
01353 static int unload_module(void)
01354 {
01355 ast_channel_unregister(&alsa_tech);
01356 ast_cli_unregister_multiple(cli_alsa, sizeof(cli_alsa) / sizeof(struct ast_cli_entry));
01357
01358 if (alsa.icard)
01359 snd_pcm_close(alsa.icard);
01360 if (alsa.ocard)
01361 snd_pcm_close(alsa.ocard);
01362 if (sndcmd[0] > 0) {
01363 close(sndcmd[0]);
01364 close(sndcmd[1]);
01365 }
01366 if (alsa.owner)
01367 ast_softhangup(alsa.owner, AST_SOFTHANGUP_APPUNLOAD);
01368 if (alsa.owner)
01369 return -1;
01370 return 0;
01371 }
01372
01373 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "ALSA Console Channel Driver");