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: 377383 $")
00037
00038 #include <fcntl.h>
00039 #include <netinet/in.h>
00040 #include <sys/ioctl.h>
00041 #include <sys/mman.h>
00042 #include <sys/poll.h>
00043 #include <dahdi/user.h>
00044
00045 #include "asterisk/lock.h"
00046 #include "asterisk/translate.h"
00047 #include "asterisk/config.h"
00048 #include "asterisk/module.h"
00049 #include "asterisk/cli.h"
00050 #include "asterisk/channel.h"
00051 #include "asterisk/utils.h"
00052 #include "asterisk/linkedlists.h"
00053 #include "asterisk/ulaw.h"
00054
00055 #define BUFFER_SIZE 8000
00056
00057 #define G723_SAMPLES 240
00058 #define G729_SAMPLES 160
00059 #define ULAW_SAMPLES 160
00060
00061 #ifndef DAHDI_FORMAT_MAX_AUDIO
00062 #define DAHDI_FORMAT_G723_1 (1 << 0)
00063 #define DAHDI_FORMAT_GSM (1 << 1)
00064 #define DAHDI_FORMAT_ULAW (1 << 2)
00065 #define DAHDI_FORMAT_ALAW (1 << 3)
00066 #define DAHDI_FORMAT_G726 (1 << 4)
00067 #define DAHDI_FORMAT_ADPCM (1 << 5)
00068 #define DAHDI_FORMAT_SLINEAR (1 << 6)
00069 #define DAHDI_FORMAT_LPC10 (1 << 7)
00070 #define DAHDI_FORMAT_G729A (1 << 8)
00071 #define DAHDI_FORMAT_SPEEX (1 << 9)
00072 #define DAHDI_FORMAT_ILBC (1 << 10)
00073 #endif
00074
00075 static struct channel_usage {
00076 int total;
00077 int encoders;
00078 int decoders;
00079 } channels;
00080
00081 static char *handle_cli_transcoder_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00082
00083 static struct ast_cli_entry cli[] = {
00084 AST_CLI_DEFINE(handle_cli_transcoder_show, "Display DAHDI transcoder utilization.")
00085 };
00086
00087 struct format_map {
00088 unsigned int map[32][32];
00089 };
00090
00091 static struct format_map global_format_map = { { { 0 } } };
00092
00093 struct translator {
00094 struct ast_translator t;
00095 AST_LIST_ENTRY(translator) entry;
00096 };
00097
00098 static AST_LIST_HEAD_STATIC(translators, translator);
00099
00100 struct codec_dahdi_pvt {
00101 int fd;
00102 struct dahdi_transcoder_formats fmts;
00103 unsigned int softslin:1;
00104 unsigned int fake:2;
00105 uint16_t required_samples;
00106 uint16_t samples_in_buffer;
00107 uint16_t samples_written_to_hardware;
00108 uint8_t ulaw_buffer[1024];
00109 };
00110
00111
00112 static int ulawtolin(struct ast_trans_pvt *pvt, int samples)
00113 {
00114 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00115 int i = samples;
00116 uint8_t *src = &dahdip->ulaw_buffer[0];
00117 int16_t *dst = pvt->outbuf.i16 + pvt->datalen;
00118
00119
00120 while (i--) {
00121 *dst++ = AST_MULAW(*src++);
00122 }
00123
00124 return 0;
00125 }
00126
00127
00128 static int lintoulaw(struct ast_trans_pvt *pvt, struct ast_frame *f)
00129 {
00130 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00131 int i = f->samples;
00132 uint8_t *dst = &dahdip->ulaw_buffer[dahdip->samples_in_buffer];
00133 int16_t *src = f->data.ptr;
00134
00135 if (dahdip->samples_in_buffer + i > sizeof(dahdip->ulaw_buffer)) {
00136 ast_log(LOG_ERROR, "Out of buffer space!\n");
00137 return -i;
00138 }
00139
00140 while (i--) {
00141 *dst++ = AST_LIN2MU(*src++);
00142 }
00143
00144 dahdip->samples_in_buffer += f->samples;
00145 return 0;
00146 }
00147
00148 static char *handle_cli_transcoder_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00149 {
00150 struct channel_usage copy;
00151
00152 switch (cmd) {
00153 case CLI_INIT:
00154 e->command = "transcoder show";
00155 e->usage =
00156 "Usage: transcoder show\n"
00157 " Displays channel utilization of DAHDI transcoder(s).\n";
00158 return NULL;
00159 case CLI_GENERATE:
00160 return NULL;
00161 }
00162
00163 if (a->argc != 2)
00164 return CLI_SHOWUSAGE;
00165
00166 copy = channels;
00167
00168 if (copy.total == 0)
00169 ast_cli(a->fd, "No DAHDI transcoders found.\n");
00170 else
00171 ast_cli(a->fd, "%d/%d encoders/decoders of %d channels are in use.\n", copy.encoders, copy.decoders, copy.total);
00172
00173 return CLI_SUCCESS;
00174 }
00175
00176 static void dahdi_write_frame(struct codec_dahdi_pvt *dahdip, const uint8_t *buffer, const ssize_t count)
00177 {
00178 int res;
00179 if (!count) return;
00180 res = write(dahdip->fd, buffer, count);
00181 if (-1 == res) {
00182 ast_log(LOG_ERROR, "Failed to write to transcoder: %s\n", strerror(errno));
00183 }
00184 if (count != res) {
00185 ast_log(LOG_ERROR, "Requested write of %zd bytes, but only wrote %d bytes.\n", count, res);
00186 }
00187 }
00188
00189 static int dahdi_encoder_framein(struct ast_trans_pvt *pvt, struct ast_frame *f)
00190 {
00191 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00192
00193 if (!f->subclass.format.id) {
00194
00195 dahdip->fake = 2;
00196 pvt->samples = f->samples;
00197 return 0;
00198 }
00199
00200
00201
00202 if (dahdip->softslin) {
00203 if (lintoulaw(pvt, f)) {
00204 return -1;
00205 }
00206 } else {
00207
00208
00209
00210
00211 if (dahdip->samples_in_buffer + f->samples > sizeof(dahdip->ulaw_buffer)) {
00212 ast_log(LOG_ERROR, "Out of buffer space.\n");
00213 return -1;
00214 }
00215 memcpy(&dahdip->ulaw_buffer[dahdip->samples_in_buffer], f->data.ptr, f->samples);
00216 dahdip->samples_in_buffer += f->samples;
00217 }
00218
00219 while (dahdip->samples_in_buffer >= dahdip->required_samples) {
00220 dahdi_write_frame(dahdip, dahdip->ulaw_buffer, dahdip->required_samples);
00221 dahdip->samples_written_to_hardware += dahdip->required_samples;
00222 dahdip->samples_in_buffer -= dahdip->required_samples;
00223 if (dahdip->samples_in_buffer) {
00224
00225 memmove(dahdip->ulaw_buffer, &dahdip->ulaw_buffer[dahdip->required_samples],
00226 dahdip->samples_in_buffer);
00227 }
00228 }
00229 pvt->samples += f->samples;
00230 pvt->datalen = 0;
00231 return -1;
00232 }
00233
00234 static void dahdi_wait_for_packet(int fd)
00235 {
00236 struct pollfd p = {0};
00237 p.fd = fd;
00238 p.events = POLLIN;
00239 poll(&p, 1, 10);
00240 }
00241
00242 static struct ast_frame *dahdi_encoder_frameout(struct ast_trans_pvt *pvt)
00243 {
00244 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00245 int res;
00246
00247 if (2 == dahdip->fake) {
00248 dahdip->fake = 1;
00249 pvt->f.frametype = AST_FRAME_VOICE;
00250 ast_format_clear(&pvt->f.subclass.format);
00251 pvt->f.samples = dahdip->required_samples;
00252 pvt->f.data.ptr = NULL;
00253 pvt->f.offset = 0;
00254 pvt->f.datalen = 0;
00255 pvt->f.mallocd = 0;
00256 pvt->samples = 0;
00257
00258 return ast_frisolate(&pvt->f);
00259
00260 } else if (1 == dahdip->fake) {
00261 dahdip->fake = 0;
00262 return NULL;
00263 }
00264
00265 if (dahdip->samples_written_to_hardware >= dahdip->required_samples) {
00266 dahdi_wait_for_packet(dahdip->fd);
00267 }
00268
00269 res = read(dahdip->fd, pvt->outbuf.c + pvt->datalen, pvt->t->buf_size - pvt->datalen);
00270 if (-1 == res) {
00271 if (EWOULDBLOCK == errno) {
00272
00273 return NULL;
00274 } else {
00275 ast_log(LOG_ERROR, "Failed to read from transcoder: %s\n", strerror(errno));
00276 return NULL;
00277 }
00278 } else {
00279 pvt->f.datalen = res;
00280 pvt->f.frametype = AST_FRAME_VOICE;
00281 ast_format_copy(&pvt->f.subclass.format, &pvt->t->dst_format);
00282 pvt->f.mallocd = 0;
00283 pvt->f.offset = AST_FRIENDLY_OFFSET;
00284 pvt->f.src = pvt->t->name;
00285 pvt->f.data.ptr = pvt->outbuf.c;
00286 pvt->f.samples = ast_codec_get_samples(&pvt->f);
00287
00288 dahdip->samples_written_to_hardware =
00289 (dahdip->samples_written_to_hardware >= pvt->f.samples) ?
00290 dahdip->samples_written_to_hardware - pvt->f.samples : 0;
00291
00292 pvt->samples = 0;
00293 pvt->datalen = 0;
00294 return ast_frisolate(&pvt->f);
00295 }
00296
00297
00298 return NULL;
00299 }
00300
00301 static int dahdi_decoder_framein(struct ast_trans_pvt *pvt, struct ast_frame *f)
00302 {
00303 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00304
00305 if (!f->subclass.format.id) {
00306
00307 dahdip->fake = 2;
00308 pvt->samples = f->samples;
00309 return 0;
00310 }
00311
00312 if (!f->datalen) {
00313 if (f->samples != dahdip->required_samples) {
00314 ast_log(LOG_ERROR, "%d != %d %d\n", f->samples, dahdip->required_samples, f->datalen);
00315 }
00316 }
00317 dahdi_write_frame(dahdip, f->data.ptr, f->datalen);
00318 dahdip->samples_written_to_hardware += f->samples;
00319 pvt->samples += f->samples;
00320 pvt->datalen = 0;
00321 return -1;
00322 }
00323
00324 static struct ast_frame *dahdi_decoder_frameout(struct ast_trans_pvt *pvt)
00325 {
00326 int res;
00327 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00328
00329 if (2 == dahdip->fake) {
00330 dahdip->fake = 1;
00331 pvt->f.frametype = AST_FRAME_VOICE;
00332 ast_format_clear(&pvt->f.subclass.format);
00333 pvt->f.samples = dahdip->required_samples;
00334 pvt->f.data.ptr = NULL;
00335 pvt->f.offset = 0;
00336 pvt->f.datalen = 0;
00337 pvt->f.mallocd = 0;
00338 pvt->samples = 0;
00339 return ast_frisolate(&pvt->f);
00340 } else if (1 == dahdip->fake) {
00341 pvt->samples = 0;
00342 dahdip->fake = 0;
00343 return NULL;
00344 }
00345
00346 if (dahdip->samples_written_to_hardware >= ULAW_SAMPLES) {
00347 dahdi_wait_for_packet(dahdip->fd);
00348 }
00349
00350
00351 if (dahdip->softslin) {
00352 res = read(dahdip->fd, dahdip->ulaw_buffer, sizeof(dahdip->ulaw_buffer));
00353 } else {
00354 res = read(dahdip->fd, pvt->outbuf.c + pvt->datalen, pvt->t->buf_size - pvt->datalen);
00355 }
00356
00357 if (-1 == res) {
00358 if (EWOULDBLOCK == errno) {
00359
00360 return NULL;
00361 } else {
00362 ast_log(LOG_ERROR, "Failed to read from transcoder: %s\n", strerror(errno));
00363 return NULL;
00364 }
00365 } else {
00366 if (dahdip->softslin) {
00367 ulawtolin(pvt, res);
00368 pvt->f.datalen = res * 2;
00369 } else {
00370 pvt->f.datalen = res;
00371 }
00372 pvt->datalen = 0;
00373 pvt->f.frametype = AST_FRAME_VOICE;
00374 ast_format_copy(&pvt->f.subclass.format, &pvt->t->dst_format);
00375 pvt->f.mallocd = 0;
00376 pvt->f.offset = AST_FRIENDLY_OFFSET;
00377 pvt->f.src = pvt->t->name;
00378 pvt->f.data.ptr = pvt->outbuf.c;
00379 pvt->f.samples = res;
00380 pvt->samples = 0;
00381 dahdip->samples_written_to_hardware =
00382 (dahdip->samples_written_to_hardware >= res) ?
00383 dahdip->samples_written_to_hardware - res : 0;
00384
00385 return ast_frisolate(&pvt->f);
00386 }
00387
00388
00389 return NULL;
00390 }
00391
00392
00393 static void dahdi_destroy(struct ast_trans_pvt *pvt)
00394 {
00395 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00396
00397 switch (ast_format_id_from_old_bitfield(dahdip->fmts.dstfmt)) {
00398 case AST_FORMAT_G729A:
00399 case AST_FORMAT_G723_1:
00400 ast_atomic_fetchadd_int(&channels.encoders, -1);
00401 break;
00402 default:
00403 ast_atomic_fetchadd_int(&channels.decoders, -1);
00404 break;
00405 }
00406
00407 close(dahdip->fd);
00408 }
00409
00410 static int dahdi_translate(struct ast_trans_pvt *pvt, struct ast_format *dst_format, struct ast_format *src_format)
00411 {
00412
00413 int fd;
00414 struct codec_dahdi_pvt *dahdip = pvt->pvt;
00415 int flags;
00416 int tried_once = 0;
00417 const char *dev_filename = "/dev/dahdi/transcode";
00418
00419 if ((fd = open(dev_filename, O_RDWR)) < 0) {
00420 ast_log(LOG_ERROR, "Failed to open %s: %s\n", dev_filename, strerror(errno));
00421 return -1;
00422 }
00423
00424 dahdip->fmts.srcfmt = ast_format_to_old_bitfield(src_format);
00425 dahdip->fmts.dstfmt = ast_format_to_old_bitfield(dst_format);
00426
00427 ast_debug(1, "Opening transcoder channel from %s to %s.\n", ast_getformatname(src_format), ast_getformatname(dst_format));
00428
00429 retry:
00430 if (ioctl(fd, DAHDI_TC_ALLOCATE, &dahdip->fmts)) {
00431 if ((ENODEV == errno) && !tried_once) {
00432
00433
00434
00435
00436
00437
00438
00439
00440 if (AST_FORMAT_SLINEAR == ast_format_id_from_old_bitfield(dahdip->fmts.srcfmt)) {
00441 ast_debug(1, "Using soft_slin support on source\n");
00442 dahdip->softslin = 1;
00443 dahdip->fmts.srcfmt = ast_format_id_to_old_bitfield(AST_FORMAT_ULAW);
00444 } else if (AST_FORMAT_SLINEAR == ast_format_id_from_old_bitfield(dahdip->fmts.dstfmt)) {
00445 ast_debug(1, "Using soft_slin support on destination\n");
00446 dahdip->softslin = 1;
00447 dahdip->fmts.dstfmt = ast_format_id_to_old_bitfield(AST_FORMAT_ULAW);
00448 }
00449 tried_once = 1;
00450 goto retry;
00451 }
00452 ast_log(LOG_ERROR, "Unable to attach to transcoder: %s\n", strerror(errno));
00453 close(fd);
00454
00455 return -1;
00456 }
00457
00458 flags = fcntl(fd, F_GETFL);
00459 if (flags > - 1) {
00460 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK))
00461 ast_log(LOG_WARNING, "Could not set non-block mode!\n");
00462 }
00463
00464 dahdip->fd = fd;
00465
00466 dahdip->required_samples = ((dahdip->fmts.dstfmt|dahdip->fmts.srcfmt) & (ast_format_id_to_old_bitfield(AST_FORMAT_G723_1))) ? G723_SAMPLES : G729_SAMPLES;
00467
00468 switch (ast_format_id_from_old_bitfield(dahdip->fmts.dstfmt)) {
00469 case AST_FORMAT_G729A:
00470 ast_atomic_fetchadd_int(&channels.encoders, +1);
00471 break;
00472 case AST_FORMAT_G723_1:
00473 ast_atomic_fetchadd_int(&channels.encoders, +1);
00474 break;
00475 default:
00476 ast_atomic_fetchadd_int(&channels.decoders, +1);
00477 break;
00478 }
00479
00480 return 0;
00481 }
00482
00483 static int dahdi_new(struct ast_trans_pvt *pvt)
00484 {
00485 return dahdi_translate(pvt,
00486 &pvt->t->dst_format,
00487 &pvt->t->src_format);
00488 }
00489
00490 static struct ast_frame *fakesrc_sample(void)
00491 {
00492
00493 static struct ast_frame f = {
00494 .frametype = AST_FRAME_VOICE,
00495 .samples = 160,
00496 .src = __PRETTY_FUNCTION__
00497 };
00498
00499 return &f;
00500 }
00501
00502 static int is_encoder(struct translator *zt)
00503 {
00504 if ((zt->t.src_format.id == AST_FORMAT_ULAW) ||
00505 (zt->t.src_format.id == AST_FORMAT_ALAW) ||
00506 (zt->t.src_format.id == AST_FORMAT_SLINEAR)) {
00507 return 1;
00508 } else {
00509 return 0;
00510 }
00511 }
00512
00513 static int register_translator(int dst, int src)
00514 {
00515 struct translator *zt;
00516 int res;
00517 struct ast_format dst_format;
00518 struct ast_format src_format;
00519
00520 ast_format_from_old_bitfield(&dst_format, (1 << dst));
00521 ast_format_from_old_bitfield(&src_format, (1 << src));
00522
00523 if (!(zt = ast_calloc(1, sizeof(*zt)))) {
00524 return -1;
00525 }
00526
00527 snprintf((char *) (zt->t.name), sizeof(zt->t.name), "zap%sto%s",
00528 ast_getformatname(&src_format), ast_getformatname(&dst_format));
00529 ast_format_copy(&zt->t.src_format, &src_format);
00530 ast_format_copy(&zt->t.dst_format, &dst_format);
00531 zt->t.buf_size = BUFFER_SIZE;
00532 if (is_encoder(zt)) {
00533 zt->t.framein = dahdi_encoder_framein;
00534 zt->t.frameout = dahdi_encoder_frameout;
00535 } else {
00536 zt->t.framein = dahdi_decoder_framein;
00537 zt->t.frameout = dahdi_decoder_frameout;
00538 }
00539 zt->t.destroy = dahdi_destroy;
00540 zt->t.buffer_samples = 0;
00541 zt->t.newpvt = dahdi_new;
00542 zt->t.sample = fakesrc_sample;
00543 zt->t.native_plc = 0;
00544
00545 zt->t.desc_size = sizeof(struct codec_dahdi_pvt);
00546 if ((res = ast_register_translator(&zt->t))) {
00547 ast_free(zt);
00548 return -1;
00549 }
00550
00551 AST_LIST_LOCK(&translators);
00552 AST_LIST_INSERT_HEAD(&translators, zt, entry);
00553 AST_LIST_UNLOCK(&translators);
00554
00555 global_format_map.map[dst][src] = 1;
00556
00557 return res;
00558 }
00559
00560 static void drop_translator(int dst, int src)
00561 {
00562 struct translator *cur;
00563
00564 AST_LIST_LOCK(&translators);
00565 AST_LIST_TRAVERSE_SAFE_BEGIN(&translators, cur, entry) {
00566 if (cur->t.src_format.id != ast_format_id_from_old_bitfield((1 << src)))
00567 continue;
00568
00569 if (cur->t.dst_format.id != ast_format_id_from_old_bitfield((1 << dst)))
00570 continue;
00571
00572 AST_LIST_REMOVE_CURRENT(entry);
00573 ast_unregister_translator(&cur->t);
00574 ast_free(cur);
00575 global_format_map.map[dst][src] = 0;
00576 break;
00577 }
00578 AST_LIST_TRAVERSE_SAFE_END;
00579 AST_LIST_UNLOCK(&translators);
00580 }
00581
00582 static void unregister_translators(void)
00583 {
00584 struct translator *cur;
00585
00586 AST_LIST_LOCK(&translators);
00587 while ((cur = AST_LIST_REMOVE_HEAD(&translators, entry))) {
00588 ast_unregister_translator(&cur->t);
00589 ast_free(cur);
00590 }
00591 AST_LIST_UNLOCK(&translators);
00592 }
00593
00594 static void build_translators(struct format_map *map, unsigned int dstfmts, unsigned int srcfmts)
00595 {
00596 unsigned int src, dst;
00597
00598 for (src = 0; src < 32; src++) {
00599 for (dst = 0; dst < 32; dst++) {
00600 if (!(srcfmts & (1 << src)))
00601 continue;
00602
00603 if (!(dstfmts & (1 << dst)))
00604 continue;
00605
00606 if (global_format_map.map[dst][src])
00607 continue;
00608
00609 if (!register_translator(dst, src))
00610 map->map[dst][src] = 1;
00611 }
00612 }
00613 }
00614
00615 static int find_transcoders(void)
00616 {
00617 struct dahdi_transcoder_info info = { 0, };
00618 struct format_map map = { { { 0 } } };
00619 int fd;
00620 unsigned int x, y;
00621
00622 if ((fd = open("/dev/dahdi/transcode", O_RDWR)) < 0) {
00623 ast_log(LOG_ERROR, "Failed to open /dev/dahdi/transcode: %s\n", strerror(errno));
00624 return 0;
00625 }
00626
00627 for (info.tcnum = 0; !ioctl(fd, DAHDI_TC_GETINFO, &info); info.tcnum++) {
00628 ast_verb(2, "Found transcoder '%s'.\n", info.name);
00629
00630
00631
00632
00633
00634
00635
00636 if (info.dstfmts & (DAHDI_FORMAT_ULAW | DAHDI_FORMAT_ALAW)) {
00637 info.dstfmts |= DAHDI_FORMAT_SLINEAR;
00638 info.dstfmts &= ~(DAHDI_FORMAT_ULAW | DAHDI_FORMAT_ALAW);
00639 }
00640 if (info.srcfmts & (DAHDI_FORMAT_ULAW | DAHDI_FORMAT_ALAW)) {
00641 info.srcfmts |= DAHDI_FORMAT_SLINEAR;
00642 info.srcfmts &= ~(DAHDI_FORMAT_ULAW | DAHDI_FORMAT_ALAW);
00643 }
00644
00645 build_translators(&map, info.dstfmts, info.srcfmts);
00646 ast_atomic_fetchadd_int(&channels.total, info.numchannels / 2);
00647
00648 }
00649
00650 close(fd);
00651
00652 if (!info.tcnum) {
00653 ast_verb(2, "No hardware transcoders found.\n");
00654 }
00655
00656 for (x = 0; x < 32; x++) {
00657 for (y = 0; y < 32; y++) {
00658 if (!map.map[x][y] && global_format_map.map[x][y])
00659 drop_translator(x, y);
00660 }
00661 }
00662
00663 return 0;
00664 }
00665
00666 static int reload(void)
00667 {
00668 return AST_MODULE_LOAD_SUCCESS;
00669 }
00670
00671 static int unload_module(void)
00672 {
00673 ast_cli_unregister_multiple(cli, ARRAY_LEN(cli));
00674 unregister_translators();
00675
00676 return 0;
00677 }
00678
00679 static int load_module(void)
00680 {
00681 ast_ulaw_init();
00682 find_transcoders();
00683 ast_cli_register_multiple(cli, ARRAY_LEN(cli));
00684 return AST_MODULE_LOAD_SUCCESS;
00685 }
00686
00687 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Generic DAHDI Transcoder Codec Translator",
00688 .load = load_module,
00689 .unload = unload_module,
00690 .reload = reload,
00691 );