Fri May 26 01:45:34 2006

Asterisk developer's documentation


format_ogg_vorbis.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2005, Jeff Ollie
00005  *
00006  * See http://www.asterisk.org for more information about
00007  * the Asterisk project. Please do not directly contact
00008  * any of the maintainers of this project for assistance;
00009  * the project provides a web site, mailing lists and IRC
00010  * channels for your use.
00011  *
00012  * This program is free software, distributed under the terms of
00013  * the GNU General Public License Version 2. See the LICENSE file
00014  * at the top of the source tree.
00015  */
00016 
00017 /*! \file
00018  *
00019  * \brief OGG/Vorbis streams.
00020  * \arg File name extension: ogg
00021  * \ingroup formats
00022  */
00023  
00024 #include <sys/types.h>
00025 #include <netinet/in.h>
00026 #include <arpa/inet.h>
00027 #include <stdlib.h>
00028 #include <sys/time.h>
00029 #include <stdio.h>
00030 #include <unistd.h>
00031 #include <errno.h>
00032 #include <string.h>
00033 
00034 #include <vorbis/codec.h>
00035 #include <vorbis/vorbisenc.h>
00036 
00037 #ifdef _WIN32
00038 #include <io.h>
00039 #include <fcntl.h>
00040 #endif
00041 
00042 #include "asterisk.h"
00043 
00044 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 7221 $")
00045 
00046 #include "asterisk/lock.h"
00047 #include "asterisk/channel.h"
00048 #include "asterisk/file.h"
00049 #include "asterisk/logger.h"
00050 #include "asterisk/module.h"
00051 
00052 #define SAMPLES_MAX 160
00053 #define BLOCK_SIZE 4096
00054 
00055 
00056 struct ast_filestream {
00057    void *reserved[AST_RESERVED_POINTERS];
00058 
00059    FILE *f;
00060 
00061    /* structures for handling the Ogg container */
00062    ogg_sync_state  oy;
00063    ogg_stream_state os;
00064    ogg_page  og;
00065    ogg_packet   op;
00066    
00067    /* structures for handling Vorbis audio data */
00068    vorbis_info  vi;
00069    vorbis_comment  vc;
00070    vorbis_dsp_state vd;
00071    vorbis_block    vb;
00072    
00073    /*! \brief Indicates whether this filestream is set up for reading or writing. */
00074    int writing;
00075 
00076    /*! \brief Indicates whether an End of Stream condition has been detected. */
00077    int eos;
00078 
00079    /*! \brief Buffer to hold audio data. */
00080    short buffer[SAMPLES_MAX];
00081 
00082    /*! \brief Asterisk frame object. */
00083    struct ast_frame fr;
00084    char waste[AST_FRIENDLY_OFFSET];
00085    char empty;
00086 };
00087 
00088 AST_MUTEX_DEFINE_STATIC(ogg_vorbis_lock);
00089 static int glistcnt = 0;
00090 
00091 static char *name = "ogg_vorbis";
00092 static char *desc = "OGG/Vorbis audio";
00093 static char *exts = "ogg";
00094 
00095 /*!
00096  * \brief Create a new OGG/Vorbis filestream and set it up for reading.
00097  * \param f File that points to on disk storage of the OGG/Vorbis data.
00098  * \return The new filestream.
00099  */
00100 static struct ast_filestream *ogg_vorbis_open(FILE *f)
00101 {
00102    int i;
00103    int bytes;
00104    int result;
00105    char **ptr;
00106    char *buffer;
00107 
00108    struct ast_filestream *tmp;
00109 
00110    if((tmp = malloc(sizeof(struct ast_filestream)))) {
00111       memset(tmp, 0, sizeof(struct ast_filestream));
00112 
00113       tmp->writing = 0;
00114       tmp->f = f;
00115 
00116       ogg_sync_init(&tmp->oy);
00117 
00118       buffer = ogg_sync_buffer(&tmp->oy, BLOCK_SIZE);
00119       bytes = fread(buffer, 1, BLOCK_SIZE, f);
00120       ogg_sync_wrote(&tmp->oy, bytes);
00121 
00122       result = ogg_sync_pageout(&tmp->oy, &tmp->og);
00123       if(result != 1) {
00124          if(bytes < BLOCK_SIZE) {
00125             ast_log(LOG_ERROR, "Run out of data...\n");
00126          } else {
00127             ast_log(LOG_ERROR, "Input does not appear to be an Ogg bitstream.\n");
00128          }
00129          fclose(f);
00130          ogg_sync_clear(&tmp->oy);
00131          free(tmp);
00132          return NULL;
00133       }
00134       
00135       ogg_stream_init(&tmp->os, ogg_page_serialno(&tmp->og));
00136       vorbis_info_init(&tmp->vi);
00137       vorbis_comment_init(&tmp->vc);
00138 
00139       if(ogg_stream_pagein(&tmp->os, &tmp->og) < 0) { 
00140          ast_log(LOG_ERROR, "Error reading first page of Ogg bitstream data.\n");
00141          fclose(f);
00142          ogg_stream_clear(&tmp->os);
00143          vorbis_comment_clear(&tmp->vc);
00144          vorbis_info_clear(&tmp->vi);
00145          ogg_sync_clear(&tmp->oy);
00146          free(tmp);
00147          return NULL;
00148       }
00149       
00150       if(ogg_stream_packetout(&tmp->os, &tmp->op) != 1) { 
00151          ast_log(LOG_ERROR, "Error reading initial header packet.\n");
00152          fclose(f);
00153          ogg_stream_clear(&tmp->os);
00154          vorbis_comment_clear(&tmp->vc);
00155          vorbis_info_clear(&tmp->vi);
00156          ogg_sync_clear(&tmp->oy);
00157          free(tmp);
00158          return NULL;
00159       }
00160       
00161       if(vorbis_synthesis_headerin(&tmp->vi, &tmp->vc, &tmp->op) < 0) { 
00162          ast_log(LOG_ERROR, "This Ogg bitstream does not contain Vorbis audio data.\n");
00163          fclose(f);
00164          ogg_stream_clear(&tmp->os);
00165          vorbis_comment_clear(&tmp->vc);
00166          vorbis_info_clear(&tmp->vi);
00167          ogg_sync_clear(&tmp->oy);
00168          free(tmp);
00169          return NULL;
00170       }
00171       
00172       i = 0;
00173       while(i < 2) {
00174          while(i < 2){
00175             result = ogg_sync_pageout(&tmp->oy, &tmp->og);
00176             if(result == 0)
00177                break;
00178             if(result == 1) {
00179                ogg_stream_pagein(&tmp->os, &tmp->og);
00180                while(i < 2) {
00181                   result = ogg_stream_packetout(&tmp->os,&tmp->op);
00182                   if(result == 0)
00183                      break;
00184                   if(result < 0) {
00185                      ast_log(LOG_ERROR, "Corrupt secondary header.  Exiting.\n");
00186                      fclose(f);
00187                      ogg_stream_clear(&tmp->os);
00188                      vorbis_comment_clear(&tmp->vc);
00189                      vorbis_info_clear(&tmp->vi);
00190                      ogg_sync_clear(&tmp->oy);
00191                      free(tmp);
00192                      return NULL;
00193                   }
00194                   vorbis_synthesis_headerin(&tmp->vi, &tmp->vc, &tmp->op);
00195                   i++;
00196                }
00197             }
00198          }
00199 
00200          buffer = ogg_sync_buffer(&tmp->oy, BLOCK_SIZE);
00201          bytes = fread(buffer, 1, BLOCK_SIZE, f);
00202          if(bytes == 0 && i < 2) {
00203             ast_log(LOG_ERROR, "End of file before finding all Vorbis headers!\n");
00204             fclose(f);
00205             ogg_stream_clear(&tmp->os);
00206             vorbis_comment_clear(&tmp->vc);
00207             vorbis_info_clear(&tmp->vi);
00208             ogg_sync_clear(&tmp->oy);
00209             free(tmp);
00210             return NULL;
00211          }
00212          ogg_sync_wrote(&tmp->oy, bytes);
00213       }
00214       
00215       ptr = tmp->vc.user_comments;
00216       while(*ptr){
00217          ast_log(LOG_DEBUG, "OGG/Vorbis comment: %s\n", *ptr);
00218          ++ptr;
00219       }
00220       ast_log(LOG_DEBUG, "OGG/Vorbis bitstream is %d channel, %ldHz\n", tmp->vi.channels, tmp->vi.rate);
00221       ast_log(LOG_DEBUG, "OGG/Vorbis file encoded by: %s\n", tmp->vc.vendor);
00222 
00223       if(tmp->vi.channels != 1) {
00224          ast_log(LOG_ERROR, "Only monophonic OGG/Vorbis files are currently supported!\n");
00225          ogg_stream_clear(&tmp->os);
00226          vorbis_comment_clear(&tmp->vc);
00227          vorbis_info_clear(&tmp->vi);
00228          ogg_sync_clear(&tmp->oy);
00229          free(tmp);
00230          return NULL;
00231       }
00232       
00233 
00234       if(tmp->vi.rate != 8000) {
00235          ast_log(LOG_ERROR, "Only 8000Hz OGG/Vorbis files are currently supported!\n");
00236          fclose(f);
00237          ogg_stream_clear(&tmp->os);
00238          vorbis_block_clear(&tmp->vb);
00239          vorbis_dsp_clear(&tmp->vd);
00240          vorbis_comment_clear(&tmp->vc);
00241          vorbis_info_clear(&tmp->vi);
00242          ogg_sync_clear(&tmp->oy);
00243          free(tmp);
00244          return NULL;
00245       }
00246       
00247       vorbis_synthesis_init(&tmp->vd, &tmp->vi);
00248       vorbis_block_init(&tmp->vd, &tmp->vb);
00249 
00250       if(ast_mutex_lock(&ogg_vorbis_lock)) {
00251          ast_log(LOG_WARNING, "Unable to lock ogg_vorbis list\n");
00252          fclose(f);
00253          ogg_stream_clear(&tmp->os);
00254          vorbis_block_clear(&tmp->vb);
00255          vorbis_dsp_clear(&tmp->vd);
00256          vorbis_comment_clear(&tmp->vc);
00257          vorbis_info_clear(&tmp->vi);
00258          ogg_sync_clear(&tmp->oy);
00259          free(tmp);
00260          return NULL;
00261       }
00262       glistcnt++;
00263       ast_mutex_unlock(&ogg_vorbis_lock);
00264       ast_update_use_count();
00265    }
00266    return tmp;
00267 }
00268 
00269 /*!
00270  * \brief Create a new OGG/Vorbis filestream and set it up for writing.
00271  * \param f File pointer that points to on-disk storage.
00272  * \param comment Comment that should be embedded in the OGG/Vorbis file.
00273  * \return A new filestream.
00274  */
00275 static struct ast_filestream *ogg_vorbis_rewrite(FILE *f, const char *comment)
00276 {
00277    ogg_packet header;
00278    ogg_packet header_comm;
00279    ogg_packet header_code;
00280 
00281    struct ast_filestream *tmp;
00282 
00283    if((tmp = malloc(sizeof(struct ast_filestream)))) {
00284       memset(tmp, 0, sizeof(struct ast_filestream));
00285 
00286       tmp->writing = 1;
00287       tmp->f = f;
00288 
00289       vorbis_info_init(&tmp->vi);
00290 
00291       if(vorbis_encode_init_vbr(&tmp->vi, 1, 8000, 0.4)) {
00292          ast_log(LOG_ERROR, "Unable to initialize Vorbis encoder!\n");
00293          free(tmp);
00294          return NULL;
00295       }
00296 
00297       vorbis_comment_init(&tmp->vc);
00298       vorbis_comment_add_tag(&tmp->vc, "ENCODER", "Asterisk PBX");
00299       if(comment)
00300          vorbis_comment_add_tag(&tmp->vc, "COMMENT", (char *) comment);
00301 
00302       vorbis_analysis_init(&tmp->vd, &tmp->vi);
00303       vorbis_block_init(&tmp->vd, &tmp->vb);
00304 
00305       ogg_stream_init(&tmp->os, rand());
00306 
00307       vorbis_analysis_headerout(&tmp->vd, &tmp->vc, &header, &header_comm, &header_code);
00308       ogg_stream_packetin(&tmp->os, &header);                     
00309       ogg_stream_packetin(&tmp->os, &header_comm);
00310       ogg_stream_packetin(&tmp->os, &header_code);
00311 
00312       while(!tmp->eos) {
00313          if(ogg_stream_flush(&tmp->os, &tmp->og) == 0)
00314             break;
00315          fwrite(tmp->og.header, 1, tmp->og.header_len, tmp->f);
00316          fwrite(tmp->og.body, 1, tmp->og.body_len, tmp->f);
00317          if(ogg_page_eos(&tmp->og))
00318             tmp->eos = 1;
00319       }
00320 
00321       if(ast_mutex_lock(&ogg_vorbis_lock)) {
00322          ast_log(LOG_WARNING, "Unable to lock ogg_vorbis list\n");
00323          fclose(f);
00324          ogg_stream_clear(&tmp->os);
00325          vorbis_block_clear(&tmp->vb);
00326          vorbis_dsp_clear(&tmp->vd);
00327          vorbis_comment_clear(&tmp->vc);
00328          vorbis_info_clear(&tmp->vi);
00329          free(tmp);
00330          return NULL;
00331       }
00332       glistcnt++;
00333       ast_mutex_unlock(&ogg_vorbis_lock);
00334       ast_update_use_count();
00335    }
00336    return tmp;
00337 }
00338 
00339 /*!
00340  * \brief Write out any pending encoded data.
00341  * \param s A OGG/Vorbis filestream.
00342  */
00343 static void write_stream(struct ast_filestream *s)
00344 {
00345    while (vorbis_analysis_blockout(&s->vd, &s->vb) == 1) {
00346       vorbis_analysis(&s->vb, NULL);
00347       vorbis_bitrate_addblock(&s->vb);
00348       
00349       while (vorbis_bitrate_flushpacket(&s->vd, &s->op)) {
00350          ogg_stream_packetin(&s->os, &s->op);
00351          while (!s->eos) {
00352             if(ogg_stream_pageout(&s->os, &s->og) == 0) {
00353                break;
00354             }
00355             fwrite(s->og.header, 1, s->og.header_len, s->f);
00356             fwrite(s->og.body, 1, s->og.body_len, s->f);
00357             if(ogg_page_eos(&s->og)) {
00358                s->eos = 1;
00359             }
00360          }
00361       }
00362    }
00363 }
00364 
00365 /*!
00366  * \brief Write audio data from a frame to an OGG/Vorbis filestream.
00367  * \param s A OGG/Vorbis filestream.
00368  * \param f An frame containing audio to be written to the filestream.
00369  * \return -1 ifthere was an error, 0 on success.
00370  */
00371 static int ogg_vorbis_write(struct ast_filestream *s, struct ast_frame *f)
00372 {
00373    int i;
00374    float **buffer;
00375    short *data;
00376 
00377    if(!s->writing) {
00378       ast_log(LOG_ERROR, "This stream is not set up for writing!\n");
00379       return -1;
00380    }
00381 
00382    if(f->frametype != AST_FRAME_VOICE) {
00383       ast_log(LOG_WARNING, "Asked to write non-voice frame!\n");
00384       return -1;
00385    }
00386    if(f->subclass != AST_FORMAT_SLINEAR) {
00387       ast_log(LOG_WARNING, "Asked to write non-SLINEAR frame (%d)!\n", f->subclass);
00388       return -1;
00389    }
00390    if(!f->datalen)
00391       return -1;
00392 
00393    data = (short *) f->data;
00394 
00395    buffer = vorbis_analysis_buffer(&s->vd, f->samples);
00396 
00397    for (i = 0; i < f->samples; i++) {
00398       buffer[0][i] = data[i]/32768.f;
00399    }
00400 
00401    vorbis_analysis_wrote(&s->vd, f->samples);
00402 
00403    write_stream(s);
00404 
00405    return 0;
00406 }
00407 
00408 /*!
00409  * \brief Close a OGG/Vorbis filestream.
00410  * \param s A OGG/Vorbis filestream.
00411  */
00412 static void ogg_vorbis_close(struct ast_filestream *s)
00413 {
00414    if(ast_mutex_lock(&ogg_vorbis_lock)) {
00415       ast_log(LOG_WARNING, "Unable to lock ogg_vorbis list\n");
00416       return;
00417    }
00418    glistcnt--;
00419    ast_mutex_unlock(&ogg_vorbis_lock);
00420    ast_update_use_count();
00421 
00422    if(s->writing) {
00423       /* Tell the Vorbis encoder that the stream is finished
00424        * and write out the rest of the data */
00425       vorbis_analysis_wrote(&s->vd, 0);
00426       write_stream(s);
00427    }
00428 
00429    ogg_stream_clear(&s->os);
00430    vorbis_block_clear(&s->vb);
00431    vorbis_dsp_clear(&s->vd);
00432    vorbis_comment_clear(&s->vc);
00433    vorbis_info_clear(&s->vi);
00434 
00435    if(s->writing) {
00436       ogg_sync_clear(&s->oy);
00437    }
00438    
00439    fclose(s->f);
00440    free(s);
00441 }
00442 
00443 /*!
00444  * \brief Get audio data.
00445  * \param s An OGG/Vorbis filestream.
00446  * \param pcm Pointer to a buffere to store audio data in.
00447  */
00448 
00449 static int read_samples(struct ast_filestream *s, float ***pcm)
00450 {
00451    int samples_in;
00452    int result;
00453    char *buffer;
00454    int bytes;
00455 
00456    while (1) {
00457       samples_in = vorbis_synthesis_pcmout(&s->vd, pcm);
00458       if(samples_in > 0) {
00459          return samples_in;
00460       }
00461       
00462       /* The Vorbis decoder needs more data... */
00463       /* See ifOGG has any packets in the current page for the Vorbis decoder. */
00464       result = ogg_stream_packetout(&s->os, &s->op);
00465       if(result > 0) {
00466          /* Yes OGG had some more packets for the Vorbis decoder. */
00467          if(vorbis_synthesis(&s->vb, &s->op) == 0) {
00468             vorbis_synthesis_blockin(&s->vd, &s->vb);
00469          }
00470          
00471          continue;
00472       }
00473 
00474       if(result < 0)
00475          ast_log(LOG_WARNING, "Corrupt or missing data at this page position; continuing...\n");
00476       
00477       /* No more packets left in the current page... */
00478 
00479       if(s->eos) {
00480          /* No more pages left in the stream */
00481          return -1;
00482       }
00483 
00484       while (!s->eos) {
00485          /* See ifOGG has any pages in it's internal buffers */
00486          result = ogg_sync_pageout(&s->oy, &s->og);
00487          if(result > 0) {
00488             /* Yes, OGG has more pages in it's internal buffers,
00489                add the page to the stream state */
00490             result = ogg_stream_pagein(&s->os, &s->og);
00491             if(result == 0) {
00492                /* Yes, got a new,valid page */
00493                if(ogg_page_eos(&s->og)) {
00494                   s->eos = 1;
00495                }
00496                break;
00497             }
00498             ast_log(LOG_WARNING, "Invalid page in the bitstream; continuing...\n");
00499          }
00500          
00501          if(result < 0)
00502             ast_log(LOG_WARNING, "Corrupt or missing data in bitstream; continuing...\n");
00503 
00504          /* No, we need to read more data from the file descrptor */
00505          /* get a buffer from OGG to read the data into */
00506          buffer = ogg_sync_buffer(&s->oy, BLOCK_SIZE);
00507          /* read more data from the file descriptor */
00508          bytes = fread(buffer, 1, BLOCK_SIZE, s->f);
00509          /* Tell OGG how many bytes we actually read into the buffer */
00510          ogg_sync_wrote(&s->oy, bytes);
00511          if(bytes == 0) {
00512             s->eos = 1;
00513          }
00514       }
00515    }
00516 }
00517 
00518 /*!
00519  * \brief Read a frame full of audio data from the filestream.
00520  * \param s The filestream.
00521  * \param whennext Number of sample times to schedule the next call.
00522  * \return A pointer to a frame containing audio data or NULL ifthere is no more audio data.
00523  */
00524 static struct ast_frame *ogg_vorbis_read(struct ast_filestream *s, int *whennext)
00525 {
00526    int clipflag = 0;
00527    int i;
00528    int j;
00529    float **pcm;
00530    float *mono;
00531    double accumulator[SAMPLES_MAX];
00532    int val;
00533    int samples_in;
00534    int samples_out = 0;
00535 
00536    while (1) {
00537       /* See ifwe have filled up an audio frame yet */
00538       if(samples_out == SAMPLES_MAX)
00539          break;
00540 
00541       /* See ifVorbis decoder has some audio data for us ... */
00542       samples_in = read_samples(s, &pcm);
00543       if(samples_in <= 0)
00544          break;
00545 
00546       /* Got some audio data from Vorbis... */
00547       /* Convert the float audio data to 16-bit signed linear */
00548       
00549       clipflag = 0;
00550 
00551       samples_in = samples_in < (SAMPLES_MAX - samples_out) ? samples_in : (SAMPLES_MAX - samples_out);
00552   
00553       for(j = 0; j < samples_in; j++)
00554          accumulator[j] = 0.0;
00555 
00556       for(i = 0; i < s->vi.channels; i++) {
00557          mono = pcm[i];
00558          for (j = 0; j < samples_in; j++) {
00559             accumulator[j] += mono[j];
00560          }
00561       }
00562 
00563       for (j = 0; j < samples_in; j++) {
00564          val =  accumulator[j] * 32767.0 / s->vi.channels;
00565          if(val > 32767) {
00566             val = 32767;
00567             clipflag = 1;
00568          }
00569          if(val < -32768) {
00570             val = -32768;
00571             clipflag = 1;
00572          }
00573          s->buffer[samples_out + j] = val;
00574       }
00575          
00576       if(clipflag)
00577          ast_log(LOG_WARNING, "Clipping in frame %ld\n", (long)(s->vd.sequence));
00578       
00579       /* Tell the Vorbis decoder how many samples we actually used. */
00580       vorbis_synthesis_read(&s->vd, samples_in);
00581       samples_out += samples_in;
00582    }
00583 
00584    if(samples_out > 0) {
00585       s->fr.frametype = AST_FRAME_VOICE;
00586       s->fr.subclass = AST_FORMAT_SLINEAR;
00587       s->fr.offset = AST_FRIENDLY_OFFSET;
00588       s->fr.datalen = samples_out * 2;
00589       s->fr.data = s->buffer;
00590       s->fr.src = name;
00591       s->fr.mallocd = 0;
00592       s->fr.samples = samples_out;
00593       *whennext = samples_out;
00594       
00595       return &s->fr;
00596    } else {
00597       return NULL;
00598    }
00599 }
00600 
00601 /*!
00602  * \brief Trucate an OGG/Vorbis filestream.
00603  * \param s The filestream to truncate.
00604  * \return 0 on success, -1 on failure.
00605  */
00606 
00607 static int ogg_vorbis_trunc(struct ast_filestream *s)
00608 {
00609    ast_log(LOG_WARNING, "Truncation is not supported on OGG/Vorbis streams!\n");
00610    return -1;
00611 }
00612 
00613 /*!
00614  * \brief Seek to a specific position in an OGG/Vorbis filestream.
00615  * \param s The filestream to truncate.
00616  * \param sample_offset New position for the filestream, measured in 8KHz samples.
00617  * \param whence Location to measure 
00618  * \return 0 on success, -1 on failure.
00619  */
00620 
00621 static int ogg_vorbis_seek(struct ast_filestream *s, long sample_offset, int whence) {
00622    ast_log(LOG_WARNING, "Seeking is not supported on OGG/Vorbis streams!\n");
00623    return -1;
00624 }
00625 
00626 static long ogg_vorbis_tell(struct ast_filestream *s) {
00627    ast_log(LOG_WARNING, "Telling is not supported on OGG/Vorbis streams!\n");
00628    return -1;
00629 }
00630 
00631 static char *ogg_vorbis_getcomment(struct ast_filestream *s) {
00632    ast_log(LOG_WARNING, "Getting comments is not supported on OGG/Vorbis streams!\n");
00633    return NULL;
00634 }
00635 
00636 int load_module()
00637 {
00638    return ast_format_register(name, exts, AST_FORMAT_SLINEAR,
00639                ogg_vorbis_open,
00640                ogg_vorbis_rewrite,
00641                ogg_vorbis_write,
00642                ogg_vorbis_seek,
00643                ogg_vorbis_trunc,
00644                ogg_vorbis_tell,
00645                ogg_vorbis_read,
00646                ogg_vorbis_close,
00647                ogg_vorbis_getcomment);
00648 }
00649 
00650 int unload_module()
00651 {
00652    return ast_format_unregister(name);
00653 }  
00654 
00655 int usecount()
00656 {
00657    return glistcnt;
00658 }
00659 
00660 char *description()
00661 {
00662    return desc;
00663 }
00664 
00665 
00666 char *key()
00667 {
00668    return ASTERISK_GPL_KEY;
00669 }
00670 
00671 /*
00672 Local Variables:
00673 mode: C
00674 c-file-style: "linux"
00675 indent-tabs-mode: t
00676 End:
00677 */

Generated on Fri May 26 01:45:34 2006 for Asterisk - the Open Source PBX by  doxygen 1.4.6