support new format for musiconhold.conf (issue #4908)
authorKevin P. Fleming <kpfleming@digium.com>
Mon, 22 Aug 2005 19:29:29 +0000 (19:29 +0000)
committerKevin P. Fleming <kpfleming@digium.com>
Mon, 22 Aug 2005 19:29:29 +0000 (19:29 +0000)
support non-SLINEAR moh streams (issue #4908)
add external app to feed TCP stream into Asterisk for moh (issue #4908)

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@6353 65c4cc65-6c06-0410-ace0-fbb531ad65f3

UPGRADE.txt
configs/musiconhold.conf.sample
frame.c
include/asterisk/frame.h
res/res_musiconhold.c
utils/Makefile
utils/streamplayer.c [new file with mode: 0755]

index 509e318..6f7a3bc 100755 (executable)
@@ -137,3 +137,9 @@ AGI:
   behavior has been fixed.  If you do not want your script to terminate 
   at the end of AGI being called (e.g. on a hangup) then set SIGHUP to 
   be ignored within your application.
+
+Music On Hold:
+
+* The preferred format for musiconhold.conf has changed; please see the
+  sample configuration file for the new format. The existing format
+  is still supported but will generate warnings when the module is loaded.
index 91d692d..56379ef 100755 (executable)
@@ -1,16 +1,30 @@
 ;
-; Music on hold class definitions
+; Music on Hold -- Sample Configuration
 ;
-[classes]
-default => quietmp3:/var/lib/asterisk/mohmp3
-;loud => mp3:/var/lib/asterisk/mohmp3
-;random => quietmp3:/var/lib/asterisk/mohmp3,-z
-;unbuffered => mp3nb:/var/lib/asterisk/mohmp3
-;quietunbuf => quietmp3nb:/var/lib/asterisk/mohmp3
-; Note that the custom mode cannot handle escaped parameters (specifically embedded spaces)
-;manual => custom:/var/lib/asterisk/mohmp3,/usr/bin/mpg123 -q -r 8000 -f 8192 -b 2048 --mono -s
-; For a custom app, reading from a directory may not be necessary
-;manual => custom:nodir,/bin/my-dirless-app myarg
+
+[default]
+mode=quietmp3
+directory=/var/lib/asterisk/mohmp3
+
+; valid mode options:
+; quietmp3     -- default
+; mp3          -- loud
+; mp3nb                -- unbuffered
+; quietmp3nb   -- quiet unbuffered
+; custom       -- run a custom application 
+; files                -- read files from a directory in any Asterisk supported format
+
+;[manual]
+;mode=custom
+; Note that with mode=custom, a directory is not required, such as when reading
+; from a stream.
+;directory=/var/lib/asterisk/mohmp3
+;application=/usr/bin/mpg123 -q -r 8000 -f 8192 -b 2048 --mono -s
+
+;[ulawstream]
+;mode=custom
+;application=/usr/bin/streamplayer 80.254.173.176 888
+;format=ulaw
 
 ;
 ; File-based (native) music on hold
@@ -31,6 +45,12 @@ default => quietmp3:/var/lib/asterisk/mohmp3
 ; this, res_musiconhold will skip the files it is not able to
 ; understand when it loads.
 ;
-[moh_files]
-;native => /var/lib/asterisk/moh-native
-;native-random => /var/lib/asterisk/moh-native,r
+
+;[native]
+;mode=files
+;directory=/var/lib/asterisk/moh-native
+;
+;[native-random]
+;mode=files
+;directory=/var/lib/asterisk/moh-native
+;random=yes    ; Play the files in a random order
diff --git a/frame.c b/frame.c
index c6abc00..5089a96 100755 (executable)
--- a/frame.c
+++ b/frame.c
@@ -1168,7 +1168,6 @@ static int speex_samples(unsigned char *data, int len)
        return cnt;
 }
 
-
 int ast_codec_get_samples(struct ast_frame *f)
 {
        int samples=0;
@@ -1210,3 +1209,35 @@ int ast_codec_get_samples(struct ast_frame *f)
        return samples;
 }
 
+int ast_codec_get_len(int format, int samples)
+{
+       int len = 0;
+
+       /* XXX Still need speex, g723, and lpc10 XXX */ 
+       switch(format) {
+       case AST_FORMAT_ILBC:
+               len = (samples / 240) * 50;
+               break;
+       case AST_FORMAT_GSM:
+               len = (samples / 160) * 33;
+               break;
+       case AST_FORMAT_G729A:
+               len = samples / 8;
+               break;
+       case AST_FORMAT_SLINEAR:
+               len = samples * 2;
+               break;
+       case AST_FORMAT_ULAW:
+       case AST_FORMAT_ALAW:
+               len = samples;
+               break;
+       case AST_FORMAT_ADPCM:
+       case AST_FORMAT_G726:
+               len = samples / 2;
+               break;
+       default:
+               ast_log(LOG_WARNING, "Unable to calculate sample length for format %s\n", ast_getformatname(format));
+       }
+
+       return len;
+}
index 7cff43e..41a4e7c 100755 (executable)
@@ -398,6 +398,9 @@ extern void ast_codec_pref_convert(struct ast_codec_pref *pref, char *buf, size_
 /* Returns the number of samples contained in the frame */
 extern int ast_codec_get_samples(struct ast_frame *f);
 
+/* Returns the number of bytes for the number of samples of the given format */
+extern int ast_codec_get_len(int format, int samples);
+
 /* Gets duration in ms of interpolation frame for a format */
 static inline int ast_codec_interp_len(int format) 
 { 
index e2a291c..c2db43d 100755 (executable)
@@ -108,12 +108,14 @@ struct moh_files_state {
 #define MOH_RANDOMIZE          (1 << 3)
 
 struct mohclass {
-       char class[80];
+       char name[MAX_MUSICCLASS];
        char dir[256];
-       char miscargs[256];
+       char args[256];
+       char mode[80];
        char filearray[MAX_MOHFILES][MAX_MOHFILE_LEN];
        unsigned int flags;
        int total_files;
+       int format;
        int pid;                /* PID of mpg123 */
        time_t start;
        pthread_t thread;
@@ -212,8 +214,8 @@ static int ast_moh_files_next(struct ast_channel *chan)
                return -1;
        }
 
-       if (option_verbose > 2)
-               ast_log(LOG_NOTICE, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
+       if (option_debug)
+               ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
 
        if (state->samples)
                ast_seekstream(chan->stream, state->samples, SEEK_SET);
@@ -285,7 +287,7 @@ static void *moh_files_alloc(struct ast_channel *chan, void *params)
                        chan->music_state = NULL;
                } else {
                        if (option_verbose > 2)
-                               ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->class, chan->name);
+                               ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->name, chan->name);
                }
        }
        
@@ -343,7 +345,7 @@ static int spawn_mp3(struct mohclass *class)
                        argv[argc++] = "8192";
                
                /* Look for extra arguments and add them to the list */
-               strncpy(xargs, class->miscargs, sizeof(xargs) - 1);
+               strncpy(xargs, class->args, sizeof(xargs) - 1);
                argptr = xargs;
                while (argptr && !ast_strlen_zero(argptr)) {
                        argv[argc++] = argptr;
@@ -355,7 +357,7 @@ static int spawn_mp3(struct mohclass *class)
                }
        } else  {
                /* Format arguments for argv vector */
-               strncpy(xargs, class->miscargs, sizeof(xargs) - 1);
+               strncpy(xargs, class->args, sizeof(xargs) - 1);
                argptr = xargs;
                while (argptr && !ast_strlen_zero(argptr)) {
                        argv[argc++] = argptr;
@@ -460,6 +462,7 @@ static void *monmp3thread(void *data)
        char buf[8192];
        short sbuf[8192];
        int res, res2;
+       int len;
        struct timeval tv, tv_tmp;
 
        tv.tv_sec = 0;
@@ -495,7 +498,9 @@ static void *monmp3thread(void *data)
                if (!class->members)
                        continue;
                /* Read mp3 audio */
-               if ((res2 = read(class->srcfd, sbuf, res * 2)) != res * 2) {
+               len = ast_codec_get_len(class->format, res);
+               
+               if ((res2 = read(class->srcfd, sbuf, len)) != len) {
                        if (!res2) {
                                close(class->srcfd);
                                class->srcfd = -1;
@@ -504,7 +509,7 @@ static void *monmp3thread(void *data)
                                        class->pid = 0;
                                }
                        } else
-                               ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, res * 2);
+                               ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len);
                        continue;
                }
                ast_mutex_lock(&moh_lock);
@@ -581,7 +586,7 @@ static struct mohclass *get_mohbyname(char *name)
        struct mohclass *moh;
        moh = mohclasses;
        while (moh) {
-               if (!strcasecmp(name, moh->class))
+               if (!strcasecmp(name, moh->name))
                        return moh;
                moh = moh->next;
        }
@@ -647,19 +652,18 @@ static void moh_release(struct ast_channel *chan, void *data)
 static void *moh_alloc(struct ast_channel *chan, void *params)
 {
        struct mohdata *res;
-       struct mohclass *class;
-       class = params;
+       struct mohclass *class = params;
 
        res = mohalloc(class);
        if (res) {
                res->origwfmt = chan->writeformat;
-               if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
-                       ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name);
+               if (ast_set_write_format(chan, class->format)) {
+                       ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
                        moh_release(NULL, res);
                        res = NULL;
                }
                if (option_verbose > 2)
-                       ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->class, chan->name);
+                       ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
        }
        return res;
 }
