--- res/res_musiconhold.c.orig Sat Sep 4 06:41:01 2004 +++ res/res_musiconhold.c Mon Sep 6 14:58:33 2004 @@ -73,6 +73,20 @@ static int respawn_time = 20; +/* ulaw magic number */ +#define MACICNUMBER 0x2e736e64 +#define REVERSEMACICNUMBER 0x646E732E + +struct ulaw_filehdr{ + uint32_t magic; /* magic number */ + uint32_t hdr_size; /* size of this header */ + uint32_t data_size; /* length of data (optional) */ + uint32_t encoding; /* data encoding format */ + uint32_t sample_rate; /* samples per second */ + uint32_t channels; /* number of interleaved channels */ +} ; + + struct mohclass { char class[80]; char dir[256]; @@ -83,6 +97,9 @@ int single; int custom; time_t start; + int format; /* Frame type */ + int audiopipe[2]; /* pipe for decoder thread */ + pthread_t decoderthread; pthread_t thread; struct mohdata *members; /* Source of audio */ @@ -108,6 +125,227 @@ #define MPG_123 "/usr/bin/mpg123" #define MAX_MP3S 256 +static uint32_t swapdw(uint32_t udw) +{ + return (udw >> 24) | ((udw >> 8) & 0xff00) | ((udw << 8) & 0xff0000L) | (udw << 24); +} + +void *ulawdecoderthread(void *args); + + +void *ulawdecoderthread(void *args) +{ + struct mohclass *class; + DIR *dir; + struct dirent *de; + char fns[MAX_MP3S][80]; + char currentulawfile[1024]; + int currentfile = 0; + int files = 0; + int readfd = -1; + char readbuf[16384]; + int len; + struct ulaw_filehdr header; + + class = (struct mohclass *)args; + + dir = opendir(class->dir); + while((de = readdir(dir)) && (files < MAX_MP3S)) { + if ((strlen(de->d_name) > 4) && !strcasecmp(de->d_name + strlen(de->d_name) - 5, ".ulaw")) { + strncpy(fns[files], de->d_name, sizeof(fns[files]) - 1); + files++; + } + } + + if (files == 0) { + ast_log(LOG_WARNING, "unable to start ulawdecoder .... no ulaw files \n"); + return NULL; + } + + /* random number seed */ + srand((unsigned)time(NULL)); + for(;/* ever */;) { + + /* Pick ulaw file to decode */ + if (strncmp(class->miscargs,"-z", 2) == 0){ + currentfile = (rand() % files); + } + + strcpy(currentulawfile, class->dir); + strcat(currentulawfile, "/"); + strcat(currentulawfile, fns[currentfile++]); + if (currentfile == files) + currentfile = 0; + + if ((readfd = open(currentulawfile, O_RDONLY)) < 0) { + ast_log(LOG_WARNING, "unable to open music on hold file %s\n", currentulawfile); + return NULL; + } + + + /* some header checks */ + + len = read(readfd,&header,24); + + if (len != 24) { + ast_log(LOG_WARNING, "unable to read header for MOH file %s \n", currentulawfile); + close(readfd); + continue; + } + + /* Check to see if data is in other endian format */ + if (header.magic == REVERSEMACICNUMBER){ + header.magic = swapdw(header.magic); + header.hdr_size = swapdw(header.hdr_size); + header.data_size = swapdw(header.data_size); + header.encoding = swapdw(header.encoding); + header.sample_rate = swapdw(header.sample_rate); + header.channels = swapdw(header.channels); + } + + if ((header.magic != MACICNUMBER) ||(header.encoding != 1)) { + ast_log(LOG_WARNING, "%s not a ulaw file \n", currentulawfile); + close(readfd); + continue; + } + + if (header.sample_rate != 8000) { + ast_log(LOG_WARNING, "%s sample rate %uld ... can only accept 8000 \n", currentulawfile, header.sample_rate); + close(readfd); + continue; + } + + if (header.sample_rate != 8000) { + ast_log(LOG_WARNING, "%s has %uld channels ... can only accept mono \n", currentulawfile, header.channels); + close(readfd); + continue; + } + + /* Junk the rest of the header */ + if(header.hdr_size > 24) + len = read(readfd,readbuf, header.hdr_size - 24); + + while((len = read(readfd,readbuf,16384))) + { + write(class->audiopipe[1],readbuf,len); + } + + close(readfd); + } +} + + +static void *monulawthread(void *data) +{ +#define MOH_MS_INTERVAL 100 + + struct mohclass *class = data; + struct mohdata *moh; + char buf[8192]; + short sbuf[8192]; + int res, res2; + struct timeval tv; + struct timeval tv_tmp; + long error_sec, error_usec; + long delay; + + + tv_tmp.tv_sec = 0; + tv_tmp.tv_usec = 0; + tv.tv_sec = 0; + tv.tv_usec = 0; + error_sec = 0; + error_usec = 0; + + if (pipe(class->audiopipe)) { + ast_log(LOG_WARNING, "Music on hold audio pipe failed\n"); + return NULL; + } + + if (ast_pthread_create(&class->decoderthread, NULL, ulawdecoderthread, class)) { + ast_log(LOG_WARNING, "Unable to create moh ulaw decoder\n"); + close(class->audiopipe[0]); + close(class->audiopipe[1]); + return NULL; + } + + for(;/* ever */;) { + if (class->pseudofd > -1) { + /* Pause some amount of time */ + res = read(class->pseudofd, buf, sizeof(buf)); + } else { + /* Reliable sleep */ + if (gettimeofday(&tv_tmp, NULL) < 0) { + ast_log(LOG_NOTICE, "gettimeofday() failed!\n"); + pthread_cancel(class ->decoderthread); + close(class->audiopipe[0]); + close(class->audiopipe[1]); + return NULL; + } + if (((unsigned long)(tv.tv_sec) > 0)&&((unsigned long)(tv.tv_usec) > 0)) { + if ((unsigned long)(tv_tmp.tv_usec) < (unsigned long)(tv.tv_usec)) { + tv_tmp.tv_usec += 1000000; + tv_tmp.tv_sec -= 1; + } + error_sec = (unsigned long)(tv_tmp.tv_sec) - (unsigned long)(tv.tv_sec); + error_usec = (unsigned long)(tv_tmp.tv_usec) - (unsigned long)(tv.tv_usec); + } else { + error_sec = 0; + error_usec = 0; + } + if (error_sec * 1000 + error_usec / 1000 < MOH_MS_INTERVAL) { + tv.tv_sec = tv_tmp.tv_sec + (MOH_MS_INTERVAL/1000 - error_sec); + tv.tv_usec = tv_tmp.tv_usec + ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec); + delay = (MOH_MS_INTERVAL/1000 - error_sec) * 1000 + + ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec) / 1000; + } else { + ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n"); + tv.tv_sec = tv_tmp.tv_sec; + tv.tv_usec = tv_tmp.tv_usec; + delay = 0; + } + if (tv.tv_usec > 1000000) { + tv.tv_sec++; + tv.tv_usec-= 1000000; + } + if (delay > 0) + usleep(delay * 1000); + res = 800; /* 800 samples */ + } + if (!class->members) + continue; + /* Read ulaw audio */ + if ((res2 = read(class->audiopipe[0], sbuf, res)) != res) { + if (!res2) { + ast_log(LOG_DEBUG, "Can't read from ulaw decoder closing moh ... \n"); + pthread_cancel(class->decoderthread); + close(class->audiopipe[0]); + close(class->audiopipe[1]); + return NULL; + + } else + ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, res * 2); + continue; + } + ast_mutex_lock(&moh_lock); + moh = class->members; + while(moh) { + /* Write data */ + if ((res = write(moh->pipe[1], sbuf, res2)) != res2) + if (option_debug) + ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2); + moh = moh->next; + } + ast_mutex_unlock(&moh_lock); + } + + /* Should not get here */ + pthread_cancel(class->decoderthread); + close(class->audiopipe[0]); + close(class->audiopipe[1]); + return NULL; +} + static int spawn_mp3(struct mohclass *class) { int fds[2]; @@ -465,8 +703,8 @@ ast_mutex_unlock(&moh_lock); 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 '%s' to frame format %s \n", chan->name, ast_getformatname(class->format)); moh_release(NULL, res); res = NULL; } @@ -484,15 +722,21 @@ { struct ast_frame f; struct mohdata *moh = data; - short buf[1280 + AST_FRIENDLY_OFFSET / 2]; + short buf[1280 + AST_FRIENDLY_OFFSET]; int res; + int bytespersample; - len = samples * 2; + if ((moh->parent)->format == AST_FORMAT_ULAW) + bytespersample = 1; + else // SLINEAR + bytespersample = 2; + + len = samples * bytespersample; if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) { ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), (int)len, chan->name); len = sizeof(buf) - AST_FRIENDLY_OFFSET; } - res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len); + res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/bytespersample, len); #if 0 if (res != len) { ast_log(LOG_WARNING, "Read only %d of %d bytes: %s\n", res, len, strerror(errno)); @@ -501,11 +745,11 @@ if (res > 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.samples = res / bytespersample; + f.data = buf + AST_FRIENDLY_OFFSET / bytespersample; f.offset = AST_FRIENDLY_OFFSET; if (ast_write(chan, &f)< 0) { ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno)); @@ -544,7 +788,7 @@ strncpy(moh->class, classname, sizeof(moh->class) - 1); if (miscargs) strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1); - if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) { + if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")|| !strcasecmp(mode, "ulaw")) { if (!strcasecmp(mode, "custom")) moh->custom = 1; if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb")) @@ -566,12 +810,27 @@ #else moh->pseudofd = -1; #endif - if (ast_pthread_create(&moh->thread, NULL, monmp3thread, moh)) { - ast_log(LOG_WARNING, "Unable to create moh...\n"); - if (moh->pseudofd > -1) - close(moh->pseudofd); - free(moh); - return -1; + if(!strcasecmp(mode, "ulaw")) { + moh->format = AST_FORMAT_ULAW; + if (ast_pthread_create(&moh->thread, NULL, monulawthread, moh)) { + ast_log(LOG_WARNING, "Unable to create moh...\n"); + if (moh->pseudofd > -1) + close(moh->pseudofd); + free(moh); + return -1; + } + + } + else { + moh->format = AST_FORMAT_SLINEAR; + if (ast_pthread_create(&moh->thread, NULL, monmp3thread, moh)) { + ast_log(LOG_WARNING, "Unable to create moh...\n"); + if (moh->pseudofd > -1) + close(moh->pseudofd); + free(moh); + return -1; + } + } } else { ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode); --- configs/musiconhold.conf.sample.orig Sun Aug 1 15:19:04 2004 +++ configs/musiconhold.conf.sample Mon Sep 6 14:56:02 2004 @@ -9,3 +9,10 @@ ;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 + +; MOH using ulaw files +; Generate using sox in mono with 8 kbs sample rate, for example +;sox -V foo.mp3 -t au -r 8000 -U -b -c 1 foo.ulaw resample -ql + +;ulaw=> ulaw:/var/lib/asterisk/mohulaw +;randomulaw => ulaw:/var/lib/asterisk/mohulaw,-z