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