@@ -674,7 +678,8 @@ static int moh_generate(struct ast_channel *chan, void *data, int len, int sampl
        if (!moh->parent->pid)
                return -1;
 
-       len = samples * 2;
+       len = ast_codec_get_len(moh->parent->format, samples);
+
        if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
                ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
                len = sizeof(buf) - AST_FRIENDLY_OFFSET;
@@ -689,17 +694,20 @@ static int moh_generate(struct ast_channel *chan, void *data, int len, int sampl
                return 0;
 
        memset(&f, 0, sizeof(f));
+       
        f.frametype = AST_FRAME_VOICE;
-       f.subclass = AST_FORMAT_SLINEAR;
+       f.subclass = moh->parent->format;
        f.mallocd = 0;
        f.datalen = res;
-       f.samples = res / 2;
        f.data = buf + AST_FRIENDLY_OFFSET / 2;
        f.offset = AST_FRIENDLY_OFFSET;
+       f.samples = ast_codec_get_samples(&f);
+
        if (ast_write(chan, &f) < 0) {
                ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
                return -1;
        }
+
        return 0;
 }
 
@@ -763,53 +771,42 @@ static int moh_scan_files(struct mohclass *class) {
        return class->total_files;
 }
 
-static int moh_register(char *classname, char *mode, char *param, char *miscargs)
+static int moh_register(struct mohclass *moh)
 {
-       struct mohclass *moh;
 #ifdef ZAPATA_MOH
        int x;
 #endif
        ast_mutex_lock(&moh_lock);
-       moh = get_mohbyname(classname);
-       ast_mutex_unlock(&moh_lock);
-       if (moh) {
-               ast_log(LOG_WARNING, "Music on Hold '%s' already exists\n", classname);
+       if (get_mohbyname(moh->name)) {
+               ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
+               free(moh);      
+               ast_mutex_unlock(&moh_lock);
                return -1;
        }
-       moh = malloc(sizeof(struct mohclass));
-       if (!moh)
-               return -1;
-       memset(moh, 0, sizeof(struct mohclass));
+       ast_mutex_unlock(&moh_lock);
+
        time(&moh->start);
        moh->start -= respawn_time;
-       strncpy(moh->class, classname, sizeof(moh->class) - 1);
-       if (miscargs) {
-               strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1);
-               if (strchr(miscargs,'r'))
-                       ast_set_flag(moh, MOH_RANDOMIZE);
-       }
-       if (!strcasecmp(mode, "files")) {
-               if (param)
-                       strncpy(moh->dir, param, sizeof(moh->dir) - 1);
+       
+       if (!strcasecmp(moh->mode, "files")) {
                if (!moh_scan_files(moh)) {
                        ast_moh_free_class(&moh);
                        return -1;
                }
-       } else if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) {
-
-               if (param)
-                       strncpy(moh->dir, param, sizeof(moh->dir) - 1);
+               if (strchr(moh->args, 'r'))
+                       ast_set_flag(moh, MOH_RANDOMIZE);
+       } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
 
-               if (!strcasecmp(mode, "custom"))
+               if (!strcasecmp(moh->mode, "custom"))
                        ast_set_flag(moh, MOH_CUSTOM);
-               else if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb"))
+               else if (!strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3nb"))
                        ast_set_flag(moh, MOH_SINGLE);
-               else if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb"))
+               else if (!strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb"))
                        ast_set_flag(moh, MOH_QUIET);
                
                moh->srcfd = -1;
 #ifdef ZAPATA_MOH
-               /* It's an MP3 Moh -- Open /dev/zap/pseudo for timing...  Is
+               /* Open /dev/zap/pseudo for timing...  Is
                   there a better, yet reliable way to do this? */
                moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
                if (moh->pseudofd < 0) {
@@ -829,7 +826,7 @@ static int moh_register(char *classname, char *mode, char *param, char *miscargs
                        return -1;
                }
        } else {
-               ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode);
+               ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
                ast_moh_free_class(&moh);
                return -1;
        }
@@ -885,21 +882,100 @@ static void local_ast_moh_stop(struct ast_channel *chan)
        }
 }
 
+static struct mohclass *moh_class_malloc(void)
+{
+       struct mohclass *class;
+
+       class = malloc(sizeof(struct mohclass));
+
+       if (!class)
+               return NULL;
+
+       memset(class, 0, sizeof(struct mohclass));
+
+       class->format = AST_FORMAT_SLINEAR;
+
+       return class;
+}
+
 static int load_moh_classes(void)
 {
        struct ast_config *cfg;
        struct ast_variable *var;
+       struct mohclass *class; 
        char *data;
        char *args;
-       int x = 0;
+       char *cat;
+       int numclasses = 0;
+       static int dep_warning = 0;
 
        cfg = ast_config_load("musiconhold.conf");
 
        if (!cfg)
                return 0;
 
+       cat = ast_category_browse(cfg, NULL);
+       for (; cat; cat = ast_category_browse(cfg, cat)) {
+               if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) {
+                       class = moh_class_malloc();
+                       if (!class) {
+                               ast_log(LOG_WARNING, "Out of memory!\n");
+                               break;
+                       }                               
+                       ast_copy_string(class->name, cat, sizeof(class->name)); 
+                       var = ast_variable_browse(cfg, cat);
+                       while (var) {
+                               if (!strcasecmp(var->name, "mode"))
+                                       ast_copy_string(class->mode, var->value, sizeof(class->name)); 
+                               else if (!strcasecmp(var->name, "directory"))
+                                       ast_copy_string(class->dir, var->value, sizeof(class->dir));
+                               else if (!strcasecmp(var->name, "application"))
+                                       ast_copy_string(class->args, var->value, sizeof(class->args));
+                               else if (!strcasecmp(var->name, "random"))
+                                       ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
+                               else if (!strcasecmp(var->name, "format")) {
+                                       class->format = ast_getformatbyname(var->value);
+                                       if (!class->format) {
+                                               ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
+                                               class->format = AST_FORMAT_SLINEAR;
+                                       }
+                               }
+                                       var = var->next;
+                       }
+
+                       if (ast_strlen_zero(class->dir)) {
+                               if (!strcasecmp(class->mode, "custom")) {
+                                       strcpy(class->dir, "nodir");
+                               } else {
+                                       ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
+                                       free(class);
+                                       continue;
+                               }
+                       }
+                       if (ast_strlen_zero(class->mode)) {
+                               ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
+                               free(class);
+                               continue;
+                       }
+                       if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
+                               ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
+                               free(class);
+                               continue;
+                       }
+
+                       moh_register(class);
+                       numclasses++;
+               }
+       }
+       
+
+       /* Deprecated Old-School Configuration */
        var = ast_variable_browse(cfg, "classes");
        while (var) {
+               if (!dep_warning) {
+                       ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated!  Please refer to the sample configuration for information on the new syntax.\n");
+                       dep_warning = 1;
+               }
                data = strchr(var->value, ':');
                if (data) {
                        *data++ = '\0';
@@ -907,26 +983,55 @@ static int load_moh_classes(void)
                        if (args)
                                *args++ = '\0';
                        if (!(get_mohbyname(var->name))) {
-                               moh_register(var->name, var->value, data, args);
-                               x++;
+                               class = moh_class_malloc();
+                               if (!class) {
+                                       ast_log(LOG_WARNING, "Out of memory!\n");
+                                       return numclasses;
+                               }
+                               
+                               ast_copy_string(class->name, var->name, sizeof(class->name));
+                               ast_copy_string(class->dir, data, sizeof(class->dir));
+                               ast_copy_string(class->mode, var->value, sizeof(class->mode));
+                               if (args)
+                                       ast_copy_string(class->args, args, sizeof(class->args));
+                               
+                               moh_register(class);
+                               numclasses++;
                        }
                }
                var = var->next;
        }
        var = ast_variable_browse(cfg, "moh_files");
        while (var) {
+               if (!dep_warning) {
+                       ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated!  Please refer to the sample configuration for information on the new syntax.\n");
+                       dep_warning = 1;
+               }
                if (!(get_mohbyname(var->name))) {
                        args = strchr(var->value, ',');
                        if (args)
                                *args++ = '\0';
-                       moh_register(var->name, "files", var->value, args);
-                       x++;
+                       class = moh_class_malloc();
+                       if (!class) {
+                               ast_log(LOG_WARNING, "Out of memory!\n");
+                               return numclasses;
+                       }
+                       
+                       ast_copy_string(class->name, var->name, sizeof(class->name));
+                       ast_copy_string(class->dir, var->value, sizeof(class->dir));
+                       strcpy(class->mode, "files");
+                       if (args)       
+                               ast_copy_string(class->args, args, sizeof(class->args));
+                       
+                       moh_register(class);
+                       numclasses++;
                }
                var = var->next;
        }
 
        ast_config_destroy(cfg);
