/*****************************************************************************\ ** ** ** PBX4Linux ** ** ** **---------------------------------------------------------------------------** ** Copyright: Andreas Eversberg ** ** ** ** opening and reading tone ** ** ** \*****************************************************************************/ #include "main.h" /* notes about codecs: CODEC_OFF is a none accepted value CODEC_LAW is 8 bit (1 byte) data 8khz other codecs are 16 bit (2 bytes) data 8khz the read_tone() will return law or 16bit mono. the read_tone will convert all other formats to 16bit mono. */ /* * open the tone (don't increase fhuse, since it is done after calling this function) * NOTE: length and left will be set to the number of samples, NOT bytes */ struct fmt { unsigned short stereo; /* 1 = pcm, 2 = adpcm */ unsigned short channels; /* number of channels */ unsigned int sample_rate; /* sample rate */ unsigned int data_rate; /* data rate */ unsigned short bytes_sample; /* bytes per sample (all channels) */ unsigned short bits_sample; /* bits per sample (one channel) */ }; int open_tone(char *file, int *codec, signed int *length, signed int *left) { int fh; char filename[256]; char linkname[256]; unsigned char buffer[256]; struct fmt *fmt; int channels = 0, bytes = 0; unsigned int size, chunk; int gotfmt = 0; struct stat _stat; int linksize; int l; char *p; int ret; /* try to open the law file */ SPRINT(filename, "%s.isdn", file); if ((fh = open(filename, O_RDONLY)) >= 0) { /* stat tone */ l = 0; while(42) { if (l >= 10) { close(fh); PERROR("Link chain too deep: '%s'\n", filename); return(-1); } if (lstat(filename, &_stat) == -1) { close(fh); PERROR("Cannot stat file: '%s'\n", filename); return(-1); } if (!S_ISLNK(_stat.st_mode)) { break; } if ((linksize=readlink(filename, linkname, sizeof(linkname))) > 0) { linkname[linksize] = '\0'; } else { close(fh); PERROR("Cannot read link information: '%s'\n", filename); return(-1); } if (linkname[0] == '/') /* absolute link */ SCPY(filename, linkname); else { /* relative link */ /* remove filename */ p = filename; while(strchr(p, '/')) { p = strchr(p, '/')+1; } *p = 0; /* concat the link */ SCAT(filename, linkname); } //printf("follow link: %s\n", filename); l++; } if (length) *length = _stat.st_size; if (left) *left = _stat.st_size; if (codec) *codec = CODEC_LAW; return(fh); } /* try to open the wave file */ SPRINT(filename, "%s.wav", file); if ((fh = open(filename, O_RDONLY)) >= 0) { /* get wave header */ ret = read(fh, buffer, 8); size=(buffer[4]) + (buffer[5]<<8) + (buffer[6]<<16) + (buffer[7]<<24); if (!!strncmp((char *)buffer, "RIFF", 4)) { close(fh); errno = 0; PERROR("%s is no riff file!\n", filename); return(-1); } // printf("%c%c%c%c size=%ld\n",buffer[0],buffer[1],buffer[2],buffer[3],size); ret = read(fh, buffer, 4); size -= 4; if (!!strncmp((char *)buffer, "WAVE", 4)) { close(fh); errno = 0; PERROR("%s is no wave file!\n", filename); return(-1); } while(size) { if (size>0 && size<8) { close(fh); errno = 0; PERROR("Remaining file size %ld not large enough for next chunk.\n",size); return(-1); } ret = read(fh, buffer, 8); chunk=(buffer[4]) + (buffer[5]<<8) + (buffer[6]<<16) + (buffer[7]<<24); size -= (8+chunk); // printf("%c%c%c%c length=%d\n",buffer[0],buffer[1],buffer[2],buffer[3],chunk); if (size < 0) { close(fh); errno = 0; PERROR("Chunk '%c%c%c%c' is larger than remainig file size (length=%ld)\n",buffer[0],buffer[1],buffer[2],buffer[3], chunk); return(-1); } if (!strncmp((char *)buffer, "fmt ", 4)) { if (chunk < 16) { close(fh); errno = 0; PERROR("File %s Fmt chunk illegal size.\n", filename); return(-1); } ret = read(fh, buffer, chunk); fmt = (struct fmt *)buffer; if (fmt->channels<1 || fmt->channels>2) { close(fh); errno = 0; PERROR("File %s Only support one or two channels file.\n", filename); return(-1); } channels = fmt->channels; // printf("Channels: %d\n", channels); if (fmt->sample_rate != 8000) { PERROR("Warning: File %s has sample rate of %ld.\n", filename, fmt->sample_rate); } // printf("Sample Rate: %ld\n", fmt->sample_rate); if (fmt->bits_sample!=8 && fmt->bits_sample!=16) { close(fh); errno = 0; PERROR("File %s has neigher 8 nor 16 bit samples.\n", filename); return(-1); } bytes = (fmt->bits_sample==16)?2:1; // printf("Bit-Resolution: %d\n", bytes*16-16); gotfmt = 1; } else if (!strncmp((char *)buffer, "data", 4)) { if (!gotfmt) { close(fh); errno = 0; PERROR("File %s No fmt chunk found before data chunk.\n", filename); return(-1); } // printf("Length: %ld samples (%ld.%03ld seconds)\n", chunk/bytes/channels, chunk/bytes/channels/8000, ((chunk/bytes/channels)%8000)*1000/8000); if (bytes==2 && channels==1) { if (codec) *codec = CODEC_MONO; if (length) *length = ((signed int)chunk)>>1; if (left) *left = ((signed int)chunk)>>1; // printf("left=%d\n",*left); } else if (bytes==2 && channels==2) { if (codec) *codec = CODEC_STEREO; if (length) *length = ((signed int)chunk)>>2; if (left) *left = ((signed int)chunk)>>2; } else if (bytes==1 && channels==1) { if (codec) *codec = CODEC_8BIT; if (length) *length = (signed int)chunk; if (left) *left = (signed int)chunk; } else { close(fh); errno = 0; PERROR("File %s Is not MONO8, MONO16 nor STEREO16.\n", filename); return(-1); } return(fh); } else { // PDEBUG(DEBUG_PORT, "Unknown chunk '%c%c%c%c'\n",buffer[0],buffer[1],buffer[2],buffer[3]); while(chunk > sizeof(buffer)) { ret = read(fh, buffer, sizeof(buffer)); chunk -= sizeof(buffer); } if (chunk) ret = read(fh, buffer, chunk); } } if (!gotfmt) { close(fh); errno = 0; PERROR("File %s No fmt chunk found in file.\n", filename); return(-1); } close(fh); errno = 0; PERROR("File %s No data chunk found in file.\n", filename); return(-1); } return(-1); } /* * read from tone, check size * the len must be the number of samples, NOT for the bytes to read!! * the data returned is law-code */ int read_tone(int fh, unsigned char *buffer, int codec, int len, signed int size, signed int *left, int speed) { int l = 0; int offset; signed short buffer16[len], *buf16 = buffer16; signed short buffer32[len<<1], *buf32 = buffer32; unsigned char buffer8[len], *buf8 = buffer8; signed int sample; int i = 0; //printf("left=%ld\n",*left); /* if no *left is given (law has unknown length) */ if (!left) goto unknown_length; if (speed!=1) { offset = ((len&(~4)) * (speed-1)); lseek(fh, offset, SEEK_CUR); /* step fowards, backwards (len must be round to 4 bytes, to be sure, that 16bit stereo will not drift out of sync)*/ *left -= offset; /* correct the current bytes left */ if (*left < 0) { /* eof */ *left = 0; return(0); } if (*left >= size) { /* eof */ *left = size; return(0); } } if (*left == 0) return(0); if (*left < len) len = *left; unknown_length: switch(codec) { case CODEC_LAW: l = read(fh, buffer, len); /* as is */ break; case CODEC_MONO: l = read(fh, buf16, len<<1); if (l>0) { l = l>>1; while(i < l) { sample = *buf16++; if (sample < -32767) sample = -32767; if (sample > 32767) sample = 32767; *buffer++ = audio_s16_to_law[sample & 0xffff]; i++; } } break; case CODEC_STEREO: l = read(fh, buf32, len<<2); if (l>0) { l = l>>2; while(i < l) { sample = (*buf32++); sample += (*buf32++); if (sample < -32767) sample = -32767; if (sample > 32767) sample = 32767; *buffer++ = audio_s16_to_law[sample & 0xffff]; i++; } } break; case CODEC_8BIT: l = read(fh, buf8, len); if (l>0) { while(i < l) { *buffer++ = audio_s16_to_law[(((*buf8++)<<8)-0x8000) & 0xffff]; i++; } } break; default: FATAL("codec %d is not supported.\n", codec); } if (l>0 && left) *left -= l; return(l); } struct toneset *toneset_first = NULL; /* * free fetched tones */ void free_tones(void) { struct toneset *toneset_temp; struct tonesettone *tonesettone_temp; void *temp; toneset_temp = toneset_first; while(toneset_temp) { tonesettone_temp = toneset_temp->first; while(tonesettone_temp) { temp = tonesettone_temp; tonesettone_temp = tonesettone_temp->next; FREE(temp, sizeof(struct tonesettone)); memuse--; } temp = toneset_temp; toneset_temp = toneset_temp->next; FREE(temp, sizeof(struct toneset)); memuse--; } toneset_first = NULL; } /* * fetch tones as specified in options.conf */ int fetch_tones(void) { DIR *dir; struct dirent *dirent; struct toneset **toneset_nextpointer; struct tonesettone **tonesettone_nextpointer; char *p, *p_next; char path[256]; char filename[256], name[256]; int fh; int tone_codec; signed int tone_size, tone_left; unsigned int memory = 0; int samples = 0; /* if disabled */ if (!options.fetch_tones) return(1); toneset_nextpointer = &toneset_first; p = options.fetch_tones; if (*p == '\0') return(1); while (*p) { p_next = p; while(*p_next) { if (*p_next == ',') { *p_next = '\0'; p_next++; break; } p_next++; } /* remove trailing / */ if (*p) if (p[strlen(p)-1] == '/') p[strlen(p)-1] = '\0'; printf("PBX: Fetching tones '%s'\n", p); PDEBUG(DEBUG_PORT, "fetching tones directory '%s'\n", p); *toneset_nextpointer = (struct toneset *)MALLOC(sizeof(struct toneset)); memuse++; memory += sizeof(struct toneset); SCPY((*toneset_nextpointer)->directory, p); tonesettone_nextpointer = &(*toneset_nextpointer)->first; SPRINT(path, "%s/%s", SHARE_DATA, p); dir = opendir(path); if (dir == NULL) { PERROR("Tone set not found: '%s'\n", path); return(0); } while((dirent=readdir(dir))) { SPRINT(name, "%s", dirent->d_name); /* remove .isdn and .wave */ if (strlen(name) >= 4) { if (!strcmp(name+strlen(name)-4, ".wav")) name[strlen(name)-4] = '\0'; } if (strlen(name) >= 5) { if (!strcmp(name+strlen(name)-5, ".isdn")) name[strlen(name)-5] = '\0'; } SPRINT(filename, "%s/%s", path, name); /* skip . / .. */ if (!strcmp(dirent->d_name, ".")) continue; if (!strcmp(dirent->d_name, "..")) continue; /* open file */ fh = open_tone(filename, &tone_codec, &tone_size, &tone_left); if (fh < 0) { PERROR("Cannot open file: '%s'\n", filename); continue; } fduse++; if (tone_size < 0) { PERROR("File has 0-length: '%s'\n", filename); close(fh); fduse--; continue; } /* Allocate tone */ *tonesettone_nextpointer = (struct tonesettone *)MALLOC(sizeof(struct tonesettone)+tone_size); memuse++; //printf("tone:%s, %ld bytes\n", name, tone_size); memory += sizeof(struct tonesettone)+tone_size; samples ++; /* load tone */ read_tone(fh, (*tonesettone_nextpointer)->data, tone_codec, tone_size, tone_size, &tone_left, 1); (*tonesettone_nextpointer)->size = tone_size; (*tonesettone_nextpointer)->codec = (tone_codec==CODEC_LAW)?CODEC_LAW:CODEC_MONO; SCPY((*tonesettone_nextpointer)->name, name); close(fh); fduse--; tonesettone_nextpointer = &((*tonesettone_nextpointer)->next); } toneset_nextpointer = &((*toneset_nextpointer)->next); p = p_next; } printf("PBX: Memory used for tones: %d bytes (%d samples)\n", memory, samples); PDEBUG(DEBUG_PORT, "Memory used for tones: %ld bytes (%d samples)\n", memory, samples); return(1); } /* * opens the fetched tone (if available) */ void *open_tone_fetched(char *dir, char *file, int *codec, signed int *length, signed int *left) { struct toneset *toneset; struct tonesettone *tonesettone; /* if anything fetched */ if (!toneset_first) return(NULL); /* find set */ toneset = toneset_first; while(toneset) { //printf("1. comparing '%s' with '%s'\n", toneset->directory, dir); if (!strcmp(toneset->directory, dir)) break; toneset = toneset->next; } if (!toneset) return(NULL); /* find tone */ tonesettone = toneset->first; while(tonesettone) { //printf("2. comparing '%s' with '%s'\n", tonesettone->name, file); if (!strcmp(tonesettone->name, file)) break; tonesettone = tonesettone->next; } if (!tonesettone) return(NULL); /* return information */ if (length) *length = tonesettone->size; if (left) *left = tonesettone->size; if (codec) *codec = tonesettone->codec; //printf("size=%ld, data=%08x\n", tonesettone->size, tonesettone->data); return(tonesettone->data); } /* * read from fetched tone, check size * the len must be the number of samples, NOT for the bytes to read!! */ int read_tone_fetched(void **fetched, void *buffer, int len, signed int size, signed int *left, int speed) { int l; //printf("left=%ld\n",*left); /* if no *left is given (law has unknown length) */ if (!left) return(0); if (*left == 0) return(0); if (*left < len) len = *left; memcpy(buffer, *fetched, len); *((char **)fetched) += len; l = len; if (l>0 && left) *left -= l; return(l); }