/* $Header: /home/yav/catty/fkiss/RCS/sound.c,v 1.21 2000/10/17 05:01:09 yav Exp $ * fkiss sound routine for Linux and FreeBSD * written by yav * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ char id_sound[] = "$Id: sound.c,v 1.21 2000/10/17 05:01:09 yav Exp $"; #include #include "config.h" #include "headers.h" #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_SYS_STAT_H # include #else # ifdef HAVE_STAT_H # include # endif #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef linux # define USE_DSP # define DEV_SOUND "/dev/dsp" # ifdef HAVE_FCNTL_H # include # endif # ifdef HAVE_SYS_IOCTL_H # include # endif # include #endif #ifdef __FreeBSD__ # define USE_DSP # define DEV_SOUND "/dev/dsp" # include #endif #ifndef DEV_SOUND # define DEV_SOUND "/dev/audio" #endif #ifdef HAVE_LIBESD # define USE_ESD 1 #else /* HAVE_LIBESD */ # define USE_ESD 0 #endif /* HAVE_LIBESD */ #if USE_ESD # ifdef HAVE_ESD_H # include # endif /* HAVE_ESD_H */ #endif /* USE_ESD */ #define PUBLIC_SOUND_C #include "extern.h" #include "ulaw.h" char *midi_player = NULL; char *sound_device = DEV_SOUND; int sound_debug = 1; /* debug information print level * 0: print nothing, silent * 1: error messages * 2: all messages */ int sound_output = 1; /* 0:Not output to device, other:output */ static int sound_enable = 0; /* 0:disable other:enable */ int sound_force = 0; /* no conversion, force write DSP data */ int use_esd = USE_ESD; static int esd; unsigned char *esd_host = NULL; typedef struct _sf { int sound_rate; /* sampling rate Hz */ int sound_channels; /* 1:mono 2:stereo */ int sound_bits; /* bits per sample 8 or 16 */ int sound_ulaw_encoded; /* input ulaw encoded */ int sound_little_endian; /* input little-endian */ int sound_unsigned; /* input zero level 0x80 */ int sample_bytes; /* byts per sample 1 or 2 */ int sound_converted; int sound_sample_id; /* ESD sample ID */ } SF; #define SOUND_CACHE_MAX 64 typedef struct { char *pathname; char *buffer; long size; long data_ofs; SF sf; } CACHE_TABLE; static CACHE_TABLE *cache_list = NULL; static int cache_n = 0; #ifdef USE_DSP static int dsp_rate; static int dsp_bits; static int dsp_channels; #endif void adjust_parms(sf) SF *sf; { if (sf->sound_bits < 1) sf->sound_bits = 8; #ifdef USE_DSP sf->sample_bytes = (sf->sound_bits+7) / 8; /* defaut 8bit data zero level 0x80 */ #endif } #ifdef USE_DSP int setup_dsp(fd, p) int fd; SF *p; { /* copy to DSP setting parms */ dsp_channels = p->sound_channels; dsp_bits = p->sound_bits; dsp_rate = p->sound_rate; ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &dsp_channels); ioctl(fd, SOUND_PCM_WRITE_BITS, &dsp_bits); ioctl(fd, SOUND_PCM_WRITE_RATE, &dsp_rate); return 0; } void conv_center80(buf, n) unsigned char *buf; int n; { while (n--) { *buf += 0x80; buf++; } } int get_highest_bytes(sf, buf, n) SF *sf; unsigned char *buf; int n; { int i, d; unsigned char *p; unsigned char *p0; d = sf->sample_bytes * sf->sound_channels; p0 = p = buf; if (sf->sound_little_endian) p0 += sf->sample_bytes - 1; if (sf->sound_channels == 1) { for (i = 0; i < n; i += d) { *p++ = *p0; p0 += d; } } else { for (i = 0; i < n; i += d) { *p++ = *p0; *p++ = *(p0 + sf->sample_bytes); p0 += d; } } return (n / sf->sample_bytes); } #endif /* USE_DSP */ /* convert ulaw to 0x80 center 8bit linear PCM */ void conv_ulaw_dsp(buf, n) unsigned char *buf; int n; { while (n--) *buf++ = ulaw_dsp[*buf]; } int convert_sound(sf, buf, n) SF *sf; char *buf; int n; { if (sound_force) return n; if (sf->sound_converted) return n; if (sf->sound_ulaw_encoded) { conv_ulaw_dsp(buf, n); return n; } #ifdef USE_DSP if (dsp_bits == 8) { if (sf->sample_bytes != 1) n = get_highest_bytes(sf, buf, n); /* adjust zero level 0x80 */ if (!sf->sound_unsigned) conv_center80(buf, n); } #endif return n; } #ifdef USE_DSP void play_sync(fd) int fd; { if (sound_debug >= 2) fputs("sync", stderr); ioctl(fd, SNDCTL_DSP_SYNC); if (sound_debug >= 2) fputs(" ", stderr); } #endif int play_sound(fd, buf, n) int fd; char *buf; int n; { #ifdef USE_DSP play_sync(fd); #endif if (sound_debug >= 2) fputs("write", stderr); if (sound_output) write(fd, buf, n); if (sound_debug >= 2) fputs(" ", stderr); return 0; } short get_short(sf, p) SF *sf; unsigned char *p; { return sf->sound_little_endian ? (*(p+1) << 8) | *p : (*p << 8) | *(p+1); } long get_long(sf, p) SF *sf; unsigned char *p; { return sf->sound_little_endian ? (((long)*(p+3)) << 24) | (((long)*(p+2)) << 16) | (*(p+1) << 8) | *p : (((long)*p) << 24) | (((long)*(p+1)) << 16) | (*(p+2) << 8) | *(p+3); } /* check Sun Audio file header * return data offset to play * == 0 : Not au file * < 0 : Not supported format */ int check_au_header(sf, buf) SF *sf; unsigned char *buf; /* data buffer */ { long data_offset; long data_type; static struct { int bits; /* bits per sample */ int ulaw; /* 0:linear 1:ulaw encoded */ } type_info[] = { { 0, 0}, /* 0: dummy */ { 8, 1}, /* 1: 8-bit ISDN u-law */ { 8, 0}, /* 2: 8-bit linear PCM */ {16, 0}, /* 3:16-bit linear PCM */ {24, 0}, /* 4:24-bit linear PCM */ {32, 0}, /* 5:32-bit linear PCM */ }; sf->sound_little_endian = 0; if (strncmp(buf, ".snd", 4) != 0) { if (strncmp(buf, "dns.", 4) != 0) return 0; /* Not au file! */ sf->sound_little_endian = 1; } data_type = get_long(sf, buf+0x0c); if (data_type < 1 || data_type > 5) return -1; /* Not supported format */ data_offset = get_long(sf, buf+0x04); /* data_size = get_long(buf+0x08); */ sf->sound_rate = get_long(sf, buf+0x10); sf->sound_channels = get_long(sf, buf+0x14); sf->sound_bits = type_info[data_type].bits; sf->sound_ulaw_encoded = type_info[data_type].ulaw; sf->sound_unsigned = 0; /* xxx */ return data_offset; } /* check Microsoft RIFF-WAVE header * return data offset * == 0 : Not wav file * < 0 : Not supported format */ int check_wav_header(sf, buf, n) SF *sf; unsigned char *buf; int n; { int i; long data_rate; long data_channel; unsigned char *p; long len; sf->sound_little_endian = 1; if (strncmp(buf, "RIFF", 4)) return 0; /* Not wav file */ len = get_long(sf, buf+4); if (strncmp(buf+8, "WAVE", 4)) return 0; /* Not wav file */ p = buf+12; if ((n -= 12) < 0) return 0; while (strncmp(p, "fmt ", 4) != 0) { len = get_long(sf, p+4); p += 8 + len; if ((n -= 8 + len) < 0) return 0; } len = get_long(sf, p+4); p += 8; if ((n -= 8) < 0) return 0; i = get_short(sf, p); switch (i) { case 0x0001: /* WAVE_FORMAT_PCM */ break; case 0x0000: /* WAVE_FORMAT_UNKNOWN */ case 0x0002: /* WAVE_FORMAT_ADPCM */ case 0x0006: /* WAVE_FORMAT_ALAW */ case 0x0007: /* WAVE_FORMAT_MULAW */ case 0x0010: /* WAVE_FORMAT_OKI_ADPCM */ case 0x0015: /* WAVE_FORMAT_DIGISTD */ case 0x0016: /* WAVE_FORMAT_DIGIFIX */ case 0x0101: /* IBM_FORMAT_MULAW */ case 0x0102: /* IBM_FORMAT_ALAW */ case 0x0103: /* IBM_FORMAT_ADPCM */ default: if (sound_debug) fprintf(stderr, "RIFF-WAVE format 0x%04x not supported!\n", i); return -1; /* Not supported format */ } data_channel = get_short(sf, p+2); data_rate = get_long(sf, p+4); /* long: average bytes/second */ /* short: block align */ i = get_short(sf, p+14); /* bits/sample? */ if (sound_debug >= 2) fprintf(stderr, "RIFF-WAVE %ld-channels, %ldHz, %d-bits/sample\n", data_channel, data_rate, i); switch (i) { case 8: i = 1; sf->sound_bits = 8; sf->sound_unsigned = 1; break; case 16: i = 2; sf->sound_bits = 16; sf->sound_unsigned = 0; break; case 32: i = 4; sf->sound_bits = 32; sf->sound_unsigned = 0; break; default: if (sound_debug) fprintf(stderr, "RIFF-WAVE 0x%04x-bits/sample not supported!\n", i); return -1; /* Not supported format */ } p += len; if ((n -= len) < 0) return 0; while (strncmp(p, "data", 4)) { len = get_long(sf, p+4); if (sound_debug >= 2) fprintf(stderr, "%08x %ld bytes section [%.4s] skip\n", p-buf, len, p); p += 8 + len; if ((n -= 8 + len) < 0) return 0; } /* number of samples = get_long(p+4) / i */ sf->sound_channels = data_channel; sf->sound_rate = data_rate; sf->sound_ulaw_encoded = 0; p += 8; if ((n -= 8) < 0) return 0; if (sound_debug >= 2) fprintf(stderr, "RIFF-WAVE offset %d\n", p - buf); return p - buf; /* return data offset */ } void disp_file_info(name) char *name; { if (use_esd) fprintf(stderr, "device: EsounD\n"); else fprintf(stderr, "device: %s\n", sound_device); fprintf(stderr, "input: %s\n", name == NULL ? "(stdin)" : name); } void disp_sound_info(sf, name) SF *sf; char *name; { fprintf(stderr, " %5dHz %2d-bit %d-channel", sf->sound_rate, sf->sound_bits, sf->sound_channels); if (sf->sound_ulaw_encoded) fprintf(stderr, " u-law"); if (sf->sound_little_endian) fprintf(stderr, " Little-endian"); if (!sf->sound_ulaw_encoded) fprintf(stderr, " %s", sf->sound_unsigned ? "unsigned" : "signed"); fprintf(stderr, "\n"); } #ifdef USE_DSP void disp_dsp_info() { fprintf(stderr, " %5dHz %2d-bit %d-channel\n", dsp_rate, dsp_bits, dsp_channels); } #endif /* USE_DSP */ void decide_midi_player() { char **p; char *midilist[] = { "/usr/bin/midiplay", "", "/usr/bin/playmidi", "", "/usr/local/bin/timidity", "-idq", NULL }; if (midi_player == NULL) { for(p = midilist; *p; p += 2) { if (is_regular_file(*p)) { midi_player = ks_malloc(strlen(*p) + 1 + strlen(*(p+1)) + 1); sprintf(midi_player, "%s %s", *p, *(p+1)); break; } } } } /* Check sound device * return 0:OK other:NG */ int sound_init() { int fd; struct stat st; cache_n = 0; decide_midi_player(); #if USE_ESD if (use_esd) { esd = esd_open_sound(esd_host); if (sound_debug >= 2) fprintf(stderr, "*esd open %d\n", esd); if (sound_debug >= 2 && esd >= 0) { esd_print_server_info(esd_get_all_info(esd)->server); } if (esd < 0) { fprintf(stderr, "Cannot to connect EsounD server ``%s''.\n", (esd_host ? (char *)esd_host : "(localhost)")); use_esd = 0; } } #endif /* USE_ESD */ if (!use_esd) { /* sound device exist? */ if (stat(sound_device, &st) != 0) return 1; /* device not found */ /* sound device writeble? */ fd = open(sound_device, O_WRONLY|O_NONBLOCK); if (fd < 0) return 2; /* device open error */ fcntl(fd, F_SETFL, ~O_NONBLOCK); close(fd); } sound_enable = 1; cache_list = (CACHE_TABLE *)malloc(sizeof(CACHE_TABLE)*SOUND_CACHE_MAX); return 0; } void sound_end() { int i; for (i = 0; i < cache_n; i++) { if (use_esd && (cache_list+i)->sf.sound_converted) { #if USE_ESD esd_sample_free(esd, (cache_list+i)->sf.sound_sample_id); esd_sample_stop(esd, (cache_list+i)->sf.sound_sample_id); #endif /* USE_ESD */ } else { free((cache_list+i)->buffer); } } if (cache_list != NULL) free(cache_list); #if USE_ESD if (use_esd) { esd_close(esd); if (sound_debug >= 2) fprintf(stderr, "*esd closed\n"); } #endif /* USE_ESD */ } /* return audio file size * minus : file not found */ long sound_filesize(name) char *name; { long r; struct stat st; r = -1; if (sound_enable && stat(name, &st) == 0) r = st.st_size; return r; } int cached_no(name) char *name; { int i; for (i = 0; i < cache_n; i++) if (strcmp(name, (cache_list+i)->pathname) == 0) return i; return -1; /* not cached */ } /* cache audio data * return minus cannot to cache */ int sound_cache(name) char *name; { char *p; FILE *fp; CACHE_TABLE *cp; if (!sound_enable) return -1; if (cached_no(name) >= 0) return 0; /* already cached */ if (cache_n >= SOUND_CACHE_MAX) return -1; cp = cache_list + cache_n; cp->size = sound_filesize(name); if (cp->size < 0) return -1; fp = fopen(name, "rb"); if (fp == NULL) return -1; /* Why? stat success, but fopen fail??? */ /* If name will be free, use strdup to keep string */ cp->pathname = name; p = malloc(cp->size); if (p == NULL) { fclose(fp); return -1; } fread(p, cp->size, 1, fp); cp->buffer = p; fclose(fp); bzero(&cp->sf, sizeof(cp->sf)); cp->data_ofs = check_wav_header(&cp->sf, cp->buffer, cp->size); if (!cp->data_ofs) cp->data_ofs = check_au_header(&cp->sf, cp->buffer, cp->size); if (cp->data_ofs < 0) { free(cp->buffer); return -1; } convert_sound(&cp->sf, cp->buffer + cp->data_ofs, cp->size - cp->data_ofs); cp->sf.sound_converted = 1; #if USE_ESD if (use_esd) { int confirm_id; esd_format_t format = 0; format = (cp->sf.sound_bits > 8 ? ESD_BITS16 : ESD_BITS8) | (cp->sf.sound_channels > 1 ? ESD_STEREO : ESD_MONO) | ESD_STREAM | ESD_PLAY; cp->sf.sound_sample_id = esd_sample_cache(esd, format, cp->sf.sound_rate, cp->size - cp->data_ofs, name); write(esd, cp->buffer + cp->data_ofs, cp->size - cp->data_ofs); confirm_id = esd_confirm_sample_cache(esd); if (confirm_id != cp->sf.sound_sample_id) { fprintf(stderr, "* confirm_id %d, sample_id %d\n", confirm_id, cp->sf.sound_sample_id); } free(cp->buffer); cp->buffer = NULL; } #endif /* USE_ESD */ cache_n++; if (sound_debug >= 2) fprintf(stderr, "sound_cache: ``%s'' cached.\n", name); return 0; } /* Play audio file * return 0: No error 1:file open error 2:device open error */ int sound_play(name) char *name; /* audio filename */ { int i; int fd = 0; int cn; long loadbytes; long ofs = 0; FILE *fp; char buf[32*1024]; /* 8 * N bytes */ char *bufp; SF sf, *sfp; char *buf2; #if USE_ESD int esd_rate; int sock = -1; esd_format_t format = 0; esd_info_t *esdctl; #endif /* USE_ESD */ if (!sound_enable) return 0; bufp = name; while (strstr(bufp + 1, ".mid") != NULL) bufp = strstr(bufp + 1, ".mid"); while (strstr(bufp + 1, ".MID") != NULL) bufp = strstr(bufp + 1, ".MID"); if ((!strncmp(bufp, ".mid", 4) || !strncmp(bufp, ".MID", 4)) && strlen(bufp) < 6) { buf2 = ks_malloc(strlen(midi_player) + 1 + strlen(name) + 1); sprintf(buf2, "%s %s", midi_player, name); ks_system2(buf2); return 0; } bzero(&sf, sizeof(sf)); bzero(buf, sizeof(buf)); if (name == NULL) { fp = stdin; bufp = buf; loadbytes = fread(buf, 1, sizeof(buf), fp); sfp = &sf; } else { cn = cached_no(name); if (cn >= 0) { fp = NULL; sfp = &(cache_list+cn)->sf; bufp = (cache_list+cn)->buffer; loadbytes = (cache_list+cn)->size; ofs = (cache_list+cn)->data_ofs; } else { fp = fopen(name, "rb"); if (fp == NULL) return 1; /* Sound file open error */ bufp = buf; loadbytes = fread(buf, 1, sizeof(buf), fp); sfp = &sf; } } if (sound_debug >= 2) disp_file_info(name); if (!sfp->sound_converted) { ofs = check_wav_header(sfp, bufp, loadbytes); if (ofs == 0) ofs = check_au_header(sfp, bufp); if (ofs < 0) { if (fp != NULL) fclose(fp); return 3; /* not supported format */ } adjust_parms(sfp); } if (sound_debug >= 2) disp_sound_info(sfp, name); if (use_esd) { #if USE_ESD if (!sfp->sound_converted) { format = (sfp->sound_bits > 8 ? ESD_BITS16 : ESD_BITS8) | (sfp->sound_channels > 1 ? ESD_STEREO : ESD_MONO) | ESD_STREAM | ESD_PLAY; esd_rate = sfp->sound_rate; sock = esd_play_stream_fallback(format, esd_rate, esd_host, name); esdctl = esd_get_all_info(esd); } #endif /* USE_ESD */ } else { fd = open(sound_device, O_WRONLY|O_NONBLOCK); if (fd < 0) { if (fp != NULL) fclose(fp); return 2; /* device open error */ } fcntl(fd, F_SETFL, ~O_NONBLOCK); #ifdef USE_DSP setup_dsp(fd, sfp); if (sound_debug >= 2) disp_dsp_info(); #endif /* USE_DSP */ } if (use_esd && sfp->sound_converted) { #if USE_ESD #if 0 fprintf(stderr, "* loop %d\n", sfp->sound_sample_id); esd_sample_loop(esd, sfp->sound_sample_id); #else esd_sample_play(esd, sfp->sound_sample_id); #endif #endif /* USE_ESD */ } else { while (loadbytes) { i = convert_sound(sfp, bufp+ofs, loadbytes-ofs); if (sound_debug >= 2) fprintf(stderr, "%d ", i); if (use_esd) { #if USE_ESD write(sock, bufp+ofs, i); #endif /* USE_ESD */ } else { play_sound(fd, bufp+ofs, i); } if (fp == NULL) { loadbytes = 0; } else { ofs = 0; loadbytes = fread(buf, 1, sizeof(buf), fp); } } } if (sound_debug >= 2) fputs("fin ", stderr); #if USE_ESD if (use_esd) { if (sock >= 0) { close(sock); } } #endif /* USE_ESD */ if (!use_esd) { #ifdef USE_DSP play_sync(fd); #endif if (sound_debug >= 2) fputs("close", stderr); close(fd); } if (sound_debug >= 2) fputs(" \n", stderr); if (fp != NULL) fclose(fp); return 0; } /* End of file */