-       return x;
+
+       return numclasses;
 }
 
 static void ast_moh_destroy(void)
@@ -998,7 +1103,7 @@ static int cli_files_show(int fd, int argc, char *argv[])
                if (!class->total_files)
                        continue;
 
-               ast_cli(fd, "Class: %s\n", class->class);
+               ast_cli(fd, "Class: %s\n", class->name);
                for (i = 0; i < class->total_files; i++)
                        ast_cli(fd, "\tFile: %s\n", class->filearray[i]);
        }
@@ -1007,8 +1112,27 @@ static int cli_files_show(int fd, int argc, char *argv[])
        return 0;
 }
 
+static int moh_classes_show(int fd, int argc, char *argv[])
+{
+       struct mohclass *class;
+
+       ast_mutex_lock(&moh_lock);
+       for (class = mohclasses; class; class = class->next) {
+               ast_cli(fd, "Class: %s\n", class->name);
+               ast_cli(fd, "\tDirectory: %s\n", ast_strlen_zero(class->dir) ? "<none>" : class->dir);
+               if (ast_test_flag(class, MOH_CUSTOM))
+                       ast_cli(fd, "\tApplication: %s\n", ast_strlen_zero(class->args) ? "<none>" : class->args);
+               ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
+       }
+       ast_mutex_unlock(&moh_lock);
+
+       return 0;
+}
+
 static struct ast_cli_entry  cli_moh = { { "moh", "reload"}, moh_cli, "Music On Hold", "Music On Hold", NULL};
 
