lcr/tones.c

565 lines
14 KiB
C

/*****************************************************************************\
** **
** 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 __attribute__((__unused__)) 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);
}