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
00035
00036
00037
00038
00039
00040
00041 #include "asterisk.h"
00042
00043 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 371592 $")
00044
00045 #include <ctype.h>
00046 #include <math.h>
00047 #include <sys/ioctl.h>
00048
00049 #ifdef __linux
00050 #include <linux/soundcard.h>
00051 #elif defined(__FreeBSD__) || defined(__CYGWIN__) || defined(__GLIBC__)
00052 #include <sys/soundcard.h>
00053 #else
00054 #include <soundcard.h>
00055 #endif
00056
00057 #include "asterisk/channel.h"
00058 #include "asterisk/file.h"
00059 #include "asterisk/callerid.h"
00060 #include "asterisk/module.h"
00061 #include "asterisk/pbx.h"
00062 #include "asterisk/cli.h"
00063 #include "asterisk/causes.h"
00064 #include "asterisk/musiconhold.h"
00065 #include "asterisk/app.h"
00066
00067 #include "console_video.h"
00068
00069
00070
00071 static struct ast_jb_conf default_jbconf =
00072 {
00073 .flags = 0,
00074 .max_size = 200,
00075 .resync_threshold = 1000,
00076 .impl = "fixed",
00077 .target_extra = 40,
00078 };
00079 static struct ast_jb_conf global_jbconf;
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209 #define FRAME_SIZE 160
00210 #define QUEUE_SIZE 10
00211
00212 #if defined(__FreeBSD__)
00213 #define FRAGS 0x8
00214 #else
00215 #define FRAGS ( ( (6 * 5) << 16 ) | 0x6 )
00216 #endif
00217
00218
00219
00220
00221
00222 #define TEXT_SIZE 256
00223
00224 #if 0
00225 #define TRYOPEN 1
00226 #endif
00227 #define O_CLOSE 0x444
00228
00229 #if defined( __OpenBSD__ ) || defined( __NetBSD__ )
00230 #define DEV_DSP "/dev/audio"
00231 #else
00232 #define DEV_DSP "/dev/dsp"
00233 #endif
00234
00235 static char *config = "oss.conf";
00236
00237 static int oss_debug;
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247 struct chan_oss_pvt {
00248 struct chan_oss_pvt *next;
00249
00250 char *name;
00251 int total_blocks;
00252 int sounddev;
00253 enum { M_UNSET, M_FULL, M_READ, M_WRITE } duplex;
00254 int autoanswer;
00255 int autohangup;
00256 int hookstate;
00257 char *mixer_cmd;
00258 unsigned int queuesize;
00259 unsigned int frags;
00260
00261 int warned;
00262 #define WARN_used_blocks 1
00263 #define WARN_speed 2
00264 #define WARN_frag 4
00265 int w_errors;
00266 struct timeval lastopen;
00267
00268 int overridecontext;
00269 int mute;
00270
00271
00272
00273
00274 #define BOOST_SCALE (1<<9)
00275 #define BOOST_MAX 40
00276 int boost;
00277 char device[64];
00278
00279 pthread_t sthread;
00280
00281 struct ast_channel *owner;
00282
00283 struct video_desc *env;
00284
00285 char ext[AST_MAX_EXTENSION];
00286 char ctx[AST_MAX_CONTEXT];
00287 char language[MAX_LANGUAGE];
00288 char cid_name[256];
00289 char cid_num[256];
00290 char mohinterpret[MAX_MUSICCLASS];
00291
00292
00293 char oss_write_buf[FRAME_SIZE * 2];
00294 int oss_write_dst;
00295
00296
00297
00298 char oss_read_buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET];
00299 int readpos;
00300 struct ast_frame read_f;
00301 };
00302
00303
00304 static struct chan_oss_pvt *find_desc(const char *dev);
00305
00306 static char *oss_active;
00307
00308
00309 struct video_desc *get_video_desc(struct ast_channel *c)
00310 {
00311 struct chan_oss_pvt *o = c ? ast_channel_tech_pvt(c) : find_desc(oss_active);
00312 return o ? o->env : NULL;
00313 }
00314 static struct chan_oss_pvt oss_default = {
00315 .sounddev = -1,
00316 .duplex = M_UNSET,
00317 .autoanswer = 1,
00318 .autohangup = 1,
00319 .queuesize = QUEUE_SIZE,
00320 .frags = FRAGS,
00321 .ext = "s",
00322 .ctx = "default",
00323 .readpos = AST_FRIENDLY_OFFSET,
00324 .lastopen = { 0, 0 },
00325 .boost = BOOST_SCALE,
00326 };
00327
00328
00329 static int setformat(struct chan_oss_pvt *o, int mode);
00330
00331 static struct ast_channel *oss_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor,
00332 const char *data, int *cause);
00333 static int oss_digit_begin(struct ast_channel *c, char digit);
00334 static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration);
00335 static int oss_text(struct ast_channel *c, const char *text);
00336 static int oss_hangup(struct ast_channel *c);
00337 static int oss_answer(struct ast_channel *c);
00338 static struct ast_frame *oss_read(struct ast_channel *chan);
00339 static int oss_call(struct ast_channel *c, const char *dest, int timeout);
00340 static int oss_write(struct ast_channel *chan, struct ast_frame *f);
00341 static int oss_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
00342 static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
00343 static char tdesc[] = "OSS Console Channel Driver";
00344
00345
00346 static struct ast_channel_tech oss_tech = {
00347 .type = "Console",
00348 .description = tdesc,
00349 .requester = oss_request,
00350 .send_digit_begin = oss_digit_begin,
00351 .send_digit_end = oss_digit_end,
00352 .send_text = oss_text,
00353 .hangup = oss_hangup,
00354 .answer = oss_answer,
00355 .read = oss_read,
00356 .call = oss_call,
00357 .write = oss_write,
00358 .write_video = console_write_video,
00359 .indicate = oss_indicate,
00360 .fixup = oss_fixup,
00361 };
00362
00363
00364
00365
00366 static struct chan_oss_pvt *find_desc(const char *dev)
00367 {
00368 struct chan_oss_pvt *o = NULL;
00369
00370 if (!dev)
00371 ast_log(LOG_WARNING, "null dev\n");
00372
00373 for (o = oss_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next);
00374
00375 if (!o)
00376 ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--");
00377
00378 return o;
00379 }
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391
00392 static char *ast_ext_ctx(const char *src, char **ext, char **ctx)
00393 {
00394 struct chan_oss_pvt *o = find_desc(oss_active);
00395
00396 if (ext == NULL || ctx == NULL)
00397 return NULL;
00398
00399 *ext = *ctx = NULL;
00400
00401 if (src && *src != '\0')
00402 *ext = ast_strdup(src);
00403
00404 if (*ext == NULL)
00405 return NULL;
00406
00407 if (!o->overridecontext) {
00408
00409 *ctx = strrchr(*ext, '@');
00410 if (*ctx)
00411 *(*ctx)++ = '\0';
00412 }
00413
00414 return *ext;
00415 }
00416
00417
00418
00419
00420 static int used_blocks(struct chan_oss_pvt *o)
00421 {
00422 struct audio_buf_info info;
00423
00424 if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) {
00425 if (!(o->warned & WARN_used_blocks)) {
00426 ast_log(LOG_WARNING, "Error reading output space\n");
00427 o->warned |= WARN_used_blocks;
00428 }
00429 return 1;
00430 }
00431
00432 if (o->total_blocks == 0) {
00433 if (0)
00434 ast_log(LOG_WARNING, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments);
00435 o->total_blocks = info.fragments;
00436 }
00437
00438 return o->total_blocks - info.fragments;
00439 }
00440
00441
00442 static int soundcard_writeframe(struct chan_oss_pvt *o, short *data)
00443 {
00444 int res;
00445
00446 if (o->sounddev < 0)
00447 setformat(o, O_RDWR);
00448 if (o->sounddev < 0)
00449 return 0;
00450
00451
00452
00453
00454
00455
00456 res = used_blocks(o);
00457 if (res > o->queuesize) {
00458 if (o->w_errors++ == 0 && (oss_debug & 0x4))
00459 ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors);
00460 return 0;
00461 }
00462 o->w_errors = 0;
00463 return write(o->sounddev, (void *)data, FRAME_SIZE * 2);
00464 }
00465
00466
00467
00468
00469
00470
00471 static int setformat(struct chan_oss_pvt *o, int mode)
00472 {
00473 int fmt, desired, res, fd;
00474
00475 if (o->sounddev >= 0) {
00476 ioctl(o->sounddev, SNDCTL_DSP_RESET, 0);
00477 close(o->sounddev);
00478 o->duplex = M_UNSET;
00479 o->sounddev = -1;
00480 }
00481 if (mode == O_CLOSE)
00482 return 0;
00483 if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000)
00484 return -1;
00485 o->lastopen = ast_tvnow();
00486 fd = o->sounddev = open(o->device, mode | O_NONBLOCK);
00487 if (fd < 0) {
00488 ast_log(LOG_WARNING, "Unable to re-open DSP device %s: %s\n", o->device, strerror(errno));
00489 return -1;
00490 }
00491 if (o->owner)
00492 ast_channel_set_fd(o->owner, 0, fd);
00493
00494 #if __BYTE_ORDER == __LITTLE_ENDIAN
00495 fmt = AFMT_S16_LE;
00496 #else
00497 fmt = AFMT_S16_BE;
00498 #endif
00499 res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
00500 if (res < 0) {
00501 ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n");
00502 return -1;
00503 }
00504 switch (mode) {
00505 case O_RDWR:
00506 res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
00507
00508 res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt);
00509 if (res == 0 && (fmt & DSP_CAP_DUPLEX)) {
00510 ast_verb(2, "Console is full duplex\n");
00511 o->duplex = M_FULL;
00512 };
00513 break;
00514
00515 case O_WRONLY:
00516 o->duplex = M_WRITE;
00517 break;
00518
00519 case O_RDONLY:
00520 o->duplex = M_READ;
00521 break;
00522 }
00523
00524 fmt = 0;
00525 res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt);
00526 if (res < 0) {
00527 ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
00528 return -1;
00529 }
00530 fmt = desired = DEFAULT_SAMPLE_RATE;
00531 res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt);
00532
00533 if (res < 0) {
00534 ast_log(LOG_WARNING, "Failed to set sample rate to %d\n", desired);
00535 return -1;
00536 }
00537 if (fmt != desired) {
00538 if (!(o->warned & WARN_speed)) {
00539 ast_log(LOG_WARNING,
00540 "Requested %d Hz, got %d Hz -- sound may be choppy\n",
00541 desired, fmt);
00542 o->warned |= WARN_speed;
00543 }
00544 }
00545
00546
00547
00548
00549 if (o->frags) {
00550 fmt = o->frags;
00551 res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt);
00552 if (res < 0) {
00553 if (!(o->warned & WARN_frag)) {
00554 ast_log(LOG_WARNING,
00555 "Unable to set fragment size -- sound may be choppy\n");
00556 o->warned |= WARN_frag;
00557 }
00558 }
00559 }
00560
00561 res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
00562 res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res);
00563
00564 return 0;
00565 }
00566
00567
00568
00569
00570 static int oss_digit_begin(struct ast_channel *c, char digit)
00571 {
00572 return 0;
00573 }
00574
00575 static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration)
00576 {
00577
00578 ast_verbose(" << Console Received digit %c of duration %u ms >> \n",
00579 digit, duration);
00580 return 0;
00581 }
00582
00583 static int oss_text(struct ast_channel *c, const char *text)
00584 {
00585
00586 ast_verbose(" << Console Received text %s >> \n", text);
00587 return 0;
00588 }
00589
00590
00591
00592
00593 static int oss_call(struct ast_channel *c, const char *dest, int timeout)
00594 {
00595 struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
00596 struct ast_frame f = { AST_FRAME_CONTROL, };
00597 AST_DECLARE_APP_ARGS(args,
00598 AST_APP_ARG(name);
00599 AST_APP_ARG(flags);
00600 );
00601 char *parse = ast_strdupa(dest);
00602
00603 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
00604
00605 ast_verbose(" << Call to device '%s' dnid '%s' rdnis '%s' on console from '%s' <%s> >>\n",
00606 dest,
00607 S_OR(ast_channel_dialed(c)->number.str, ""),
00608 S_COR(ast_channel_redirecting(c)->from.number.valid, ast_channel_redirecting(c)->from.number.str, ""),
00609 S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, ""),
00610 S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, ""));
00611 if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "answer") == 0) {
00612 f.subclass.integer = AST_CONTROL_ANSWER;
00613 ast_queue_frame(c, &f);
00614 } else if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "noanswer") == 0) {
00615 f.subclass.integer = AST_CONTROL_RINGING;
00616 ast_queue_frame(c, &f);
00617 ast_indicate(c, AST_CONTROL_RINGING);
00618 } else if (o->autoanswer) {
00619 ast_verbose(" << Auto-answered >> \n");
00620 f.subclass.integer = AST_CONTROL_ANSWER;
00621 ast_queue_frame(c, &f);
00622 o->hookstate = 1;
00623 } else {
00624 ast_verbose("<< Type 'answer' to answer, or use 'autoanswer' for future calls >> \n");
00625 f.subclass.integer = AST_CONTROL_RINGING;
00626 ast_queue_frame(c, &f);
00627 ast_indicate(c, AST_CONTROL_RINGING);
00628 }
00629 return 0;
00630 }
00631
00632
00633
00634
00635 static int oss_answer(struct ast_channel *c)
00636 {
00637 struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
00638 ast_verbose(" << Console call has been answered >> \n");
00639 ast_setstate(c, AST_STATE_UP);
00640 o->hookstate = 1;
00641 return 0;
00642 }
00643
00644 static int oss_hangup(struct ast_channel *c)
00645 {
00646 struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
00647
00648 ast_channel_tech_pvt_set(c, NULL);
00649 o->owner = NULL;
00650 ast_verbose(" << Hangup on console >> \n");
00651 console_video_uninit(o->env);
00652 ast_module_unref(ast_module_info->self);
00653 if (o->hookstate) {
00654 if (o->autoanswer || o->autohangup) {
00655
00656 o->hookstate = 0;
00657 setformat(o, O_CLOSE);
00658 }
00659 }
00660 return 0;
00661 }
00662
00663
00664 static int oss_write(struct ast_channel *c, struct ast_frame *f)
00665 {
00666 int src;
00667 struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
00668
00669
00670
00671
00672
00673
00674
00675 src = 0;
00676 while (src < f->datalen) {
00677
00678 int l = sizeof(o->oss_write_buf) - o->oss_write_dst;
00679
00680 if (f->datalen - src >= l) {
00681 memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l);
00682 soundcard_writeframe(o, (short *) o->oss_write_buf);
00683 src += l;
00684 o->oss_write_dst = 0;
00685 } else {
00686 l = f->datalen - src;
00687 memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l);
00688 src += l;
00689 o->oss_write_dst += l;
00690 }
00691 }
00692 return 0;
00693 }
00694
00695 static struct ast_frame *oss_read(struct ast_channel *c)
00696 {
00697 int res;
00698 struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
00699 struct ast_frame *f = &o->read_f;
00700
00701
00702
00703 memset(f, '\0', sizeof(struct ast_frame));
00704 f->frametype = AST_FRAME_NULL;
00705 f->src = oss_tech.type;
00706
00707 res = read(o->sounddev, o->oss_read_buf + o->readpos, sizeof(o->oss_read_buf) - o->readpos);
00708 if (res < 0)
00709 return f;
00710
00711 o->readpos += res;
00712 if (o->readpos < sizeof(o->oss_read_buf))
00713 return f;
00714
00715 if (o->mute)
00716 return f;
00717
00718 o->readpos = AST_FRIENDLY_OFFSET;
00719 if (ast_channel_state(c) != AST_STATE_UP)
00720 return f;
00721
00722 f->frametype = AST_FRAME_VOICE;
00723 ast_format_set(&f->subclass.format, AST_FORMAT_SLINEAR, 0);
00724 f->samples = FRAME_SIZE;
00725 f->datalen = FRAME_SIZE * 2;
00726 f->data.ptr = o->oss_read_buf + AST_FRIENDLY_OFFSET;
00727 if (o->boost != BOOST_SCALE) {
00728 int i, x;
00729 int16_t *p = (int16_t *) f->data.ptr;
00730 for (i = 0; i < f->samples; i++) {
00731 x = (p[i] * o->boost) / BOOST_SCALE;
00732 if (x > 32767)
00733 x = 32767;
00734 else if (x < -32768)
00735 x = -32768;
00736 p[i] = x;
00737 }
00738 }
00739
00740 f->offset = AST_FRIENDLY_OFFSET;
00741 return f;
00742 }
00743
00744 static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
00745 {
00746 struct chan_oss_pvt *o = ast_channel_tech_pvt(newchan);
00747 o->owner = newchan;
00748 return 0;
00749 }
00750
00751 static int oss_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen)
00752 {
00753 struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
00754 int res = 0;
00755
00756 switch (cond) {
00757 case AST_CONTROL_INCOMPLETE:
00758 case AST_CONTROL_BUSY:
00759 case AST_CONTROL_CONGESTION:
00760 case AST_CONTROL_RINGING:
00761 case AST_CONTROL_PVT_CAUSE_CODE:
00762 case -1:
00763 res = -1;
00764 break;
00765 case AST_CONTROL_PROGRESS:
00766 case AST_CONTROL_PROCEEDING:
00767 case AST_CONTROL_VIDUPDATE:
00768 case AST_CONTROL_SRCUPDATE:
00769 break;
00770 case AST_CONTROL_HOLD:
00771 ast_verbose(" << Console Has Been Placed on Hold >> \n");
00772 ast_moh_start(c, data, o->mohinterpret);
00773 break;
00774 case AST_CONTROL_UNHOLD:
00775 ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
00776 ast_moh_stop(c);
00777 break;
00778 default:
00779 ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, ast_channel_name(c));
00780 return -1;
00781 }
00782
00783 return res;
00784 }
00785
00786
00787
00788
00789 static struct ast_channel *oss_new(struct chan_oss_pvt *o, char *ext, char *ctx, int state, const char *linkedid)
00790 {
00791 struct ast_channel *c;
00792
00793 c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, linkedid, 0, "Console/%s", o->device + 5);
00794 if (c == NULL)
00795 return NULL;
00796 ast_channel_tech_set(c, &oss_tech);
00797 if (o->sounddev < 0)
00798 setformat(o, O_RDWR);
00799 ast_channel_set_fd(c, 0, o->sounddev);
00800
00801 ast_format_set(ast_channel_readformat(c), AST_FORMAT_SLINEAR, 0);
00802 ast_format_set(ast_channel_writeformat(c), AST_FORMAT_SLINEAR, 0);
00803 ast_format_cap_add(ast_channel_nativeformats(c), ast_channel_readformat(c));
00804
00805
00806
00807
00808
00809 ast_channel_tech_pvt_set(c, o);
00810
00811 if (!ast_strlen_zero(o->language))
00812 ast_channel_language_set(c, o->language);
00813
00814
00815 if (!ast_strlen_zero(o->cid_num)) {
00816 ast_channel_caller(c)->ani.number.valid = 1;
00817 ast_channel_caller(c)->ani.number.str = ast_strdup(o->cid_num);
00818 }
00819 if (!ast_strlen_zero(ext)) {
00820 ast_channel_dialed(c)->number.str = ast_strdup(ext);
00821 }
00822
00823 o->owner = c;
00824 ast_module_ref(ast_module_info->self);
00825 ast_jb_configure(c, &global_jbconf);
00826 if (state != AST_STATE_DOWN) {
00827 if (ast_pbx_start(c)) {
00828 ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(c));
00829 ast_hangup(c);
00830 o->owner = c = NULL;
00831 }
00832 }
00833 console_video_start(get_video_desc(c), c);
00834
00835 return c;
00836 }
00837
00838 static struct ast_channel *oss_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
00839 {
00840 struct ast_channel *c;
00841 struct chan_oss_pvt *o;
00842 AST_DECLARE_APP_ARGS(args,
00843 AST_APP_ARG(name);
00844 AST_APP_ARG(flags);
00845 );
00846 char *parse = ast_strdupa(data);
00847 char buf[256];
00848 struct ast_format tmpfmt;
00849
00850 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
00851 o = find_desc(args.name);
00852
00853 ast_log(LOG_WARNING, "oss_request ty <%s> data 0x%p <%s>\n", type, data, data);
00854 if (o == NULL) {
00855 ast_log(LOG_NOTICE, "Device %s not found\n", args.name);
00856
00857 return NULL;
00858 }
00859 if (!(ast_format_cap_iscompatible(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0)))) {
00860 ast_log(LOG_NOTICE, "Format %s unsupported\n", ast_getformatname_multiple(buf, sizeof(buf), cap));
00861 return NULL;
00862 }
00863 if (o->owner) {
00864 ast_log(LOG_NOTICE, "Already have a call (chan %p) on the OSS channel\n", o->owner);
00865 *cause = AST_CAUSE_BUSY;
00866 return NULL;
00867 }
00868 c = oss_new(o, NULL, NULL, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL);
00869 if (c == NULL) {
00870 ast_log(LOG_WARNING, "Unable to create new OSS channel\n");
00871 return NULL;
00872 }
00873 return c;
00874 }
00875
00876 static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value);
00877
00878
00879
00880
00881 static char *console_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00882 {
00883 struct chan_oss_pvt *o = find_desc(oss_active);
00884 const char *var, *value;
00885 switch (cmd) {
00886 case CLI_INIT:
00887 e->command = CONSOLE_VIDEO_CMDS;
00888 e->usage =
00889 "Usage: " CONSOLE_VIDEO_CMDS "...\n"
00890 " Generic handler for console commands.\n";
00891 return NULL;
00892
00893 case CLI_GENERATE:
00894 return NULL;
00895 }
00896
00897 if (a->argc < e->args)
00898 return CLI_SHOWUSAGE;
00899 if (o == NULL) {
00900 ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
00901 oss_active);
00902 return CLI_FAILURE;
00903 }
00904 var = a->argv[e->args-1];
00905 value = a->argc > e->args ? a->argv[e->args] : NULL;
00906 if (value)
00907 store_config_core(o, var, value);
00908 if (!console_video_cli(o->env, var, a->fd))
00909 return CLI_SUCCESS;
00910
00911 if (!strcasecmp(var, "device")) {
00912 ast_cli(a->fd, "device is [%s]\n", o->device);
00913 }
00914 return CLI_SUCCESS;
00915 }
00916
00917 static char *console_autoanswer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00918 {
00919 struct chan_oss_pvt *o = find_desc(oss_active);
00920
00921 switch (cmd) {
00922 case CLI_INIT:
00923 e->command = "console {set|show} autoanswer [on|off]";
00924 e->usage =
00925 "Usage: console {set|show} autoanswer [on|off]\n"
00926 " Enables or disables autoanswer feature. If used without\n"
00927 " argument, displays the current on/off status of autoanswer.\n"
00928 " The default value of autoanswer is in 'oss.conf'.\n";
00929 return NULL;
00930
00931 case CLI_GENERATE:
00932 return NULL;
00933 }
00934
00935 if (a->argc == e->args - 1) {
00936 ast_cli(a->fd, "Auto answer is %s.\n", o->autoanswer ? "on" : "off");
00937 return CLI_SUCCESS;
00938 }
00939 if (a->argc != e->args)
00940 return CLI_SHOWUSAGE;
00941 if (o == NULL) {
00942 ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
00943 oss_active);
00944 return CLI_FAILURE;
00945 }
00946 if (!strcasecmp(a->argv[e->args-1], "on"))
00947 o->autoanswer = 1;
00948 else if (!strcasecmp(a->argv[e->args - 1], "off"))
00949 o->autoanswer = 0;
00950 else
00951 return CLI_SHOWUSAGE;
00952 return CLI_SUCCESS;
00953 }
00954
00955
00956 static char *console_do_answer(int fd)
00957 {
00958 struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
00959 struct chan_oss_pvt *o = find_desc(oss_active);
00960 if (!o->owner) {
00961 if (fd > -1)
00962 ast_cli(fd, "No one is calling us\n");
00963 return CLI_FAILURE;
00964 }
00965 o->hookstate = 1;
00966 ast_queue_frame(o->owner, &f);
00967 return CLI_SUCCESS;
00968 }
00969
00970
00971
00972
00973 static char *console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00974 {
00975 switch (cmd) {
00976 case CLI_INIT:
00977 e->command = "console answer";
00978 e->usage =
00979 "Usage: console answer\n"
00980 " Answers an incoming call on the console (OSS) channel.\n";
00981 return NULL;
00982
00983 case CLI_GENERATE:
00984 return NULL;
00985 }
00986 if (a->argc != e->args)
00987 return CLI_SHOWUSAGE;
00988 return console_do_answer(a->fd);
00989 }
00990
00991
00992
00993
00994
00995
00996
00997 static char *console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00998 {
00999 struct chan_oss_pvt *o = find_desc(oss_active);
01000 char buf[TEXT_SIZE];
01001
01002 if (cmd == CLI_INIT) {
01003 e->command = "console send text";
01004 e->usage =
01005 "Usage: console send text <message>\n"
01006 " Sends a text message for display on the remote terminal.\n";
01007 return NULL;
01008 } else if (cmd == CLI_GENERATE)
01009 return NULL;
01010
01011 if (a->argc < e->args + 1)
01012 return CLI_SHOWUSAGE;
01013 if (!o->owner) {
01014 ast_cli(a->fd, "Not in a call\n");
01015 return CLI_FAILURE;
01016 }
01017 ast_join(buf, sizeof(buf) - 1, a->argv + e->args);
01018 if (!ast_strlen_zero(buf)) {
01019 struct ast_frame f = { 0, };
01020 int i = strlen(buf);
01021 buf[i] = '\n';
01022 f.frametype = AST_FRAME_TEXT;
01023 f.subclass.integer = 0;
01024 f.data.ptr = buf;
01025 f.datalen = i + 1;
01026 ast_queue_frame(o->owner, &f);
01027 }
01028 return CLI_SUCCESS;
01029 }
01030
01031 static char *console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01032 {
01033 struct chan_oss_pvt *o = find_desc(oss_active);
01034
01035 if (cmd == CLI_INIT) {
01036 e->command = "console hangup";
01037 e->usage =
01038 "Usage: console hangup\n"
01039 " Hangs up any call currently placed on the console.\n";
01040 return NULL;
01041 } else if (cmd == CLI_GENERATE)
01042 return NULL;
01043
01044 if (a->argc != e->args)
01045 return CLI_SHOWUSAGE;
01046 if (!o->owner && !o->hookstate) {
01047 ast_cli(a->fd, "No call to hang up\n");
01048 return CLI_FAILURE;
01049 }
01050 o->hookstate = 0;
01051 if (o->owner)
01052 ast_queue_hangup_with_cause(o->owner, AST_CAUSE_NORMAL_CLEARING);
01053 setformat(o, O_CLOSE);
01054 return CLI_SUCCESS;
01055 }
01056
01057 static char *console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01058 {
01059 struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_FLASH } };
01060 struct chan_oss_pvt *o = find_desc(oss_active);
01061
01062 if (cmd == CLI_INIT) {
01063 e->command = "console flash";
01064 e->usage =
01065 "Usage: console flash\n"
01066 " Flashes the call currently placed on the console.\n";
01067 return NULL;
01068 } else if (cmd == CLI_GENERATE)
01069 return NULL;
01070
01071 if (a->argc != e->args)
01072 return CLI_SHOWUSAGE;
01073 if (!o->owner) {
01074 ast_cli(a->fd, "No call to flash\n");
01075 return CLI_FAILURE;
01076 }
01077 o->hookstate = 0;
01078 if (o->owner)
01079 ast_queue_frame(o->owner, &f);
01080 return CLI_SUCCESS;
01081 }
01082
01083 static char *console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01084 {
01085 char *s = NULL;
01086 char *mye = NULL, *myc = NULL;
01087 struct chan_oss_pvt *o = find_desc(oss_active);
01088
01089 if (cmd == CLI_INIT) {
01090 e->command = "console dial";
01091 e->usage =
01092 "Usage: console dial [extension[@context]]\n"
01093 " Dials a given extension (and context if specified)\n";
01094 return NULL;
01095 } else if (cmd == CLI_GENERATE)
01096 return NULL;
01097
01098 if (a->argc > e->args + 1)
01099 return CLI_SHOWUSAGE;
01100 if (o->owner) {
01101 int i;
01102 struct ast_frame f = { AST_FRAME_DTMF, { 0 } };
01103 const char *digits;
01104
01105 if (a->argc == e->args) {
01106 ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n");
01107 return CLI_FAILURE;
01108 }
01109 digits = a->argv[e->args];
01110
01111 for (i = 0; i < strlen(digits); i++) {
01112 f.subclass.integer = digits[i];
01113 ast_queue_frame(o->owner, &f);
01114 }
01115 return CLI_SUCCESS;
01116 }
01117
01118 if (a->argc == e->args + 1)
01119 s = ast_ext_ctx(a->argv[e->args], &mye, &myc);
01120
01121 if (mye == NULL)
01122 mye = o->ext;
01123 if (myc == NULL)
01124 myc = o->ctx;
01125 if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
01126 o->hookstate = 1;
01127 oss_new(o, mye, myc, AST_STATE_RINGING, NULL);
01128 } else
01129 ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc);
01130 if (s)
01131 ast_free(s);
01132 return CLI_SUCCESS;
01133 }
01134
01135 static char *console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01136 {
01137 struct chan_oss_pvt *o = find_desc(oss_active);
01138 const char *s;
01139 int toggle = 0;
01140
01141 if (cmd == CLI_INIT) {
01142 e->command = "console {mute|unmute} [toggle]";
01143 e->usage =
01144 "Usage: console {mute|unmute} [toggle]\n"
01145 " Mute/unmute the microphone.\n";
01146 return NULL;
01147 } else if (cmd == CLI_GENERATE)
01148 return NULL;
01149
01150 if (a->argc > e->args)
01151 return CLI_SHOWUSAGE;
01152 if (a->argc == e->args) {
01153 if (strcasecmp(a->argv[e->args-1], "toggle"))
01154 return CLI_SHOWUSAGE;
01155 toggle = 1;
01156 }
01157 s = a->argv[e->args-2];
01158 if (!strcasecmp(s, "mute"))
01159 o->mute = toggle ? !o->mute : 1;
01160 else if (!strcasecmp(s, "unmute"))
01161 o->mute = toggle ? !o->mute : 0;
01162 else
01163 return CLI_SHOWUSAGE;
01164 ast_cli(a->fd, "Console mic is %s\n", o->mute ? "off" : "on");
01165 return CLI_SUCCESS;
01166 }
01167
01168 static char *console_transfer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01169 {
01170 struct chan_oss_pvt *o = find_desc(oss_active);
01171 struct ast_channel *b = NULL;
01172 char *tmp, *ext, *ctx;
01173
01174 switch (cmd) {
01175 case CLI_INIT:
01176 e->command = "console transfer";
01177 e->usage =
01178 "Usage: console transfer <extension>[@context]\n"
01179 " Transfers the currently connected call to the given extension (and\n"
01180 " context if specified)\n";
01181 return NULL;
01182 case CLI_GENERATE:
01183 return NULL;
01184 }
01185
01186 if (a->argc != 3)
01187 return CLI_SHOWUSAGE;
01188 if (o == NULL)
01189 return CLI_FAILURE;
01190 if (o->owner == NULL || (b = ast_bridged_channel(o->owner)) == NULL) {
01191 ast_cli(a->fd, "There is no call to transfer\n");
01192 return CLI_SUCCESS;
01193 }
01194
01195 tmp = ast_ext_ctx(a->argv[2], &ext, &ctx);
01196 if (ctx == NULL)
01197 ctx = ast_strdupa(ast_channel_context(o->owner));
01198 if (!ast_exists_extension(b, ctx, ext, 1,
01199 S_COR(ast_channel_caller(b)->id.number.valid, ast_channel_caller(b)->id.number.str, NULL))) {
01200 ast_cli(a->fd, "No such extension exists\n");
01201 } else {
01202 ast_cli(a->fd, "Whee, transferring %s to %s@%s.\n", ast_channel_name(b), ext, ctx);
01203 if (ast_async_goto(b, ctx, ext, 1))
01204 ast_cli(a->fd, "Failed to transfer :(\n");
01205 }
01206 if (tmp)
01207 ast_free(tmp);
01208 return CLI_SUCCESS;
01209 }
01210
01211 static char *console_active(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01212 {
01213 switch (cmd) {
01214 case CLI_INIT:
01215 e->command = "console {set|show} active [<device>]";
01216 e->usage =
01217 "Usage: console active [device]\n"
01218 " If used without a parameter, displays which device is the current\n"
01219 " console. If a device is specified, the console sound device is changed to\n"
01220 " the device specified.\n";
01221 return NULL;
01222 case CLI_GENERATE:
01223 return NULL;
01224 }
01225
01226 if (a->argc == 3)
01227 ast_cli(a->fd, "active console is [%s]\n", oss_active);
01228 else if (a->argc != 4)
01229 return CLI_SHOWUSAGE;
01230 else {
01231 struct chan_oss_pvt *o;
01232 if (strcmp(a->argv[3], "show") == 0) {
01233 for (o = oss_default.next; o; o = o->next)
01234 ast_cli(a->fd, "device [%s] exists\n", o->name);
01235 return CLI_SUCCESS;
01236 }
01237 o = find_desc(a->argv[3]);
01238 if (o == NULL)
01239 ast_cli(a->fd, "No device [%s] exists\n", a->argv[3]);
01240 else
01241 oss_active = o->name;
01242 }
01243 return CLI_SUCCESS;
01244 }
01245
01246
01247
01248
01249 static void store_boost(struct chan_oss_pvt *o, const char *s)
01250 {
01251 double boost = 0;
01252 if (sscanf(s, "%30lf", &boost) != 1) {
01253 ast_log(LOG_WARNING, "invalid boost <%s>\n", s);
01254 return;
01255 }
01256 if (boost < -BOOST_MAX) {
01257 ast_log(LOG_WARNING, "boost %s too small, using %d\n", s, -BOOST_MAX);
01258 boost = -BOOST_MAX;
01259 } else if (boost > BOOST_MAX) {
01260 ast_log(LOG_WARNING, "boost %s too large, using %d\n", s, BOOST_MAX);
01261 boost = BOOST_MAX;
01262 }
01263 boost = exp(log(10) * boost / 20) * BOOST_SCALE;
01264 o->boost = boost;
01265 ast_log(LOG_WARNING, "setting boost %s to %d\n", s, o->boost);
01266 }
01267
01268 static char *console_boost(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01269 {
01270 struct chan_oss_pvt *o = find_desc(oss_active);
01271
01272 switch (cmd) {
01273 case CLI_INIT:
01274 e->command = "console boost";
01275 e->usage =
01276 "Usage: console boost [boost in dB]\n"
01277 " Sets or display mic boost in dB\n";
01278 return NULL;
01279 case CLI_GENERATE:
01280 return NULL;
01281 }
01282
01283 if (a->argc == 2)
01284 ast_cli(a->fd, "boost currently %5.1f\n", 20 * log10(((double) o->boost / (double) BOOST_SCALE)));
01285 else if (a->argc == 3)
01286 store_boost(o, a->argv[2]);
01287 return CLI_SUCCESS;
01288 }
01289
01290 static struct ast_cli_entry cli_oss[] = {
01291 AST_CLI_DEFINE(console_answer, "Answer an incoming console call"),
01292 AST_CLI_DEFINE(console_hangup, "Hangup a call on the console"),
01293 AST_CLI_DEFINE(console_flash, "Flash a call on the console"),
01294 AST_CLI_DEFINE(console_dial, "Dial an extension on the console"),
01295 AST_CLI_DEFINE(console_mute, "Disable/Enable mic input"),
01296 AST_CLI_DEFINE(console_transfer, "Transfer a call to a different extension"),
01297 AST_CLI_DEFINE(console_cmd, "Generic console command"),
01298 AST_CLI_DEFINE(console_sendtext, "Send text to the remote device"),
01299 AST_CLI_DEFINE(console_autoanswer, "Sets/displays autoanswer"),
01300 AST_CLI_DEFINE(console_boost, "Sets/displays mic boost in dB"),
01301 AST_CLI_DEFINE(console_active, "Sets/displays active console"),
01302 };
01303
01304
01305
01306
01307
01308
01309 static void store_mixer(struct chan_oss_pvt *o, const char *s)
01310 {
01311 int i;
01312
01313 for (i = 0; i < strlen(s); i++) {
01314 if (!isalnum(s[i]) && strchr(" \t-/", s[i]) == NULL) {
01315 ast_log(LOG_WARNING, "Suspect char %c in mixer cmd, ignoring:\n\t%s\n", s[i], s);
01316 return;
01317 }
01318 }
01319 if (o->mixer_cmd)
01320 ast_free(o->mixer_cmd);
01321 o->mixer_cmd = ast_strdup(s);
01322 ast_log(LOG_WARNING, "setting mixer %s\n", s);
01323 }
01324
01325
01326
01327
01328 static void store_callerid(struct chan_oss_pvt *o, const char *s)
01329 {
01330 ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
01331 }
01332
01333 static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value)
01334 {
01335 CV_START(var, value);
01336
01337
01338 if (!ast_jb_read_conf(&global_jbconf, var, value))
01339 return;
01340
01341 if (!console_video_config(&o->env, var, value))
01342 return;
01343 CV_BOOL("autoanswer", o->autoanswer);
01344 CV_BOOL("autohangup", o->autohangup);
01345 CV_BOOL("overridecontext", o->overridecontext);
01346 CV_STR("device", o->device);
01347 CV_UINT("frags", o->frags);
01348 CV_UINT("debug", oss_debug);
01349 CV_UINT("queuesize", o->queuesize);
01350 CV_STR("context", o->ctx);
01351 CV_STR("language", o->language);
01352 CV_STR("mohinterpret", o->mohinterpret);
01353 CV_STR("extension", o->ext);
01354 CV_F("mixer", store_mixer(o, value));
01355 CV_F("callerid", store_callerid(o, value)) ;
01356 CV_F("boost", store_boost(o, value));
01357
01358 CV_END;
01359 }
01360
01361
01362
01363
01364 static struct chan_oss_pvt *store_config(struct ast_config *cfg, char *ctg)
01365 {
01366 struct ast_variable *v;
01367 struct chan_oss_pvt *o;
01368
01369 if (ctg == NULL) {
01370 o = &oss_default;
01371 ctg = "general";
01372 } else {
01373 if (!(o = ast_calloc(1, sizeof(*o))))
01374 return NULL;
01375 *o = oss_default;
01376
01377 if (strcmp(ctg, "general") == 0) {
01378 o->name = ast_strdup("dsp");
01379 oss_active = o->name;
01380 goto openit;
01381 }
01382 o->name = ast_strdup(ctg);
01383 }
01384
01385 strcpy(o->mohinterpret, "default");
01386
01387 o->lastopen = ast_tvnow();
01388
01389 for (v = ast_variable_browse(cfg, ctg); v; v = v->next) {
01390 store_config_core(o, v->name, v->value);
01391 }
01392 if (ast_strlen_zero(o->device))
01393 ast_copy_string(o->device, DEV_DSP, sizeof(o->device));
01394 if (o->mixer_cmd) {
01395 char *cmd;
01396
01397 if (ast_asprintf(&cmd, "mixer %s", o->mixer_cmd) >= 0) {
01398 ast_log(LOG_WARNING, "running [%s]\n", cmd);
01399 if (system(cmd) < 0) {
01400 ast_log(LOG_WARNING, "system() failed: %s\n", strerror(errno));
01401 }
01402 ast_free(cmd);
01403 }
01404 }
01405
01406
01407 if (get_gui_startup(o->env))
01408 console_video_start(o->env, NULL);
01409
01410 if (o == &oss_default)
01411 return NULL;
01412
01413 openit:
01414 #ifdef TRYOPEN
01415 if (setformat(o, O_RDWR) < 0) {
01416 ast_verb(1, "Device %s not detected\n", ctg);
01417 ast_verb(1, "Turn off OSS support by adding " "'noload=chan_oss.so' in /etc/asterisk/modules.conf\n");
01418 goto error;
01419 }
01420 if (o->duplex != M_FULL)
01421 ast_log(LOG_WARNING, "XXX I don't work right with non " "full-duplex sound cards XXX\n");
01422 #endif
01423
01424
01425 if (o != &oss_default) {
01426 o->next = oss_default.next;
01427 oss_default.next = o;
01428 }
01429 return o;
01430
01431 #ifdef TRYOPEN
01432 error:
01433 if (o != &oss_default)
01434 ast_free(o);
01435 return NULL;
01436 #endif
01437 }
01438
01439 static int load_module(void)
01440 {
01441 struct ast_config *cfg = NULL;
01442 char *ctg = NULL;
01443 struct ast_flags config_flags = { 0 };
01444 struct ast_format tmpfmt;
01445
01446
01447 memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
01448
01449
01450 if (!(cfg = ast_config_load(config, config_flags))) {
01451 ast_log(LOG_NOTICE, "Unable to load config %s\n", config);
01452 return AST_MODULE_LOAD_DECLINE;
01453 } else if (cfg == CONFIG_STATUS_FILEINVALID) {
01454 ast_log(LOG_ERROR, "Config file %s is in an invalid format. Aborting.\n", config);
01455 return AST_MODULE_LOAD_DECLINE;
01456 }
01457
01458 do {
01459 store_config(cfg, ctg);
01460 } while ( (ctg = ast_category_browse(cfg, ctg)) != NULL);
01461
01462 ast_config_destroy(cfg);
01463
01464 if (find_desc(oss_active) == NULL) {
01465 ast_log(LOG_NOTICE, "Device %s not found\n", oss_active);
01466
01467
01468 return AST_MODULE_LOAD_FAILURE;
01469 }
01470
01471 if (!(oss_tech.capabilities = ast_format_cap_alloc())) {
01472 return AST_MODULE_LOAD_FAILURE;
01473 }
01474 ast_format_cap_add(oss_tech.capabilities, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
01475
01476
01477
01478
01479 if (ast_channel_register(&oss_tech)) {
01480 ast_log(LOG_ERROR, "Unable to register channel type 'OSS'\n");
01481 return AST_MODULE_LOAD_DECLINE;
01482 }
01483
01484 ast_cli_register_multiple(cli_oss, ARRAY_LEN(cli_oss));
01485
01486 return AST_MODULE_LOAD_SUCCESS;
01487 }
01488
01489
01490 static int unload_module(void)
01491 {
01492 struct chan_oss_pvt *o, *next;
01493
01494 ast_channel_unregister(&oss_tech);
01495 ast_cli_unregister_multiple(cli_oss, ARRAY_LEN(cli_oss));
01496
01497 o = oss_default.next;
01498 while (o) {
01499 close(o->sounddev);
01500 if (o->owner)
01501 ast_softhangup(o->owner, AST_SOFTHANGUP_APPUNLOAD);
01502 if (o->owner)
01503 return -1;
01504 next = o->next;
01505 ast_free(o->name);
01506 ast_free(o);
01507 o = next;
01508 }
01509 oss_tech.capabilities = ast_format_cap_destroy(oss_tech.capabilities);
01510 return 0;
01511 }
01512
01513 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "OSS Console Channel Driver");