+static struct ast_cli_entry  cli_moh_classes_show = { { "moh", "classes", "show"}, moh_classes_show, "List MOH classes", "Lists all MOH classes", NULL};
+
 static struct ast_cli_entry  cli_moh_files_show = { { "moh", "files", "show"}, cli_files_show, "List MOH file-based classes", "Lists all loaded file-based MOH classes and their files", NULL};
 
 static void init_classes(void) 
@@ -1033,6 +1157,7 @@ int load_module(void)
        ast_register_atexit(ast_moh_destroy);
        ast_cli_register(&cli_moh);
        ast_cli_register(&cli_moh_files_show);
+       ast_cli_register(&cli_moh_classes_show);
        if (!res)
                res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
        if (!res)
index 689aacb..137b0b7 100755 (executable)
@@ -21,7 +21,7 @@ ifeq ($(findstring BSD,${OSARCH}),BSD)
 CFLAGS+=-I$(CROSS_COMPILE_TARGET)/usr/local/include -L$(CROSS_COMPILE_TARGET)/usr/local/lib
 endif
 
-TARGET=stereorize
+TARGET=stereorize streamplayer
 
 TARGET+=$(shell if [ -f $(CROSS_COMPILE_TARGET)/usr/include/popt.h ]; then echo "smsq"; else if [ -f $(CROSS_COMPILE_TARGET)/usr/local/include/popt.h ]; then echo "smsq"; fi ; fi)
 TARGET+=$(shell if [ -f $(CROSS_COMPILE_TARGET)/usr/include/newt.h ]; then echo "astman"; else if [ -f $(CROSS_COMPILE_TARGET)/usr/local/include/newt.h ]; then echo "astman"; fi ; fi)
@@ -53,6 +53,9 @@ check_expr : check_expr.c ../ast_expr.a
 smsq: smsq.o
        $(CC) $(CFLAGS) -o smsq ${SOL} smsq.o -lpopt
 
+streamplayer: streamplayer.o
+       $(CC) $(CFLAGS) -o streamplayer ${SOL} streamplayer.o
+
 ifneq ($(wildcard .depend),)
 include .depend
 endif
diff --git a/utils/streamplayer.c b/utils/streamplayer.c
new file mode 100755 (executable)
index 0000000..5ae742f
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+* streamplayer.c
+*
+* A utility for reading from a stream
+* 
+* Copyright (C) 2005, Digium, Inc.
+*
+* Russell Bryant <russell@digium.com>
+*
+* This program is free software, distributed under the terms of
+* the GNU General Public License
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+
+int main(int argc, char *argv[])
+{
+       struct sockaddr_in sin;
+       struct hostent *hp;
+       int s;
+       int res;
+       char buf[2048];
+       fd_set wfds;
+       struct timeval tv;
+
+       if (argc != 3) {
+               fprintf(stderr, "Usage: ./streamplayer <ip> <port>\n");
+               exit(1);
+       }
+
+       hp = gethostbyname(argv[1]);
+       if (!hp) {
+               fprintf(stderr, "Unable to lookup IP for host '%s'\n", argv[1]);
+               exit(1);
+       }
+
+       memset(&sin, 0, sizeof(sin));
+       
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(atoi(argv[2]));
+       memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+       
+       s = socket(AF_INET, SOCK_STREAM, 0);
+       
+       if (s < 0) {
+               fprintf(stderr, "Unable to allocate socket!\n");
+               exit(1);
+       }       
+
+       res = connect(s, (struct sockaddr *)&sin, sizeof(sin));
+       
+       if (res) {
+               fprintf(stderr, "Unable to connect to host!\n");
+               close(s);
+               exit(1);        
+       }
+
+       while (1) {
+               res = read(s, buf, sizeof(buf));
+
+               if (res < 1)
+                       break;          
+       
+               memset(&tv, 0, sizeof(tv));             
+               FD_ZERO(&wfds);
+               FD_SET(1, &wfds);
+
+               select(2, NULL, &wfds, NULL, &tv);
+
+               if (FD_ISSET(1, &wfds))
+                       write(1, buf, res);
+       }
+
+       close(s);
+       exit(res);
+}