diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
commit | 1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch) | |
tree | 4495d23e7b54ab5700e3839081e797c1eafe0db9 /cmd/ossplay | |
download | oss4-upstream.tar.gz |
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'cmd/ossplay')
-rw-r--r-- | cmd/ossplay/.config | 3 | ||||
-rw-r--r-- | cmd/ossplay/ossplay.c | 1254 | ||||
-rw-r--r-- | cmd/ossplay/ossplay.h | 291 | ||||
-rw-r--r-- | cmd/ossplay/ossplay.man | 53 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_console.c | 397 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_console.h | 37 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_decode.c | 1783 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_decode.h | 30 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_parser.c | 1830 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_parser.h | 8 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_wparser.c | 358 | ||||
-rw-r--r-- | cmd/ossplay/ossplay_wparser.h | 21 |
12 files changed, 6065 insertions, 0 deletions
diff --git a/cmd/ossplay/.config b/cmd/ossplay/.config new file mode 100644 index 0000000..5c1fcde --- /dev/null +++ b/cmd/ossplay/.config @@ -0,0 +1,3 @@ +cflags=$OGGDEFINE +ldflags=$DLOPENLDFLAGS +forgetos=VxWorks diff --git a/cmd/ossplay/ossplay.c b/cmd/ossplay/ossplay.c new file mode 100644 index 0000000..feab4fa --- /dev/null +++ b/cmd/ossplay/ossplay.c @@ -0,0 +1,1254 @@ +/* + * Purpose: Sources for the ossplay audio player and for the ossrecord + * audio recorder shipped with OSS. + * + * Description: + * OSSPlay is a audio file player that supports most commonly used uncompressed + * audio formats (.wav, .snd, .au, .aiff). It doesn't play compressed formats + * such as MP3. + * OSSRecord is a simple file recorder. It can write simple file formats + * (.wav, .au, .aiff). + * + * This file contains the audio backend and misc. functions. + * + * This program is bit old and it uses some OSS features that may no longer be + * required. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "ossplay_decode.h" +#include "ossplay_parser.h" +#include "ossplay_wparser.h" + +#include <signal.h> +#include <strings.h> +#include <unistd.h> + +unsigned int amplification = 100; +int eflag = 0, force_speed = 0, force_fmt = 0, force_channels = 0, + overwrite = 1, verbose = 0, quiet = 0; +flag from_stdin = 0, int_conv = 0, level_meters = 0, loop = 0, + raw_file = 0, raw_mode = 0; +double seek_time = 0; +long seek_byte = 0; +off_t (*ossplay_lseek) (int, off_t, int) = lseek; + +char script[512] = ""; +unsigned int nfiles = 1; +double datalimit = 0; +fctypes_t type = WAVE_FILE; + +const format_t format_a[] = { + {"S8", AFMT_S8, CRP, AFMT_S16_NE}, + {"U8", AFMT_U8, CRP, AFMT_S16_NE}, + {"S16_LE", AFMT_S16_LE, CRP, AFMT_S16_NE}, + {"S16_BE", AFMT_S16_BE, CRP, AFMT_S16_NE}, + {"U16_LE", AFMT_U16_LE, CRP, AFMT_S16_NE}, + {"U16_BE", AFMT_U16_BE, CRP, AFMT_S16_NE}, + {"S24_LE", AFMT_S24_LE, CRP, 0}, + {"S24_BE", AFMT_S24_BE, CRP, 0}, + {"S32_LE", AFMT_S32_LE, CRP, AFMT_S32_NE}, + {"S32_BE", AFMT_S32_BE, CRP, AFMT_S32_NE}, + {"A_LAW", AFMT_A_LAW, CRP, AFMT_S16_NE}, + {"MU_LAW", AFMT_MU_LAW, CRP, AFMT_S16_NE}, + {"FLOAT32_LE", AFMT_FLOAT32_LE, CP, 0}, + {"FLOAT32_BE", AFMT_FLOAT32_BE, CP, 0}, + {"DOUBLE64_LE", AFMT_DOUBLE64_LE, CP, 0}, + {"DOUBLE64_BE", AFMT_DOUBLE64_BE, CP, 0}, + {"S24_PACKED", AFMT_S24_PACKED, CRP, 0}, + {"S24_PACKED_BE", AFMT_S24_PACKED_BE, CP, 0}, + {"IMA_ADPCM", AFMT_IMA_ADPCM, CP, 0}, + {"IMA_ADPCM_3BITS", AFMT_MS_IMA_ADPCM_3BITS,CP, 0}, + {"MS_ADPCM", AFMT_MS_ADPCM, CP, 0}, + {"CR_ADPCM_2", AFMT_CR_ADPCM_2, CP, 0}, + {"CR_ADPCM_3", AFMT_CR_ADPCM_3, CP, 0}, + {"CR_ADPCM_4", AFMT_CR_ADPCM_4, CP, 0}, + {"SPDIF_RAW", AFMT_SPDIF_RAW, CR, 0}, + {"FIBO_DELTA", AFMT_FIBO_DELTA, CP, 0}, + {"EXP_DELTA", AFMT_EXP_DELTA, CP, 0}, + {NULL, 0, CP, 0} +}; + +static const container_t container_a[] = { + {"RAW", RAW_FILE, AFMT_S16_LE, 2, 44100}, + {"WAV", WAVE_FILE, AFMT_S16_LE, 2, 48000}, + {"AU", AU_FILE, AFMT_MU_LAW, 1, 8000}, + {"AIFF", AIFF_FILE, AFMT_S16_BE, 2, 48000}, + {"CAF", CAF_FILE, AFMT_S16_NE, 2, 48000}, + {NULL, RAW_FILE, 0, 0, 0} +}; /* Order should match fctypes_t enum so that container_a[type] works */ + +static void describe_error (void); +static void find_devname (char *, const char *); +static fctypes_t select_container (const char *); +static int select_format (const char *, int); +static void ossplay_usage (const char *); +static void ossrecord_usage (const char *); +static void ossplay_getint (int); +static void print_play_verbose_info (const unsigned char *, ssize_t, void *); +static void print_record_verbose_info (const unsigned char *, ssize_t, void *); + +big_t +be_int (const unsigned char * p, int l) +{ + int i; + big_t val; + + val = 0; + + for (i = 0; i < l; i++) + { + val = (val << 8) | p[i]; + } + + return val; +} + +big_t +le_int (const unsigned char * p, int l) +{ + int i; + big_t val; + + val = 0; + + for (i = l - 1; i >= 0; i--) + { + val = (val << 8) | p[i]; + } + + return val; +} + +static void +describe_error (void) +{ + switch (errno) + { + case ENXIO: + case ENODEV: + print_msg (ERRORM, "\nThe device file was found in /dev but\n" + "there is no driver for it currently loaded.\n" + "\n" + "You can start it by executing the soundon command as\n" + "super user (root).\n"); + break; + + case ENOSPC: + print_msg (ERRORM, "\nThe soundcard driver was not installed\n" + "properly. The system is out of DMA compatible memory.\n" + "Please reboot your system and try again.\n"); + + break; + + case ENOENT: + print_msg (ERRORM, "\nThe sound device file is missing from /dev.\n" + "You should try re-installing OSS.\n"); + break; + + case EBUSY: + print_msg (ERRORM, + "\nThere is some other application using this audio device.\n" + "Exit it and try again.\n"); + print_msg (ERRORM, + "You can possibly find out the conflicting application by" + "looking\n", + "at the printout produced by command 'ossinfo -a -v1'\n"); + break; + + default:; + } +} + +static void +find_devname (char * devname, const char * num) +{ +/* + * OSS 4.0 the audio device numbering may be different from the + * legacy /dev/dsp# numbering reported by /dev/sndstat. Try to find the + * device name (devnode) that matches the given device number. + * + * Prior versions of ossplay simply used the the /dev/dsp# number. + */ + int dev; + int mixer_fd; + oss_audioinfo ai; + const char * devmixer; + + if ((devmixer = getenv("OSS_MIXERDEV")) == NULL) + devmixer = "/dev/mixer"; + + if (sscanf (num, "%d", &dev) != 1) + { + print_msg (ERRORM, "Invalid audio device number '%s'\n", num); + exit (E_SETUP_ERROR); + } + + if ((mixer_fd = open (devmixer, O_RDWR, 0)) == -1) + { + perror_msg (devmixer); + print_msg (WARNM, "Warning: Defaulting to /dev/dsp%s\n", num); + snprintf (devname, OSS_DEVNODE_SIZE, "/dev/dsp%s", num); + return; + } + + ai.dev = dev; + + if (ioctl (mixer_fd, SNDCTL_AUDIOINFO, &ai) == -1) + { + perror_msg ("SNDCTL_AUDIOINFO"); + print_msg (WARNM, "Warning: Defaulting to /dev/dsp%s\n", num); + snprintf (devname, OSS_DEVNODE_SIZE, "/dev/dsp%s", num); + close (mixer_fd); + return; + } + + strncpy (devname, ai.devnode, OSS_DEVNODE_SIZE); + + close (mixer_fd); + return; +} + +const char * +filepart (const char *name) +{ + const char * s = name; + + if (name == NULL) return ""; + + while (*name) + { + if (name[0] == '/' && name[1] != '\0') + s = name + 1; + name++; + } + + return s; +} + +float +format2bits (int format) +{ + switch (format) + { + case AFMT_CR_ADPCM_2: return 2; + case AFMT_CR_ADPCM_3: return 2.6666F; + case AFMT_MS_IMA_ADPCM_3BITS: return 3; + case AFMT_CR_ADPCM_4: + case AFMT_MAC_IMA_ADPCM: + case AFMT_MS_IMA_ADPCM: + case AFMT_IMA_ADPCM: + case AFMT_MS_ADPCM: + case AFMT_FIBO_DELTA: + case AFMT_EXP_DELTA: return 4; + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_U8: + case AFMT_S8: return 8; + case AFMT_VORBIS: + case AFMT_MPEG: + case AFMT_S16_LE: + case AFMT_S16_BE: + case AFMT_U16_LE: + case AFMT_U16_BE: return 16; + case AFMT_S24_PACKED: + case AFMT_S24_PACKED_BE: return 24; + case AFMT_S24_LE: + case AFMT_S24_BE: + case AFMT_SPDIF_RAW: + case AFMT_FLOAT32_LE: + case AFMT_FLOAT32_BE: + case AFMT_S32_LE: + case AFMT_S32_BE: return 32; + case AFMT_DOUBLE64_LE: + case AFMT_DOUBLE64_BE: return 64; + case AFMT_FLOAT: return sizeof (float) * 8; + case AFMT_QUERY: + default: return 0; + } +} + +void +close_device (dspdev_t * dsp) +{ + if (dsp->fd == -1) return; + close (dsp->fd); + dsp->fd = -1; +} + +void +open_device (dspdev_t * dsp) +{ + const char * devdsp; + + if (dsp->fd >= 0) + close_device (dsp); + + dsp->format = 0; dsp->channels = 0; dsp->speed = 0; + + if ((devdsp = getenv("OSS_AUDIODEV")) == NULL) + devdsp = "/dev/dsp"; + + if (raw_mode) + dsp->flags |= O_EXCL; /* Disable redirection to the virtual mixer */ + + if (dsp->dname[0] == '\0') strcpy (dsp->dname, devdsp); + + if ((dsp->fd = open (dsp->dname, dsp->flags, 0)) == -1) + { + perror_msg (dsp->dname); + describe_error (); + exit (E_SETUP_ERROR); + } + + if (raw_mode) + { + /* + * Disable sample rate/format conversions. + */ + int tmp = 0; + ioctl (dsp->fd, SNDCTL_DSP_COOKEDMODE, &tmp); + } +} + +static void +ossplay_usage (const char * prog) +{ + print_msg (HELPM, "Usage: %s [options] filename(s)\n", prog?prog:"ossplay"); + print_msg (HELPM, " Options: -v Verbose output.\n"); + print_msg (HELPM, " -q No informative printouts.\n"); + print_msg (HELPM, " -d<devname> Change output device.\n"); + print_msg (HELPM, " -g<gain> Change gain.\n"); + print_msg (HELPM, " -s<rate> Change playback rate.\n"); + print_msg (HELPM, " -f<fmt>|? Change/Query input format.\n"); + print_msg (HELPM, " -c<channels> Change number of channels.\n"); + print_msg (HELPM, " -o<playtgt>|? Select/Query output target.\n"); + print_msg (HELPM, " -l Loop playback indefinitely.\n"); + print_msg (HELPM, " -W Treat all input as raw PCM.\n"); + print_msg (HELPM, " -S<secs> Start playing from offset.\n"); + print_msg (HELPM, + " -R Open sound device in raw mode.\n"); + exit (E_USAGE); +} + +static void +ossrecord_usage (const char * prog) +{ + print_msg (HELPM, "Usage: %s [options] filename\n", prog?prog:"ossrecord"); + print_msg (HELPM, " Options: -v Verbose output.\n"); + print_msg (HELPM, " -d<device> Change input device.\n"); + print_msg (HELPM, " -c<channels> Change number of channels\n"); + print_msg (HELPM, " -L<level> Change recording level.\n"); + print_msg (HELPM, + " -g<gain> Change gain percentage.\n"); + print_msg (HELPM, " -s<rate> Change recording rate.\n"); + print_msg (HELPM, " -f<fmt|?> Change/Query sample format.\n"); + print_msg (HELPM, + " -F<cnt|?> Change/Query container format.\n"); + print_msg (HELPM, " -l Display level meters.\n"); + print_msg (HELPM, + " -i<recsrc|?> Select/Query recording source.\n"); + print_msg (HELPM, + " -m<nfiles> Repeat recording <nfiles> times.\n"); + print_msg (HELPM, + " -r<command> Run <command> after recording.\n"); + print_msg (HELPM, + " -t<maxsecs> Record no more than <maxsecs> in a" + " single recording.\n"); + print_msg (HELPM, + " -R Open sound device in raw mode.\n"); + print_msg (HELPM, " -O Do not allow overwrite.\n"); + exit (E_USAGE); +} + +const char * +sample_format_name (int sformat) +{ + int i; + + for (i = 0; format_a[i].fmt != 0; i++) + if (format_a[i].fmt == sformat) + return format_a[i].name; + + return ""; +} + +static fctypes_t +select_container (const char * optstr) +{ +/* + * Handling of the -F command line option (force container format). + * + * Empty or "?" shows the supported container format names. + */ + int i; + + if ((!strcmp(optstr, "?")) || (*optstr == '\0')) + { + print_msg (STARTM, "\nSupported container format names are:\n\n"); + for (i = 0; container_a[i].name != NULL; i++) + print_msg (CONTM, "%s ", container_a[i].name); + print_msg (ENDM, "\n"); + exit (0); + } + + for (i = 0; container_a[i].name != NULL; i++) + if (!strcasecmp(container_a[i].name, optstr)) + return container_a[i].type; + + print_msg (ERRORM, "Unsupported container format name '%s'!\n", optstr); + exit (E_USAGE); +} + +static int +select_format (const char * optstr, int dir) +{ +/* + * Handling of the -f command line option (force input format). + * + * Empty or "?" shows the supported format names. + */ + int i; + + if ((!strcmp(optstr, "?")) || (*optstr == '\0')) + { + print_msg (STARTM, "\nSupported format names are:\n\n"); + for (i = 0; format_a[i].name != NULL; i++) + if (dir & format_a[i].dir) + print_msg (CONTM, "%s ", format_a[i].name); + print_msg (ENDM, "\n"); + exit (0); + } + + for (i = 0; format_a[i].name != NULL; i++) + if ((format_a[i].dir & dir) && (!strcasecmp(format_a[i].name, optstr))) + return format_a[i].fmt; + + print_msg (ERRORM, "Unsupported format name '%s'!\n", optstr); + exit (E_USAGE); +} + +void +select_playtgt (dspdev_t * dsp) +{ +/* + * Handling of the -o command line option (playback target selection). + * + * Empty or "?" shows the available playback sources. + */ + int i, src; + oss_mixer_enuminfo ei; + + if (ioctl (dsp->fd, SNDCTL_DSP_GET_PLAYTGT_NAMES, &ei) == -1) + { + perror_msg ("SNDCTL_DSP_GET_PLAYTGT_NAMES"); + exit (E_SETUP_ERROR); + } + + if (ioctl (dsp->fd, SNDCTL_DSP_GET_PLAYTGT, &src) == -1) + { + perror_msg ("SNDCTL_DSP_GET_PLAYTGT"); + exit (E_SETUP_ERROR); + } + + if ((dsp->playtgt[0] == '\0') || (strcmp (dsp->playtgt, "?") == 0)) + { + print_msg (STARTM, + "\nPossible playback targets for the selected device:\n\n"); + + for (i = 0; i < ei.nvalues; i++) + { + print_msg (CONTM, "\t%s", ei.strings + ei.strindex[i]); + if (i == src) + print_msg (CONTM, " (currently selected)"); + print_msg (CONTM, "\n"); + } + print_msg (ENDM, "\n"); + exit (0); + } + + for (i = 0; i < ei.nvalues; i++) + { + char *s = ei.strings + ei.strindex[i]; + if (strcmp (s, dsp->playtgt) == 0) + { + src = i; + if (ioctl (dsp->fd, SNDCTL_DSP_SET_PLAYTGT, &src) == -1) + { + perror_msg ("SNDCTL_DSP_SET_PLAYTGT"); + exit (E_SETUP_ERROR); + } + + return; + } + } + + print_msg (ERRORM, + "Unknown playback target name '%s' - use -o? to get the list\n", + dsp->playtgt); + exit (E_USAGE); +} + +void +select_recsrc (dspdev_t * dsp) +{ +/* + * Handling of the -i command line option (recording source selection). + * + * Empty or "?" shows the available recording sources. + */ + int i, src; + oss_mixer_enuminfo ei; + + if (ioctl (dsp->fd, SNDCTL_DSP_GET_RECSRC_NAMES, &ei) == -1) + { + perror_msg ("SNDCTL_DSP_GET_RECSRC_NAMES"); + exit (E_SETUP_ERROR); + } + + if (ioctl (dsp->fd, SNDCTL_DSP_GET_RECSRC, &src) == -1) + { + perror_msg ("SNDCTL_DSP_GET_RECSRC"); + exit (E_SETUP_ERROR); + } + + if (dsp->recsrc[0] == '\0' || strcmp (dsp->recsrc, "?") == 0) + { + print_msg (STARTM, + "\nPossible recording sources for the selected device:\n\n"); + + for (i = 0; i < ei.nvalues; i++) + { + print_msg (CONTM, "\t%s", ei.strings + ei.strindex[i]); + if (i == src) + print_msg (CONTM, " (currently selected)"); + print_msg (CONTM, "\n"); + } + print_msg (ENDM, "\n"); + exit (0); + } + + for (i = 0; i < ei.nvalues; i++) + { + char *s = ei.strings + ei.strindex[i]; + if (strcmp (s, dsp->recsrc) == 0) + { + src = i; + if (ioctl (dsp->fd, SNDCTL_DSP_SET_RECSRC, &src) == -1) + { + perror_msg ("SNDCTL_DSP_SET_RECSRC"); + exit (E_SETUP_ERROR); + } + return; + } + } + + print_msg (ERRORM, + "Unknown recording source name '%s' - use -i? to get the list\n", + dsp->recsrc); + exit (E_USAGE); +} + +errors_t +setup_device (dspdev_t * dsp, int format, int channels, int speed) +{ + int tmp; + + if (dsp->speed != speed || dsp->format != format || + dsp->channels != channels || dsp->fd == -1) + { +#if 0 + ioctl (dsp->fd, SNDCTL_DSP_SYNC, NULL); + ioctl (dsp->fd, SNDCTL_DSP_HALT, NULL); +#else + close_device (dsp); + open_device (dsp); + if (dsp->playtgt != NULL) select_playtgt (dsp); + if (dsp->recsrc != NULL) select_recsrc (dsp); +#endif + } + else + { + ioctl (dsp->fd, SNDCTL_SETSONG, dsp->current_songname); + return E_OK; + } + + /* + * Report the current filename as the song name. + */ + ioctl (dsp->fd, SNDCTL_SETSONG, dsp->current_songname); + + tmp = APF_NORMAL; + ioctl (dsp->fd, SNDCTL_DSP_PROFILE, &tmp); + + tmp = format; + + if (ioctl (dsp->fd, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + perror_msg (dsp->dname); + print_msg (ERRORM, "Failed to select bits/sample\n"); + return E_SETUP_ERROR; + } + + if (tmp != format) + { + print_msg (ERRORM, "%s doesn't support this audio format (%x/%x).\n", + dsp->dname, format, tmp); + return E_FORMAT_UNSUPPORTED; + } + + tmp = channels; + + if (ioctl (dsp->fd, SNDCTL_DSP_CHANNELS, &tmp) == -1) + { + perror_msg (dsp->dname); + print_msg (ERRORM, "Failed to select number of channels.\n"); + return E_SETUP_ERROR; + } + + if (tmp != channels) + { +#ifdef SRC_SUPPORT + /* We'll convert mono to stereo, so it's no use warning */ + if ((channels != 1) || (tmp != 2)) +#endif + print_msg (ERRORM, "%s doesn't support %d channels (%d).\n", + dsp->dname, channels, tmp); + return E_CHANNELS_UNSUPPORTED; + } + + tmp = speed; + + if (ioctl (dsp->fd, SNDCTL_DSP_SPEED, &tmp) == -1) + { + perror_msg (dsp->dname); + print_msg (ERRORM, "Failed to select sampling rate.\n"); + return E_SETUP_ERROR; + } + +#ifndef SRC_SUPPORT + if (tmp != speed) + { + print_msg (WARNM, "Warning: Playback using %d Hz (file %d Hz)\n", + tmp, speed); + } +#endif + + dsp->speed = tmp; + dsp->channels = channels; + dsp->format = format; + + if (verbose > 1) + print_msg (VERBOSEM, "Setup device %s/%d/%d\n", + sample_format_name (dsp->format), dsp->channels, dsp->speed); + + if (dsp->reclevel != 0) + { + tmp = dsp->reclevel | (dsp->reclevel << 8); + + if (ioctl (dsp->fd, SNDCTL_DSP_SETRECVOL, &tmp) == -1) + perror ("SNDCTL_DSP_SETRECVOL"); + } + + return E_OK; +} + +static void +ossplay_getint (int signum) +{ +#if 0 + if (eflag == signum + 128) + { + signal (signum, SIG_DFL); + raise (signum); + } +#endif + eflag = signum + 128; +} + +int +ossplay_parse_opts (int argc, char ** argv, dspdev_t * dsp) +{ + extern char * optarg; + extern int optind; + char * p; + int c; + + while ((c = getopt (argc, argv, "FRS:Wc:d:f:g:hlo:qs:v")) != EOF) + { + switch (c) + { + case 'v': + verbose++; + quiet = 0; + int_conv = 2; + break; + + case 'R': + raw_mode = 1; + break; + + case 'q': + quiet++; + verbose = 0; + if (int_conv == 2) int_conv = 0; + break; + + case 'd': + if (*optarg >= '0' && *optarg <= '9') /* Only device number given */ + find_devname (dsp->dname, optarg); + else + snprintf (dsp->dname, OSS_DEVNODE_SIZE, "%s", optarg); + break; + + case 'o': + if (!strcmp(optarg, "?")) + { + dsp->playtgt = optarg; + dsp->flags = O_WRONLY; + open_device (dsp); + select_playtgt (dsp); + } + dsp->playtgt = optarg; + break; + + case 'f': + force_fmt = select_format (optarg, CP); + break; + + case 's': + sscanf (optarg, "%d", &force_speed); + break; + + case 'c': + sscanf (optarg, "%d", &force_channels); + break; + + case 'g': + sscanf (optarg, "%u", &lification); + int_conv = 1; + break; + + case 'l': + loop = 1; + break; + + case 'F': + case 'W': + raw_file = 1; + break; + + case 'S': + c = strlen (optarg); + if ((c > 0) && ((optarg[c - 1] == 'b') || (optarg[c - 1] == 'B'))) + { + errno = 0; + seek_byte = strtol (optarg, &p, 10); + if ((*p != '\0') || (seek_byte < 0)) ossplay_usage (argv[0]); + } + else + { + errno = 0; + seek_time = strtod (optarg, &p); + if ((*p != '\0') || (errno) || (seek_time < 0)) ossplay_usage (argv[0]); + } + break; + + default: + ossplay_usage (argv[0]); + } + + } + + if (argc < optind + 1) + ossplay_usage (argv[0]); + +#ifdef SIGQUIT + signal (SIGQUIT, ossplay_getint); +#endif + return optind; +} + +int +ossrecord_parse_opts (int argc, char ** argv, dspdev_t * dsp) +{ + char * p; + int c; + extern char * optarg; + extern int optind; + + if (argc < 2) + ossrecord_usage (argv[0]); + + while ((c = getopt (argc, argv, "F:L:MORSb:c:d:f:g:hi:lm:r:s:t:wv")) != EOF) + switch (c) + { + case 'F': + type = select_container (optarg); + break; + + case 'L': + dsp->reclevel = atoi (optarg); + if (dsp->reclevel < 1 || dsp->reclevel > 100) + { + print_msg (ERRORM, "%s: Bad recording level '%s'\n", + argv[0]?argv[0]:"", optarg); + exit (-1); + } + break; + + case 'M': + force_channels = 1; + break; + + case 'R': + raw_mode = 1; + break; + + case 'S': + force_channels = 2; + break; + + case 'b': + c = atoi (optarg); + c += c % 8; /* Simple WAV format always pads to a multiple of 8 */ + switch (c) + { + case 8: force_fmt = AFMT_U8; break; + case 16: force_fmt = AFMT_S16_LE; break; + case 24: force_fmt = AFMT_S24_PACKED; break; + case 32: force_fmt = AFMT_S32_LE; break; + default: + print_msg (ERRORM, "Error: Unsupported number of bits %d\n", c); + exit (E_FORMAT_UNSUPPORTED); + } + break; + + case 'c': + sscanf (optarg, "%d", &force_channels); + break; + + case 'd': + if (*optarg >= '0' && *optarg <= '9') /* Only device number given */ + find_devname (dsp->dname, optarg); + else + snprintf (dsp->dname, OSS_DEVNODE_SIZE, "%s", optarg); + break; + + case 'f': + force_fmt = select_format (optarg, CR); + break; + + case 'g': + sscanf (optarg, "%u", &lification); + if (amplification == 0) ossrecord_usage (argv[0]); + + case 'l': + level_meters = 1; + verbose = 1; + break; + + case 'i': + if (!strcmp(optarg, "?")) + { + dsp->recsrc = optarg; + dsp->flags = O_RDONLY; + open_device (dsp); + select_recsrc (dsp); + } + dsp->recsrc = optarg; + break; + + case 'm': + sscanf (optarg, "%u", &nfiles); + break; + + case 's': + sscanf (optarg, "%d", &force_speed); + if (force_speed == 0) + { + print_msg (ERRORM, "Bad sampling rate given\n"); + exit (E_USAGE); + } + if (force_speed < 1000) force_speed *= 1000; + break; + + case 'r': + c = snprintf (script, sizeof (script), "%s", optarg); + if (((size_t)c >= sizeof (script)) || (c < 0)) + { + print_msg (ERRORM, "-r argument is too long!\n"); + exit (E_USAGE); + } + break; + + case 't': + errno = 0; + datalimit = strtod (optarg, &p); + if ((*p != '\0') || (errno) || (datalimit <= 0)) ossrecord_usage (argv[0]); + break; + + case 'O': + overwrite = 0; + break; + + case 'w': + break; + + case 'v': + verbose = 1; + break; + + case 'h': + default: + ossrecord_usage (argv[0]); + } + + if (argc != optind + 1) + /* No file or multiple file names given */ + ossrecord_usage (argv[0]); + + if (force_fmt == 0) force_fmt = container_a[type].dformat; + if (force_channels == 0) force_channels = container_a[type].dchannels; + if (force_speed == 0) force_speed = container_a[type].dspeed; + switch (force_fmt) + { + case AFMT_S8: + case AFMT_U8: + case AFMT_S16_NE: + case AFMT_S24_NE: + case AFMT_S32_NE: break; + default: level_meters = 0; /* Not implemented */ + } + + if ((signal (SIGSEGV, ossplay_getint) == SIG_ERR) || +#ifdef SIGPIPE + (signal (SIGPIPE, ossplay_getint) == SIG_ERR) || +#endif + (signal (SIGTERM, ossplay_getint) == SIG_ERR) || +#ifdef SIGQUIT + (signal (SIGQUIT, ossplay_getint) == SIG_ERR) || +#endif + (signal (SIGINT, ossplay_getint) == SIG_ERR)) + print_msg (WARNM, "Signal handler not set up!\n"); + + if (verbose) + { + oss_audioinfo ai; + + ai.dev = -1; + + if (ioctl(dsp->fd, SNDCTL_ENGINEINFO, &ai) != -1) + print_msg (VERBOSEM, "Recording from %s\n", ai.name); + } + + return optind; +} + +ldouble_t +ossplay_ldexpl (ldouble_t num, int exp) +{ + /* + * Very simple emulation of ldexpl to avoid linking to libm or assuming + * anything about float representation. + */ + if (exp > 0) + { + while (exp > 31) + { + num *= 1UL << 31; + exp -= 31; + } + num *= 1UL << exp; + } + else if (exp < 0) + { + while (exp < -31) + { + num /= 1UL << 31; + exp += 31; + } + num /= 1UL << -exp; + } + + return num; +} + +static void +print_play_verbose_info (const unsigned char * buf, ssize_t l, void * metadata) +{ +/* + * Display a rough recording level meter, and the elapsed time. + */ + + verbose_values_t * val = (verbose_values_t *)metadata; + + val->secs += l/val->constant; + if (val->secs < val->next_sec) return; + val->next_sec += PLAY_UPDATE_INTERVAL/1000; + /* + * This check is done to ensure an update at the end of the playback. + * Note that some files lie about total time, so the second condition is + * necessary so that updates will still be constricted by PLAY_UPDATE_INTERVAL. + */ + if ((val->next_sec > val->tsecs) && (val->secs < val->tsecs)) val->next_sec = val->tsecs; + + print_update (get_db_level (buf, l, val->format), val->secs, val->tstring); + + return; +} + +static void +print_record_verbose_info (const unsigned char * buf, ssize_t l, + void * metadata) +{ +/* + * Display a rough recording level meter if enabled, and the elapsed time. + */ + + verbose_values_t * val = (verbose_values_t *)metadata; + int update_dots = 1; + + val->secs += l / val->constant; + + if (val->secs >= val->next_sec) + { + val->next_sec += REC_UPDATE_INTERVAL/1000; + if ((val->tsecs) && (val->next_sec > val->tsecs)) + val->next_sec = val->tsecs; + if (level_meters) + { + val->secs_timer2 = val->next_sec_timer2 = val->secs; + goto print_level; + } + print_record_update (-1, val->secs, val->tstring, 1); + } + else if ((level_meters) && (val->secs >= val->next_sec_timer2)) + { + update_dots = 0; +print_level: + val->next_sec_timer2 += LMETER_UPDATE_INTERVAL/1000; + if ((val->tsecs) && (val->next_sec_timer2 > val->tsecs)) + val->next_sec_timer2 = val->tsecs; + print_record_update (get_db_level (buf, l, val->format), val->secs_timer2, + val->tstring, update_dots); + } +} + +int +play (dspdev_t * dsp, int fd, big_t * datamark, big_t bsize, double total_time, + double constant, readfunc_t * readf, decoders_queue_t * dec, seekfunc_t * seekf) +{ +#define EXITPLAY(code) \ + do { \ + ossplay_free (buf); \ + ossplay_free (verbose_meta); \ + clear_update (); \ + ioctl (dsp->fd, SNDCTL_DSP_HALT_OUTPUT, NULL); \ + errno = 0; \ + return (code); \ + } while (0) + + big_t rsize = bsize; + big_t filesize = *datamark; + ssize_t outl; + unsigned char * buf, * obuf, contflag = 0; + decoders_queue_t * d; + verbose_values_t * verbose_meta = NULL; + + buf = (unsigned char *)ossplay_malloc (bsize); + + if (verbose) + { + verbose_meta = setup_verbose (dsp->format, + format2bits(dsp->format) * dsp->channels * + dsp->speed / 8.0, total_time); + if (seek_time == 0) print_play_verbose_info (NULL, 0, verbose_meta); + } + + *datamark = 0; + + while (*datamark < filesize) + { + if (eflag) EXITPLAY (eflag); + + rsize = bsize; + if (rsize > filesize - *datamark) rsize = filesize - *datamark; + + if ((seek_time != 0) && (seekf != NULL)) + { + errors_t ret; + + ret = seekf (fd, datamark, filesize, constant, rsize, dsp->channels, + dec->metadata); + if (ret == E_OK) + { + if (verbose) + { + verbose_meta->secs = (double)seek_time; + verbose_meta->next_sec = (double)seek_time; + print_play_verbose_info (NULL, 0, verbose_meta); + } + seek_time = 0; + continue; + } + else if (ret == SEEK_CONT_AFTER_DECODE) contflag = 1; + else EXITPLAY (ret); + } + + if ((outl = readf (fd, buf, rsize, dec->metadata)) <= 0) + { + if (errno) perror_msg ("read"); + if ((filesize != BIG_SPECIAL) && (*datamark < filesize) && (!eflag)) + { + print_msg (NOTIFYM, "Sound data ended prematurely!\n"); + } + EXITPLAY (eflag); + } + *datamark += outl; + + if (contflag) + { + contflag = 0; + continue; + } + + obuf = buf; d = dec; + do + { + outl = d->decoder (&(d->outbuf), obuf, outl, d->metadata); + obuf = d->outbuf; + d = d->next; + } + while (d != NULL); + + if (verbose) print_play_verbose_info (obuf, outl, verbose_meta); + if (write (dsp->fd, obuf, outl) == -1) + { + if ((errno == EINTR) && (eflag)) EXITPLAY (eflag); + ossplay_free (buf); + perror_msg ("audio write"); + exit (E_DECODE); + } + } + + ossplay_free (buf); + ossplay_free (verbose_meta); + clear_update (); + return 0; +} + +int +record (dspdev_t * dsp, FILE * wave_fp, const char * filename, double constant, + double datatime, big_t * data_size, decoders_queue_t * dec) +{ +#define EXITREC(code) \ + do { \ + ossplay_free (buf); \ + ossplay_free (verbose_meta); \ + clear_update (); \ + if ((eflag) && (verbose)) \ + print_msg (VERBOSEM, "\nStopped (%d).\n", eflag-128); \ + ioctl (dsp->fd, SNDCTL_DSP_HALT_INPUT, NULL); \ + return (code); \ + } while(0) + + unsigned char * buf, * obuf; + ssize_t l, outl; + big_t data_size_limit = *data_size; + decoders_queue_t * d; + verbose_values_t * verbose_meta = NULL; + + if (verbose) + { + verbose_meta = setup_verbose (dsp->format, constant, datatime); + strncpy (verbose_meta->tstring, filename, 20)[19] = 0; + } + + *data_size = 0; + buf = (unsigned char *)ossplay_malloc (RECBUF_SIZE); + /*LINTED*/ while (1) + { + if ((l = read (dsp->fd, buf, RECBUF_SIZE)) < 0) + { + if ((errno == EINTR) && (eflag)) EXITREC (eflag); + if (errno == ECONNRESET) EXITREC (E_ENCODE); /* Device disconnected */ + perror_msg (dsp->dname); + EXITREC (E_ENCODE); + } + if (l == 0) + { + print_msg (ERRORM, "Unexpected EOF on audio device\n"); + EXITREC (eflag); + } + + obuf = buf; d = dec; outl = l; + do + { + outl = d->decoder (&(d->outbuf), obuf, outl, d->metadata); + obuf = d->outbuf; + d = d->next; + } + while (d != NULL); + + if (eflag) EXITREC (eflag); + + if (fwrite (obuf, outl, 1, wave_fp) != 1) + { + if ((errno == EINTR) && (eflag)) EXITREC (eflag); + perror_msg (filename); + EXITREC (E_ENCODE); + } + + *data_size += outl; + if (verbose) print_record_verbose_info (obuf, outl, verbose_meta); + + if ((datalimit != 0) && (*data_size >= data_size_limit)) break; + } + + ossplay_free (buf); + ossplay_free (verbose_meta); + clear_update (); + print_msg (VERBOSEM, "\nDone.\n"); + return 0; +} + +errors_t +silence (dspdev_t * dsp, big_t len, int speed) +{ + errors_t ret; + ssize_t i; + unsigned char empty[1024]; + + ret = setup_device (dsp, AFMT_U8, 1, speed); + + if (ret == E_FORMAT_UNSUPPORTED) + { + len *= 4; + if ((ret = setup_device (dsp, AFMT_S16_NE, 2, speed))) return ret; + } + else if (ret) return ret; + + memset (empty, 0, 1024 * sizeof (unsigned char)); + + while (len > 0) + { + i = 1024; + if ((big_t)i > len) i = len; + if ((i = write (dsp->fd, empty, i)) < 0) return -1; + + len -= i; + } + + return E_OK; +} diff --git a/cmd/ossplay/ossplay.h b/cmd/ossplay/ossplay.h new file mode 100644 index 0000000..226ad5c --- /dev/null +++ b/cmd/ossplay/ossplay.h @@ -0,0 +1,291 @@ +#ifndef OSSPLAY_H +#define OSSPLAY_H + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#include <soundcard.h> +#include "ossplay_console.h" + +#define PLAYBUF_SIZE 1024 +#define RECBUF_SIZE 512 +/* Parser's buf length */ +#define P_READBUF_SIZE 1024 +#define DEFAULT_CHANNELS 1 +#define DEFAULT_FORMAT AFMT_U8 +#define DEFAULT_SPEED 11025 +#define MAX_CHANNELS 128 +/* + * Every update of output in verbose mode while playing is separated by at + * least PLAY_UPDATE_INTERVAL milliseconds. + */ +#define PLAY_UPDATE_INTERVAL 200.0 +/* As above, but for recording */ +#define REC_UPDATE_INTERVAL 1000.0 +/* As above, but used for level meters while recording */ +#define LMETER_UPDATE_INTERVAL 20.0 +/* Should be smaller than the above. Used to ensure an update at end of file */ +#define UPDATE_EPSILON 1.0 + +/* Sanity check - no allocation by ossplay should pass this. */ +#define OSSPLAY_MAX_MALLOC 32*1024*1024 + +#if !defined(OSS_NO_INTTYPES_H) && !defined(OSS_NO_LONG_LONG) +#define __STDC_LIMIT_MACROS +#include <inttypes.h> + +typedef long double ldouble_t; +typedef int8_t int8; +typedef uint8_t uint8; +typedef int16_t int16; +typedef uint16_t uint16; +typedef int32_t int32; +typedef uint32_t uint32; +typedef char flag; +typedef intptr_t intptr; +#define S32_MAX INT32_MAX +#define S32_MIN INT32_MIN +#define U32_MAX UINT32_MAX +typedef uintmax_t big_t; +typedef intmax_t sbig_t; +#define _PRIbig_t "%ju" +#define BIG_SPECIAL UINTMAX_MAX + +#else +#ifdef OSS_NO_LONG_LONG +typedef long sbig_t; +typedef unsigned long big_t; +#define _PRIbig_t "%lu" +#define BIG_SPECIAL ULONG_MAX +#else +typedef long long sbig_t; +typedef unsigned long long big_t; +#define _PRIbig_t "%llu" +#define BIG_SPECIAL ULLONG_MAX +#endif + +typedef long double ldouble_t; +typedef signed char int8; +typedef unsigned char uint8; +typedef short int16; +typedef unsigned short uint16; +typedef int int32; +typedef unsigned int uint32; +typedef char flag; +typedef long intptr; +#define S32_MAX 2147483647 +#define S32_MIN (-S32_MAX - 1) +#define U32_MAX 4294967295U +#endif /* !OSS_NO_INTTYPES_H */ + +/* + * We overload the format definitions to include some "fake" formats. + * Therefor, the values should be negative to avoid collusions. + */ +enum { + AFMT_MS_ADPCM = -256, + AFMT_MS_IMA_ADPCM, + AFMT_MS_IMA_ADPCM_3BITS, + AFMT_MAC_IMA_ADPCM, + AFMT_S24_PACKED_BE, + AFMT_CR_ADPCM_2, + AFMT_CR_ADPCM_3, + AFMT_CR_ADPCM_4, + AFMT_FIBO_DELTA, + AFMT_EXP_DELTA, + AFMT_FLOAT32_BE, + AFMT_FLOAT32_LE, + AFMT_DOUBLE64_BE, + AFMT_DOUBLE64_LE +}; +#define AFMT_S24_PACKED_LE AFMT_S24_PACKED + +typedef struct { + int fd; + int format; + int channels; + int speed; + int flags; + int reclevel; +#ifndef OSS_DEVNODE_SIZE +#define OSS_DEVNODE_SIZE 32 +#endif + char dname[OSS_DEVNODE_SIZE]; +#ifndef OSS_LONGNAME_SIZE +#define OSS_LONGNAME_SIZE 64 +#endif + char current_songname[OSS_LONGNAME_SIZE]; + char * recsrc; + char * playtgt; +} +dspdev_t; + +typedef enum errors_t { + E_OK, + E_SETUP_ERROR, + E_FORMAT_UNSUPPORTED, + E_CHANNELS_UNSUPPORTED, + E_DECODE, + E_ENCODE, + E_USAGE, + /* + * Not an error, but since seek function can also return an error this needs + * to be different from the others + */ + SEEK_CONT_AFTER_DECODE +} +errors_t; + +#ifdef OGG_SUPPORT +#include <vorbis/vorbisfile.h> + +typedef struct { + void * vorbisfile_handle; + int (*ov_clear) (OggVorbis_File *); + vorbis_comment * (*ov_comment) (OggVorbis_File *, int); + vorbis_info * (*ov_info) (OggVorbis_File *, int); + int (*ov_open_callbacks) (void *, OggVorbis_File *, char *, long, ov_callbacks); + long (*ov_raw_tell) (OggVorbis_File *); + long (*ov_read) (OggVorbis_File *, char *, int, int, int, int, int *); + int (*ov_seekable) (OggVorbis_File *); + double (*ov_time_total) (OggVorbis_File *, int); + int (*ov_time_seek) (OggVorbis_File *, double); +} dlopen_funcs_t; + +typedef struct { + OggVorbis_File vf; + dlopen_funcs_t * f; + + int bitstream, setup; + dspdev_t * dsp; +} +ogg_data_t; +#else +typedef void * dlopen_funcs_t; +#endif + +/* + * ossplay supports more containers than the list below. This type is used by + * the IFF parser, ossrecord and some other functions though. + */ +typedef enum fctypes_t { + RAW_FILE, + WAVE_FILE, + AU_FILE, + AIFF_FILE, + CAF_FILE, + AIFC_FILE, + WAVE_FILE_BE, + _8SVX_FILE, + _16SV_FILE, + MAUD_FILE, + W64_FILE, + OGG_FILE +} +fctypes_t; + +#define IS_IFF_FILE(t) (((t) == WAVE_FILE) || ((t) == WAVE_FILE_BE) || \ + ((t) == AIFF_FILE) || ((t) == AIFC_FILE) || \ + ((t) == _8SVX_FILE) || ((t) == _16SV_FILE) || \ + ((t) == MAUD_FILE) \ + ) +/* + * Used in the format_t table below. + * Shows what actions can be done with the format - Play, Record or both. + */ +typedef enum direction_t { + CP = 0x1, + CR = 0x2, + CRP = 0x3 +} +direction_t; + +typedef struct fmt_struct { + const char * name; + const int fmt; + const direction_t dir; + const int may_conv; +} +format_t; + +typedef struct cnt_struct { + const char * name; + const fctypes_t type; + const int dformat; + const int dchannels; + const int dspeed; +} +container_t; + +typedef struct { + int coeff1, coeff2; +} +adpcm_coeff; + +typedef struct msadpcm_values { + uint16 nBlockAlign; + uint16 wSamplesPerBlock; + uint16 wNumCoeff; + uint16 bits; + adpcm_coeff coeff[32]; + int channels; +} +msadpcm_values_t; + +typedef ssize_t (decfunc_t) (unsigned char **, unsigned char *, ssize_t, + void *); +typedef errors_t (seekfunc_t) (int, big_t *, big_t, double, big_t, int, void *); +typedef ssize_t (readfunc_t) (int, void *, size_t, void *); + +typedef enum decoder_flag_t { + FREE_NONE = 0, + FREE_OBUF = 1, + FREE_META = 2 +} +decoder_flag_t; + +typedef struct decoders_queue { + struct decoders_queue * next; + decfunc_t * decoder; + unsigned char * outbuf; + void * metadata; + decoder_flag_t flag; +} +decoders_queue_t; + +big_t be_int (const unsigned char *, int); +const char * filepart (const char *); +float format2bits (int); +big_t le_int (const unsigned char *, int); +ldouble_t ossplay_ldexpl (ldouble_t, int); +int ossplay_parse_opts (int, char **, dspdev_t *); +int ossrecord_parse_opts (int, char **, dspdev_t *); +int play (dspdev_t *, int, big_t *, big_t, double, double, + readfunc_t *, decoders_queue_t *, seekfunc_t *); +int record (dspdev_t *, FILE *, const char *, double, double, + big_t *, decoders_queue_t * dec); +const char * sample_format_name (int); +errors_t setup_device (dspdev_t *, int, int, int); +errors_t silence (dspdev_t *, big_t, int); + +void select_playtgt (dspdev_t *); +void select_recsrc (dspdev_t *); +void open_device (dspdev_t *); +void close_device (dspdev_t *); + +#if !defined(OSS_BIG_ENDIAN) && !defined(OSS_LITTLE_ENDIAN) +#if AFMT_S16_NE == AFMT_S16_BE +#define OSS_BIG_ENDIAN +#else +#define OSS_LITTLE_ENDIAN +#endif /* AFMT_S16_NE == AFMT_S16_BE */ +#endif /* !OSS_BIG_ENDIAN && !OSS_LITTLE_ENDIAN */ + +#endif diff --git a/cmd/ossplay/ossplay.man b/cmd/ossplay/ossplay.man new file mode 100644 index 0000000..eb80da5 --- /dev/null +++ b/cmd/ossplay/ossplay.man @@ -0,0 +1,53 @@ +NAME +ossplay - Open Sound System playback program. + +SYNOPSIS +ossplay [-RWhlvq] [-S secs ] [ -c channels ] [ -d devname ] + [ -f fmtname | ? ] [ -g gain ] [ -o playtarget | ? ] + [ -s rate ] filename | - ... + +DESCRIPTION +ossplay plays raw PCM, Microsoft RIFF (.wav), Sun ULaw (.au), Mac AIFF (.aif) +and other types of audio files. By default the application will try to +determine the audio file's format and play audio based on the stored +inforation about sample format, number of channels and sampling rate. + +OPTIONS +-v Verbose output. Multiple invocations increase the level + of verbosity. +-q Quiet (no information printed). +-l Loop playback indefinately. +-d<devname> Select <devname> as the device (eg -d/dev/dsp2). +-s<rate> Select the playback rate for raw PCM audio (eg -s48000). +-c<channels Select the number of channels 1=mono 2=stereo, 4, 6, 8, etc. +-f<fmtname> Select the input format (eg -fU8 or -fS16_BE). +-f? Prints the list of supported format names. +-o<playtarget> Selects the play target name if the device supports multiple + play targets (such as front, rear, side). +-o? Prints the list of available play targets. +-g<gain> Amplify all played samples by percentage given as argument. + 100 (default) means normal signal level, 200 means double level. +-W Treat all input as raw PCM data. +-R Disable redirection to virtual mixer engines and sample + rate/format conversions. Should not be used unless absolutely + necessary. +-S<secs> Start playing at <secs> seconds from start of file. + The argument can contain a fractional part (e.g. -S1.2) +-h Display usage information. + +INTERRUPT +Sending a SIGQUIT (Ctrl-\e in most terminals) will make ossplay stop playing +the currently played file and skip to the next file. + +NOTES +The ossplay executable is the same as the ossrecord executable. +Behaviour is decided by the name used to invoke the program. + +SEE ALSO +ossrecord(1), ossmix(1), ossxmix(1) + +FILES +/usr/bin/ossplay + +AUTHOR +4Front Technologies diff --git a/cmd/ossplay/ossplay_console.c b/cmd/ossplay/ossplay_console.c new file mode 100644 index 0000000..f9b55bf --- /dev/null +++ b/cmd/ossplay/ossplay_console.c @@ -0,0 +1,397 @@ +/* + * Purpose: Console output interface functions and related. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "ossplay_console.h" +#include "ossplay_parser.h" +#include "ossplay_decode.h" +#include <sys/wait.h> +#ifdef OGG_SUPPORT +#include <dlfcn.h> +#endif + +extern int eflag, quiet, verbose; +extern flag from_stdin, loop; + +static FILE * normalout; +static int dots = -11, direction = 0; + +void +perror_msg (const char * s) +{ + if (quiet < 2) perror (s); +} + +void +clear_update (void) +{ + if (verbose) fprintf (normalout, "\r\n"); + dots = -11; + direction = 0; +} + +void +print_update (int v, double secs, const char * total) +{ + char vu[12] = "-------++!!", * rtime; + + if (v > 0) vu[v] = '\0'; + else /* v == 0 */ + { + vu[0] = '0'; + vu[1] = '\0'; + } + + rtime = totime (secs); + fprintf (stdout, "\rTime: %s of %s VU %-11s", rtime, total, vu); + fflush (stdout); + ossplay_free (rtime); +} + +void +print_record_update (int v, double secs, const char * fname, int update) +{ + char vu[12] = "-------++!!"; + + int x1, x2, i; + extern int level_meters; + + fprintf (stderr, "\r%s [", fname); + x1 = dots; + x2 = dots + 10; + + if (update) + { + if (direction == 0) + { + dots++; + if (dots >= 10) direction = 1; + } + else + { + dots--; + if (dots <= -10) direction = 0; + } + } + + if (dots < 0) + { + x1 = 0; + x2 = dots + 10; + if (x2 < 0) x2 = 0; + } + if (dots >= 0) + { + x2 = 10; + x1 = dots; + } + + for (i = 0; i < x1; i++) + fprintf (stderr, " "); + for (i = x1; i < x2; i++) + fprintf (stderr, "."); + for (i = 0; i < 10 - x2; i++) + fprintf (stderr, " "); + + if (secs < 60.0) + fprintf (stderr, "] %1.2f secs", secs); + else + { + int hours, mins; + + mins = (int) (secs / 60.0); + secs -= (mins * 60); + + hours = mins / 60; + mins = mins % 60; + fprintf (stderr, "] %02d:%02d:%02d", hours, mins, (int)secs); + } + + if (!level_meters) + { + return; + } + else if (v > 0) + { + vu[v] = '\0'; + fprintf (stderr, " VU %-11s", vu); + } + else if (v == 0) + { + fprintf (stderr, " VU %-11s", "0"); + } + + fflush (stderr); +} + +void print_msg (prtype_t type, const char * fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + switch (type) + { + case NOTIFYM: + if (quiet) break; + case WARNM: + if (quiet == 2) break; + case ERRORM: + vfprintf (stderr, fmt, ap); + break; + case HELPM: + vfprintf (stdout, fmt, ap); + break; + case VERBOSEM: + if (verbose) vfprintf (normalout, fmt, ap); + break; + default: /* case NORMALM, STARTM, CONTM, ENDM: */ + if (!quiet) vfprintf (normalout, fmt, ap); + break; + } + va_end (ap); +} + +void * +ossplay_malloc (size_t sz) +{ + void *ptr; + + if ((sz == 0) || (sz > OSSPLAY_MAX_MALLOC)) { + fprintf (stderr, "Unreasonable allocation size " _PRIbig_t ", aborting", + (big_t)sz); + exit (E_SETUP_ERROR); + } + ptr = malloc (sz); + if (ptr == NULL) { + /* Not all libcs support using %z for size_t */ + fprintf (stderr, "Can't allocate " _PRIbig_t " bytes\n", (big_t)sz); + exit (-1); + } + return ptr; +} + +void +ossplay_free (void * ptr) +{ + if (ptr == NULL) return; + free (ptr); +} + +off_t +ossplay_lseek_stdin (int fd, off_t off, int w) +{ + off_t i; + ssize_t bytes_read; + char buf[BUFSIZ]; + + if (w == SEEK_END) return -1; + if (off < 0) return -1; + if (off == 0) return 0; + i = off; + while (i > 0) { + bytes_read = read(fd, buf, (i > BUFSIZ)?BUFSIZ:i); + if (bytes_read == -1) return -1; + else if (bytes_read == 0) return off - i; + i -= bytes_read; + } + return off; +} + +char * +ossplay_strdup (const char * s) +{ + char * p; + + if (s == NULL) return NULL; + p = strdup (s); + if (p == NULL) + { + fprintf (stderr, "Can't allocate memory for strdup\n"); + exit (-1); + } + return p; +} + +#ifdef OGG_SUPPORT +int +ossplay_dlclose (void * handle) +{ + return dlclose (handle); +} + +void * +ossplay_dlopen (const char * filename) +{ + return dlopen (filename, RTLD_LAZY | RTLD_LOCAL); +} + +int +ossplay_vdlsym (void * handle, ...) +{ + va_list ap; + const char * symbol; + void ** v = NULL; + + va_start (ap, handle); + + while (1) + { + v = va_arg (ap, void **); + if (v == (void **)NULL) break; + symbol = va_arg (ap, const char *); + *v = dlsym (handle, symbol); + if (*v == NULL) + { + const char * msg = dlerror(); + + print_msg (ERRORM, "Can't find symbol %s! (Error: %s)\n", + symbol, msg?msg:""); + return -1; + } + } + + return 0; +} + +const char * +ossplay_dlerror (void) +{ + return dlerror(); +} +#endif + +static int +ossplay_main (int argc, char ** argv) +{ + int i, loop_flag; + dspdev_t dsp = { -1 }; + errors_t ret = E_OK; + dlopen_funcs_t * vft = NULL; + + normalout = stdout; + + i = ossplay_parse_opts (argc, argv, &dsp); + + argc -= i - 1; + argv += i - 1; + + dsp.flags = O_WRONLY; + open_device (&dsp); + if (dsp.playtgt != NULL) select_playtgt (&dsp); + + do { + loop_flag = 0; + for (i = 1; i < argc; i++) { + if (argv[i][0] == '\0') continue; + strncpy (dsp.current_songname, filepart (argv[i]), + sizeof (dsp.current_songname)); + dsp.current_songname[sizeof (dsp.current_songname) - 1] = '\0'; + from_stdin = !strcmp (argv[i], "-"); + ret = play_file (&dsp, argv[i], &vft); + if (ret || from_stdin) argv[i] = ""; + if ((ret == 0) && (!from_stdin)) loop_flag = 1; + eflag = 0; + } + } while (loop && loop_flag); + +#ifdef OGG_SUPPORT + if (vft != NULL) + { + ossplay_dlclose (vft->vorbisfile_handle); + ossplay_free (vft); + } +#endif + + close_device (&dsp); + return ret; +} + +static int +ossrecord_main (int argc, char ** argv) +{ + int i, oind; + char current_filename[512]; + dspdev_t dsp = { -1 }; + errors_t err; + + extern int force_fmt, force_channels, force_speed, nfiles; + extern double datalimit; + extern fctypes_t type; + extern char script[512]; + + normalout = stderr; + /* Since recording can be redirected to stdout, we always output to stderr */ + + oind = ossrecord_parse_opts (argc, argv, &dsp); + + dsp.flags = O_RDONLY; + open_device (&dsp); + if (dsp.recsrc != NULL) select_recsrc (&dsp); + + strncpy (dsp.current_songname, filepart (argv[oind]), + sizeof (dsp.current_songname)); + dsp.current_songname[sizeof (dsp.current_songname) - 1] = 0; + + for (i = 0; i < nfiles; i++) + { + if (nfiles > 1) + /* XXX */ + snprintf (current_filename, sizeof (current_filename), + argv[oind], i + 1); + else + snprintf (current_filename, sizeof (current_filename), + "%s", argv[oind]); + err = encode_sound (&dsp, type, current_filename, force_fmt, + force_channels, force_speed, datalimit); + if (*script) + { + if (fork () == 0) + { + if (execlp (script, script, current_filename, (char *)NULL) == -1) + { + perror (script); + exit (-1); + } + } + + print_msg (NORMALM, + "Waiting for the '%s' script(s) to finish - please stand" + " by\n", script); + while (wait (NULL) != -1); + } + + if (err) return err; + } + + close_device (&dsp); + return 0; +} + +char * +totime (double secs) +{ + char time[20]; + unsigned long min = secs / 60; + + snprintf (time, 20, "%.2lu:%05.2f", min, secs - min * 60); + + return ossplay_strdup (time); +} + +int +main (int argc, char **argv) +{ + if (strstr (filepart (argv[0]), "ossplay")) exit(ossplay_main (argc, argv)); + exit(ossrecord_main (argc, argv)); +} diff --git a/cmd/ossplay/ossplay_console.h b/cmd/ossplay/ossplay_console.h new file mode 100644 index 0000000..c0f90fd --- /dev/null +++ b/cmd/ossplay/ossplay_console.h @@ -0,0 +1,37 @@ +#ifndef _OSSPLAY_CONSOLE_H +#define _OSSPLAY_CONSOLE_H + +#include <stddef.h> +#include <sys/types.h> + +typedef enum { + ERRORM, + HELPM, + NORMALM, + NOTIFYM, + WARNM, + STARTM, + CONTM, + ENDM, + VERBOSEM +} +prtype_t; + +void clear_update (void); +void ossplay_free (void *); +void * ossplay_malloc (size_t); +off_t ossplay_lseek_stdin (int, off_t, int); +char * ossplay_strdup (const char *); +#ifdef OGG_SUPPORT +int ossplay_dlclose (void *); +void * ossplay_dlopen (const char *); +const char * ossplay_dlerror (void); +int ossplay_vdlsym (void *, ...); +#endif +void perror_msg (const char * s); +void print_msg (prtype_t, const char *, ...); +void print_record_update (int, double, const char *, int); +void print_update (int, double, const char *); +char * totime (double); + +#endif diff --git a/cmd/ossplay/ossplay_decode.c b/cmd/ossplay/ossplay_decode.c new file mode 100644 index 0000000..cf769af --- /dev/null +++ b/cmd/ossplay/ossplay_decode.c @@ -0,0 +1,1783 @@ +/* + * Purpose: Sample format decode routines for ossplay + * + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "ossplay_decode.h" +#include "ossplay_wparser.h" + +typedef struct cradpcm_values { + const unsigned char * const * table; + + signed char limit; + signed char shift; + signed char step; + unsigned char ratio; + unsigned char pred; +} +cradpcm_values_t; + +typedef struct fib_values { + unsigned char pred; + const signed char * table; +} +fib_values_t; + +typedef struct ima_values { + int channels; + int16 pred[MAX_CHANNELS]; + int8 index[MAX_CHANNELS]; +} +ima_values_t; + +#ifdef SRC_SUPPORT +/* + * For actual use, we can rely on vmix. + * This is useful for testing though. + */ +#include "../../kernel/framework/audio/oss_grc3.c" + +typedef struct grc_values { + int bits; + int channels; + int speed; + int ospeed; + int obsize; + grc3state_t grc[]; +} grc_data_t; +static decfunc_t decode_src; +static decfunc_t decode_mono_to_stereo; +static grc_data_t * setup_grc3 (int, int, int, int, int); +#endif + +extern int amplification, eflag, force_speed, force_fmt, force_channels; +extern flag int_conv, overwrite, verbose; +extern char audio_devname[32]; +extern off_t (*ossplay_lseek) (int, off_t, int); +extern double seek_time; +extern const format_t format_a[]; + +static void decode_ima (unsigned char *, unsigned char *, ssize_t, int16 *, + int8 *, int, int); +static void decode_ima_3bits (unsigned char *, unsigned char *, ssize_t, + int16 *, int8 *, int, int); +static decfunc_t decode_24; +static decfunc_t decode_8_to_s16; +static decfunc_t decode_amplify; +static decfunc_t decode_cr; +static decfunc_t decode_double64_be; +static decfunc_t decode_double64_le; +static decfunc_t decode_endian; +static decfunc_t decode_fib; +static decfunc_t decode_float32_be; +static decfunc_t decode_float32_le; +static decfunc_t decode_mac_ima; +static decfunc_t decode_ms_ima; +static decfunc_t decode_ms_adpcm; +static decfunc_t decode_nul; +static decfunc_t decode_raw_ima; + +static int32 float32_to_s32 (int, int, int); +static int32 double64_to_s32 (int, int32, int32, int); + +static cradpcm_values_t * setup_cr (int, int); +static fib_values_t * setup_fib (int, int); +static decoders_queue_t * setup_normalize (int *, int *, decoders_queue_t *); + +static seekfunc_t seek_normal; +static seekfunc_t seek_compressed; + +static readfunc_t read_normal; + +#ifdef OGG_SUPPORT +static readfunc_t read_ogg; +static seekfunc_t seek_ogg; +#endif + +errors_t +decode_sound (dspdev_t * dsp, int fd, big_t filesize, int format, + int channels, int speed, void * metadata) +{ + decoders_queue_t * dec, * decoders; + readfunc_t * readf; + seekfunc_t * seekf; + int bsize, obsize; + double constant, total_time; + errors_t ret = E_DECODE; + + if (force_speed != 0) speed = force_speed; + if (force_channels != 0) channels = force_channels; + if (force_fmt != 0) format = force_fmt; + if ((channels > MAX_CHANNELS) || (channels == 0)) + { + print_msg (ERRORM, "An unreasonable number of channels (%d), aborting\n", + channels); + return E_DECODE; + } + + constant = format2bits (format) * speed * channels / 8.0; + if (constant == 0) return E_DECODE; /* Shouldn't ever happen */ +#if 0 + /* + * There is no reason to use SNDCTL_DSP_GETBLKSIZE in applications like this. + * Using some fixed local buffer size will work equally well. + */ + ioctl (dsp->fd, SNDCTL_DSP_GETBLKSIZE, &bsize); +#else + bsize = PLAYBUF_SIZE; +#endif + + if (filesize < 2) return E_OK; + decoders = dec = (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + dec->next = NULL; + dec->flag = 0; + seekf = seek_normal; + readf = read_normal; + if (filesize != BIG_SPECIAL) total_time = filesize / constant; + else total_time = 0; + + switch (format) + { + case AFMT_MS_ADPCM: + if (metadata == NULL) + { + msadpcm_values_t * val = + (msadpcm_values_t *)ossplay_malloc (sizeof (msadpcm_values_t)); + + val->channels = channels; + if (speed < 22000) val->nBlockAlign = 256; + else if (speed < 44000) val->nBlockAlign = 512; + else val->nBlockAlign = 1024; + val->wSamplesPerBlock = 8 * (val->nBlockAlign - 7 * channels) / (4 * channels) + 2; + val->wNumCoeff = 7; + val->coeff[0].coeff1 = 256; val->coeff[0].coeff2 = 0; + val->coeff[1].coeff1 = 512; val->coeff[1].coeff2 = -256; + val->coeff[2].coeff1 = 0; val->coeff[2].coeff2 = 0; + val->coeff[3].coeff1 = 192; val->coeff[3].coeff2 = 64; + val->coeff[4].coeff1 = 240; val->coeff[4].coeff2 = 0; + val->coeff[5].coeff1 = 460; val->coeff[5].coeff2 = -208; + val->coeff[6].coeff1 = 392; val->coeff[6].coeff2 = -232; + + /* total_time = val->wSamplesPerBlock * filesize / val->nBlockAlign / constant; */ + bsize = val->nBlockAlign; + total_time = 0; + dec->metadata = (void *)val; + dec->flag = FREE_META; + } + else + { + msadpcm_values_t * val = (msadpcm_values_t *)metadata; + + /* Let's try anyway */ + if (val->nBlockAlign == 0) + { + val->nBlockAlign = filesize - filesize % 4; + } + else + { + total_time = val->wSamplesPerBlock * filesize * channels / + val->nBlockAlign / constant / 2; /* 4/8 == 1/2 */ + } + bsize = val->nBlockAlign; + dec->metadata = metadata; + } + + dec->decoder = decode_ms_adpcm; + obsize = 4 * bsize; + dec->outbuf = (unsigned char *)ossplay_malloc (obsize); + dec->flag |= FREE_OBUF; + seekf = seek_compressed; + + format = AFMT_S16_NE; + break; + case AFMT_MS_IMA_ADPCM: + case AFMT_MS_IMA_ADPCM_3BITS: + dec->metadata = metadata; + if (dec->metadata == NULL) + { + msadpcm_values_t * val = + (msadpcm_values_t *)ossplay_malloc (sizeof (msadpcm_values_t)); + + val->channels = channels; + val->bits = (format == AFMT_MS_IMA_ADPCM)?4:3; + val->nBlockAlign = 256 * channels * (speed > 11000)?speed/11000:1; + } + else + { + msadpcm_values_t * val = (msadpcm_values_t *)metadata; + + /* Let's try anyway - some cameras make defective WAVs */ + if (val->nBlockAlign == 0) + { + val->nBlockAlign = filesize - filesize % 4; + } + else + { + total_time = val->wSamplesPerBlock * filesize * val->bits * channels / + val->nBlockAlign / constant / 8.0; + } + bsize = val->nBlockAlign; + } + + dec->decoder = decode_ms_ima; + if (format == AFMT_MS_IMA_ADPCM_3BITS) + obsize = (bsize * 16)/3 + 2; + /* + * 8 sample words per 3 bytes, each expanding to 2 bytes, plus 2 bytes + * to deal with fractions. Slight overestimation because bsize + * includes the headers too. + */ + else + obsize = 4 * bsize; + dec->outbuf = (unsigned char *)ossplay_malloc (obsize); + dec->flag = FREE_OBUF; + seekf = seek_compressed; + + format = AFMT_S16_NE; + break; + case AFMT_MAC_IMA_ADPCM: + dec->metadata = (void *)(intptr)channels; + dec->decoder = decode_mac_ima; + bsize -= bsize % (MAC_IMA_BLKLEN * channels); + obsize = 4 * bsize; + dec->outbuf = (unsigned char *)ossplay_malloc (obsize); + dec->flag = FREE_OBUF; + seekf = seek_compressed; + + format = AFMT_S16_NE; + break; + case AFMT_IMA_ADPCM: + dec->metadata = (void *)ossplay_malloc (sizeof (ima_values_t)); + memset (dec->metadata, 0, sizeof (ima_values_t)); + ((ima_values_t *)(dec->metadata))->channels = channels; + + dec->decoder = decode_raw_ima; + obsize = 4 * bsize; + dec->outbuf = (unsigned char *)ossplay_malloc (obsize); + dec->flag = FREE_OBUF | FREE_META; + seekf = seek_compressed; + + format = AFMT_S16_NE; + break; + case AFMT_CR_ADPCM_2: + case AFMT_CR_ADPCM_3: + case AFMT_CR_ADPCM_4: + dec->metadata = (void *)setup_cr (fd, format);; + if (dec->metadata == NULL) goto exit; + dec->decoder = decode_cr; + obsize = ((cradpcm_values_t *)dec->metadata)->ratio * bsize; + dec->outbuf = (unsigned char *)ossplay_malloc (obsize); + dec->flag = FREE_OBUF | FREE_META; + seekf = seek_compressed; + + if (filesize != BIG_SPECIAL) filesize--; + format = AFMT_U8; + break; + case AFMT_FIBO_DELTA: + case AFMT_EXP_DELTA: + dec->metadata = (void *)setup_fib (fd, format);; + if (dec->metadata == NULL) goto exit; + dec->decoder = decode_fib; + obsize = 2 * bsize; + dec->outbuf = (unsigned char *)ossplay_malloc (obsize); + dec->flag = FREE_OBUF | FREE_META; + seekf = seek_compressed; + + if (filesize != BIG_SPECIAL) filesize--; + format = AFMT_U8; + break; + case AFMT_S24_PACKED: + case AFMT_S24_PACKED_BE: + dec->metadata = (void *)(intptr)format; + dec->decoder = decode_24; + bsize -= bsize % 3; + obsize = bsize/3*4; + dec->outbuf = (unsigned char *)ossplay_malloc (obsize); + dec->flag = FREE_OBUF; + + format = AFMT_S32_NE; + break; + case AFMT_FLOAT32_BE: + case AFMT_FLOAT32_LE: + if (format == AFMT_FLOAT32_BE) dec->decoder = decode_float32_be; + else dec->decoder = decode_float32_le; + bsize -= bsize % 4; + obsize = bsize; + dec->outbuf = NULL; + + format = AFMT_S32_NE; + break; + case AFMT_DOUBLE64_BE: + case AFMT_DOUBLE64_LE: + if (format == AFMT_DOUBLE64_BE) dec->decoder = decode_double64_be; + else dec->decoder = decode_double64_le; + bsize -= bsize % 8; + obsize = bsize/2; + dec->outbuf = NULL; + + format = AFMT_S32_NE; + break; +#ifdef OGG_SUPPORT + case AFMT_VORBIS: + readf = read_ogg; + dec->decoder = decode_nul; + dec->metadata = metadata; + obsize = bsize; + if (metadata == NULL) goto exit; + else + { + ogg_data_t * val = (ogg_data_t *)metadata; + if (val->f->ov_seekable (&val->vf)) + { + seekf = seek_ogg; + total_time = val->f->ov_time_total (&val->vf, -1); + } + else + { + seekf = NULL; + total_time = 0; + } + } + + format = AFMT_S16_NE; + break; +#endif + default: + dec->decoder = decode_nul; + + obsize = bsize; + break; + } + + if (int_conv) + decoders = setup_normalize (&format, &obsize, decoders); + + if ((amplification > 0) && (amplification != 100)) + { + decoders->next = + (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + decoders = decoders->next; + decoders->metadata = (void *)(intptr)format; + decoders->decoder = decode_amplify; + decoders->next = NULL; + decoders->outbuf = NULL; + decoders->flag = 0; + } + + ret = setup_device (dsp, format, channels, speed); + if (ret == E_FORMAT_UNSUPPORTED) + { + int i, tmp; + + for (i = 0; format_a[i].name != NULL; i++) + if (format_a[i].fmt == format) + { + tmp = format_a[i].may_conv; + if ((tmp == 0) || (tmp == format)) continue; + print_msg (WARNM, "Converting to format %s\n", + sample_format_name (tmp)); + ret = setup_device (dsp, tmp, channels, speed); + if (ret == E_FORMAT_UNSUPPORTED) goto exit; + decoders = setup_normalize (&format, &obsize, decoders); + goto dcont; + } + goto exit; + } + +dcont: +#ifdef SRC_SUPPORT + if ((ret == E_CHANNELS_UNSUPPORTED) && (channels == 1)) { + channels = 2; + if ((ret = setup_device (dsp, format, channels, speed))) goto exit; + decoders->next = + (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + decoders = decoders->next; + decoders->metadata = (void *)(intptr)format; + decoders->decoder = decode_mono_to_stereo; + decoders->next = NULL; + obsize *= 2; + decoders->outbuf = (unsigned char *)ossplay_malloc (obsize); + decoders->flag = FREE_OBUF; + } +#endif + + if (ret) goto exit; +#ifdef SRC_SUPPORT + if (dsp->speed != speed) { + if ((format == AFMT_MU_LAW) || (format == AFMT_A_LAW)) + decoders = setup_normalize (&format, &obsize, decoders); + decoders->next = + (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + decoders = decoders->next; + decoders->decoder = decode_src; + decoders->next = NULL; + obsize *= (dsp->speed / speed + 1) * channels * sizeof (int); + decoders->metadata = + (void *)setup_grc3 (format, channels, dsp->speed, speed, obsize); + decoders->outbuf = (unsigned char *)ossplay_malloc (obsize); + decoders->flag = FREE_OBUF | FREE_META; + speed = dsp->speed; + } +#endif + + ret = play (dsp, fd, &filesize, bsize, total_time, constant, readf, + dec, seekf); + +exit: + decoders = dec; + while (decoders != NULL) + { + if (decoders->flag & FREE_META) ossplay_free (decoders->metadata); + if (decoders->flag & FREE_OBUF) ossplay_free (decoders->outbuf); + decoders = decoders->next; + ossplay_free (dec); + dec = decoders; + } + + return ret; +} + +errors_t +encode_sound (dspdev_t * dsp, fctypes_t type, const char * fname, int format, + int channels, int speed, double data_time) +{ + big_t data_size = 0; + double constant; + int fd = -1; + decoders_queue_t * dec, * decoders = NULL; + errors_t ret; + FILE * wave_fp; + + if ((ret = setup_device (dsp, format, channels, speed))) return ret; + constant = format2bits (format) * speed * channels / 8.0; + + if (data_time != 0) data_size = data_time * constant; + + if (strcmp (fname, "-") == 0) { + wave_fp = fdopen (1, "wb"); + } else { + fd = open (fname, O_WRONLY | O_CREAT | (overwrite?O_TRUNC:O_EXCL), 0644); + if (fd == -1) { + perror (fname); + return E_ENCODE; + } + wave_fp = fdopen (fd, "wb"); + } + + if (wave_fp == NULL) + { + perror (fname); + if (fd != -1) close (fd); + return E_ENCODE; + } + + if (channels == 1) + print_msg (VERBOSEM, "Recording wav: Speed %dHz %d bits Mono\n", + speed, (int)format2bits (format)); + if (channels == 2) + print_msg (VERBOSEM, "Recording wav: Speed %dHz %d bits Stereo\n", + speed, (int)format2bits (format)); + if (channels > 2) + print_msg (VERBOSEM, "Recording wav: Speed %dHz %d bits %d channels\n", + speed, (int)format2bits (format), channels); + + /* + * Write the initial header + */ + if (write_head (wave_fp, type, data_size, format, channels, speed)) + return E_ENCODE; + + decoders = dec = + (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + dec->next = NULL; + dec->flag = 0; + dec->decoder = decode_nul; + + if ((amplification > 0) && (amplification != 100)) + { + decoders->next = + (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + decoders = decoders->next; + decoders->metadata = (void *)(intptr)format; + decoders->decoder = decode_amplify; + decoders->next = NULL; + decoders->outbuf = NULL; + decoders->flag = 0; + } + + ret = record (dsp, wave_fp, fname, constant, data_time, &data_size, dec); + + finalize_head (wave_fp, type, data_size, format, channels, speed); + fflush (wave_fp); + /* + * EINVAL and EROFS are returned for "special files which don't support + * syncronization". The user should already know he's writing to a special + * file (e.g. "ossrecord /dev/null"), so no need to warn. + */ + if ((fsync (fileno (wave_fp)) == -1) && (errno != EINVAL) && (errno != EROFS)) + { + perror (fname); + ret = E_ENCODE; + } + if (fclose (wave_fp) != 0) + { + perror (fname); + ret = E_ENCODE; + } + + decoders = dec; + while (decoders != NULL) + { + if (decoders->flag & FREE_META) ossplay_free (decoders->metadata); + if (decoders->flag & FREE_OBUF) ossplay_free (decoders->outbuf); + decoders = decoders->next; + ossplay_free (dec); + dec = decoders; + } + return ret; +} + +static ssize_t +decode_24 (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + big_t outlen = 0; + ssize_t i; + int v1; + uint32 * u32; + int32 sample_s32, * outbuf = (int32 *) * obuf; + int format = (int)(intptr)metadata; + + if (format == AFMT_S24_PACKED) v1 = 8; + else v1 = 24; + + for (i = 0; i < l-2; i += 3) + { + u32 = (uint32 *) &sample_s32; /* Alias */ + + *u32 = (buf[i] << v1) | (buf[i + 1] << 16) | (buf[i + 2] << (32-v1)); + outbuf[outlen++] = sample_s32; + } + + return 4 * outlen; +} + +static fib_values_t * +setup_fib (int fd, int format) +{ + static const signed char CodeToDelta[16] = { + -34, -21, -13, -8, -5, -3, -2, -1, 0, 1, 2, 3, 5, 8, 13, 21 + }; + static const signed char CodeToExpDelta[16] = { + -128, -64, -32, -16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16, 32, 64 + }; + unsigned char buf; + fib_values_t * val; + + if (read (fd, &buf, 1) <= 0) return NULL; + val = (fib_values_t *)ossplay_malloc (sizeof (fib_values_t)); + if (format == AFMT_EXP_DELTA) val->table = CodeToExpDelta; + else val->table = CodeToDelta; + + val->pred = buf; + + return val; +} + +static cradpcm_values_t * +setup_cr (int fd, int format) +{ + static const unsigned char T2[4][3] = { + { 128, 6, 1 }, + { 32, 4, 1 }, + { 8, 2, 1 }, + { 2, 0, 1 } + }; + + static const unsigned char T3[3][3] = { + { 128, 5, 3 }, + { 16, 2, 3 }, + { 2, 0, 1 } + }; + + static const unsigned char T4[2][3] = { + { 128, 4, 7 }, + { 8, 0, 7 } + }; + + static const unsigned char * t_row[4]; + + unsigned char buf; + cradpcm_values_t * val; + int i; + + if (read (fd, &buf, 1) <= 0) return NULL; + val = (cradpcm_values_t *)ossplay_malloc (sizeof (cradpcm_values_t)); + val->table = t_row; + + if (format == AFMT_CR_ADPCM_2) + { + val->limit = 1; + val->step = val->shift = 2; + val->ratio = 4; + for (i=0; i < 4; i++) t_row[i] = T2[i]; + } + else if (format == AFMT_CR_ADPCM_3) + { + val->limit = 3; + val->ratio = 3; + val->step = val->shift = 0; + for (i=0; i < 3; i++) t_row[i] = T3[i]; + } + else /* if (format == AFMT_CR_ADPCM_4) */ + { + val->limit = 5; + val->ratio = 2; + val->step = val->shift = 0; + for (i=0; i < 2; i++) t_row[i] = T4[i]; + } + + val->pred = buf; + + return val; +} + +static ssize_t +decode_8_to_s16 (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + int format = (int)(intptr)metadata; + ssize_t i; + int16 * outbuf = (int16 *) * obuf; + static const int16 mu_law_table[256] = { + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364,-9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 + }; + + static const int16 a_law_table[256] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, + -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, + -11008,-10496,-12032,-11520,-8960, -8448, -9984, -9472, + -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 + }; + + switch (format) + { + case AFMT_U8: + for (i = 0; i < l; i++) outbuf[i] = (buf[i] - 128) << 8; + break; + case AFMT_S8: + for (i = 0; i < l; i++) outbuf[i] = buf[i] << 8; + break; + case AFMT_MU_LAW: + for (i = 0; i < l; i++) outbuf[i] = mu_law_table[buf[i]]; + break; + case AFMT_A_LAW: + for (i = 0; i < l; i++) outbuf[i] = a_law_table[buf[i]]; + break; + } + + return 2*l; +} + +static ssize_t +decode_cr (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + cradpcm_values_t * val = (cradpcm_values_t *) metadata; + int j, pred = val->pred, step = val->step; + unsigned char value; + signed char sign; + ssize_t i; + + for (i=0; i < l; i++) + for (j=0; j < val->ratio; j++) + { + sign = (buf[i] & val->table[j][0])?-1:1; + value = (buf[i] >> val->table[j][1]) & val->table[j][2]; + pred += sign*(value << step); + if (pred > 255) pred = 255; + else if (pred < 0) pred = 0; + (*obuf)[val->ratio*i+j] = pred; + if ((value >= val->limit) && (step < 3+val->shift)) step++; + if ((value == 0) && (step > val->shift)) step--; + } + + val->pred = pred; + val->step = step; + return val->ratio*l; +} + +static ssize_t +decode_fib (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + fib_values_t * val = (fib_values_t *)metadata; + int x = val->pred; + unsigned char d; + ssize_t i; + + for (i = 0; i < 2*l; i++) + { + d = buf[i/2]; + if (i & 1) d &= 0xF; + else d >>= 4; + x += val->table[d]; + if (x > 255) x = 255; + if (x < 0) x = 0; + (*obuf)[i] = x; + } + + val->pred = x; + return 2*l; +} + +static ssize_t +decode_ms_adpcm (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + msadpcm_values_t * val = (msadpcm_values_t *)metadata; + + int error_delta, i_delta, i = 0, nib = 0, channels = val->channels; + int AdaptionTable[16] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + ssize_t outp = 0, x = 0; + int16 * wbuf = (int16 *)*obuf; + int32 delta[MAX_CHANNELS], samp1[MAX_CHANNELS], samp2[MAX_CHANNELS], + predictor[MAX_CHANNELS], new_samp, pred, n = 0; + +/* + * Playback procedure + */ +#define OUT_SAMPLE(s) \ + do { \ + if (s > 32767) s = 32767; else if (s < -32768) s = -32768; \ + wbuf[outp++] = s; \ + n += 2; \ + } while(0) + +#define GETNIBBLE \ + ((nib == 0) ? \ + (buf[x + nib++] >> 4) & 0x0f : \ + buf[x++ + --nib] & 0x0f \ + ) + + for (i = 0; i < channels; i++) + { + predictor[i] = buf[x]; + if (predictor[i] > val->wNumCoeff) + /* Shouldn't ever happen */ + predictor[i] = val->wNumCoeff; + x++; + } + + for (i = 0; i < channels; i++) + { + delta[i] = (int16) le_int (&buf[x], 2); + x += 2; + } + + for (i = 0; i < channels; i++) + { + samp1[i] = (int16) le_int (&buf[x], 2); + x += 2; + OUT_SAMPLE (samp1[i]); + } + + for (i = 0; i < channels; i++) + { + samp2[i] = (int16) le_int (&buf[x], 2); + x += 2; + OUT_SAMPLE (samp2[i]); + } + + while (n < (val->wSamplesPerBlock * 2 * channels)) + for (i = 0; i < channels; i++) + { + pred = ((samp1[i] * val->coeff[predictor[i]].coeff1) + + (samp2[i] * val->coeff[predictor[i]].coeff2)) / 256; + + if (x > l) return 2*outp; + i_delta = error_delta = GETNIBBLE; + + if (i_delta & 0x08) + i_delta -= 0x10; /* Convert to signed */ + + new_samp = pred + (delta[i] * i_delta); + OUT_SAMPLE (new_samp); + + delta[i] = delta[i] * AdaptionTable[error_delta] / 256; + if (delta[i] < 16) delta[i] = 16; + + samp2[i] = samp1[i]; + samp1[i] = new_samp; + } + + return 2*outp; +} + +static ssize_t +decode_nul (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + *obuf = buf; + return l; +} + +static ssize_t +decode_endian (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + int format = (int)(intptr)metadata; + ssize_t i; + + switch (format) + { + case AFMT_S16_OE: + { + int16 * s = (int16 *)buf; + + for (i = 0; i < l / 2; i++) + s[i] = ((s[i] >> 8) & 0x00FF) | + ((s[i] << 8) & 0xFF00); + } + break; + case AFMT_S32_OE: + case AFMT_S24_OE: + { + int32 * s = (int32 *)buf; + + for (i = 0; i < l / 4; i++) + s[i] = ((s[i] >> 24) & 0x000000FF) | + ((s[i] << 8) & 0x00FF0000) | ((s[i] >> 8) & 0x0000FF00) | + ((s[i] << 24) & 0xFF000000); + } + break; +#ifdef OSS_LITTLE_ENDIAN + case AFMT_U16_BE: /* U16_BE -> S16_LE */ +#else + case AFMT_U16_LE: /* U16_LE -> S16_BE */ +#endif + { + int16 * s = (int16 *)buf; + + for (i = 0; i < l / 2; i++) + s[i] = (((s[i] >> 8) & 0x00FF) | ((s[i] << 8) & 0xFF00)) - + USHRT_MAX/2; + } + break; + /* Not an endian conversion, but included for completeness sake */ +#ifdef OSS_LITTLE_ENDIAN + case AFMT_U16_LE: /* U16_LE -> S16_LE */ +#else + case AFMT_U16_BE: /* U16_BE -> S16_BE */ +#endif + { + int16 * s = (int16 *)buf; + + for (i = 0; i < l / 2; i++) + s[i] -= USHRT_MAX/2; + } + break; + } + *obuf = buf; + return l; +} + +static ssize_t +decode_amplify (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + int format = (int)(intptr)metadata; + ssize_t i, len; + + switch (format) + { + case AFMT_S16_NE: + { + int16 *s = (int16 *)buf; + int32 tmp; + + len = l / 2; + for (i = 0; i < len ; i++) + { + tmp = (int32)s[i] * amplification / 100; + if (tmp > SHRT_MAX) s[i] = SHRT_MAX; + else if (tmp < SHRT_MIN) s[i] = SHRT_MIN; + else s[i] = tmp; + } + } + break; + case AFMT_S32_NE: + case AFMT_S24_NE: + { + int32 *s = (int32 *)buf; + sbig_t tmp; + + len = l / 4; + for (i = 0; i < len; i++) + { + tmp = (sbig_t)s[i] * amplification / 100; + if (tmp > S32_MAX) s[i] = S32_MAX; + else if (tmp < S32_MIN) s[i] = S32_MIN; + else s[i] = tmp; + } + } + break; + } + + *obuf = buf; + return l; +} + +static void +decode_ima (unsigned char * obuf, unsigned char * buf, ssize_t l, int16 * pred0, + int8 * index0, int channels, int ch) +{ + int j; + int32 pred = *pred0; + int16 step; + int16 * outbuf = (int16 *) obuf; + int8 index = *index0, value; + signed char sign; + ssize_t i; + static const int step_tab[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 + }; + + static const int8 iTab4[16] = + {-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8}; + + for (i=0; i < l; i++) + for (j=0; j < 2; j++) + { + value = (buf[i] >> 4*j) & 15; + + step = step_tab[index]; + index += iTab4[value]; + if (index < 0) index = 0; + else if (index > 88) index = 88; + + sign = 1 - 2 * ((value >> 3) & 1); + value &= 7; + + pred += sign * (2 * value + 1) * step / 4; + if (pred > 32767) pred = 32767; + else if (pred < -32768) pred = -32768; + + outbuf[channels*(2*i+j)+ch] = pred; + } + + *index0 = index; + *pred0 = pred; + + return; +} + +static void +decode_ima_3bits (unsigned char * obuf, unsigned char * buf, ssize_t l, + int16 * pred0, int8 * index0, int channels, int ch) +{ + int j; + signed char sign; + ssize_t i; + + int32 pred = *pred0, raw; + int8 index = *index0, value; + int16 * outbuf = (int16 *) obuf, step; + + static const int step_tab[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 + }; + + static const int8 iTab3[8] = + {-1, -1, 1, 2, -1, -1, 1, 2}; + + for (i=0; i < l-2; i += 3) + { + raw = buf[i] + (buf[i+1] << 8) + (buf[i+2] << 16); + for (j = 0; j < 8; j++) + { + value = (raw >> (3*j)) & 7; + + step = step_tab[index]; + index += iTab3[value]; + if (index < 0) index = 0; + else if (index > 88) index = 88; + + sign = 1 - 2 * ((value >> 2) & 1); + value &= 3; + + pred += sign * (2 * value + 1) * step / 4; + if (pred > 32767) pred = 32767; + else if (pred < -32768) pred = -32768; + + outbuf[channels*(8*i/3+j)+ch] = pred; + } + } + + *index0 = index; + *pred0 = pred; + + return; +} + +static ssize_t +decode_mac_ima (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + ssize_t len = 0, olen = 0; + int i, channels = (int)(intptr)metadata; + int16 pred; + int8 index; + + while (len < l) + { + for (i = 0; i < channels; i++) + { + if (len + MAC_IMA_BLKLEN > l) return olen; + pred = (int16)((buf[len] << 8) | (buf[len+1] & 128)); + index = buf[len+1] & 127; + if (index > 88) index = 88; + len += 2; + + decode_ima (*obuf + olen, buf + len, MAC_IMA_BLKLEN - 2, &pred, + &index, channels, i); + len += MAC_IMA_BLKLEN-2; + } + olen += 4*(MAC_IMA_BLKLEN - 2)*channels; + } + + return olen; +} + +static ssize_t +decode_ms_ima (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + int i; + ssize_t len = 0, olen = 0; + msadpcm_values_t * val = (msadpcm_values_t *)metadata; + int8 index[MAX_CHANNELS]; + int16 * outbuf = (int16 *) * obuf, pred[MAX_CHANNELS]; + + for (i = 0; i < val->channels; i++) + { + if (len >= l) return olen; + pred[i] = (int16) le_int (buf + len, 2); + /* + * The microsoft docs says the sample from the block header should be + * played. + */ + outbuf[i] = pred[i]; + olen += 2; + index[i] = buf[len + 2]; + if (index[i] > 88) index[i] = 88; + if (index[i] < 0) index[i] = 0; + len += 4; + } + + if (val->bits == 4) + while (len < l) + { + for (i = 0; i < val->channels; i++) + { + if (len + 4 > l) return olen; + decode_ima (*obuf + olen, buf + len, 4, &pred[i], &index[i], + val->channels, i); + len += 4; + } + olen += 2*8*val->channels; + } + else + { + unsigned char rbuf[12]; + int j; + + while (len < l) + { + if (len + 12*val->channels > l) return olen; + for (i = 0; i < val->channels; i++) + { + /* + * Each sample word for a channel in an IMA ADPCM RIFF file is 4 + * bits. This doesn't resolve to an integral number of samples + * in a 3 bit ADPCM, so we use a simple method around this. + * This shouldn't skip samples since the spec guarantees the + * number of sample words in a block is divisible by 3. + */ + for (j = 0; j < 12; j++) + rbuf[j] = buf[len + j%4 + (j/4)*(val->channels*4) + i*4]; + decode_ima_3bits (*obuf + olen, rbuf, 12, &pred[i], &index[i], + val->channels, i); + } + /* 12 = 3 words per channel, each containing 4 bytes */ + len += 12*val->channels; + /* 64 = 32 samples per channel, each expanding to 2 bytes */ + olen += 64*val->channels; + } + } + return olen; +} + +static ssize_t +decode_raw_ima (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + ima_values_t * val = (ima_values_t *)metadata; + ssize_t olen = 0; + + /* We can't tell if/how it's interleaved. */ + decode_ima (*obuf, buf, l, &val->pred[0], &val->index[0], 1, 0); + olen = 4*l; + + return olen; +} + +static ssize_t +decode_float32_be (unsigned char ** obuf, unsigned char * buf, ssize_t l, + void * metadata) +{ + ssize_t i; + int exp, man; + int32 * wbuf = (int32 *) buf; + + for (i=0; i < l-3; i += 4) + { + exp = ((buf[i] & 0x7F) << 1) | ((buf[i+1] & 0x80) / 0x80); + man = ((buf[i+1] & 0x7F) << 16) | (buf[i+2] << 8) | buf[i+3]; + + *wbuf++ = float32_to_s32 (exp, man, (buf[i] & 0x80)); + } + + *obuf = buf; + return l; +} + +static ssize_t +decode_float32_le (unsigned char ** obuf, unsigned char * buf, ssize_t l, + void * metadata) +{ + ssize_t i; + int exp, man; + int32 * wbuf = (int32 *) buf; + + for (i=0; i < l-3; i += 4) + { + exp = ((buf[i+3] & 0x7F) << 1) | ((buf[i+2] & 0x80) / 0x80); + man = ((buf[i+2] & 0x7F) << 16) | (buf[i+1] << 8) | buf[i]; + + *wbuf++ = float32_to_s32 (exp, man, (buf[i+3] & 0x80)); + } + + *obuf = buf; + return l; +} + +static ssize_t +decode_double64_be (unsigned char ** obuf, unsigned char * buf, ssize_t l, + void * metadata) +{ + ssize_t i; + int exp; + int32 * wbuf = (int32 *) buf, lower, upper; + + for (i=0; i < l-7; i += 8) + { + exp = ((buf[i] & 0x7F) << 4) | ((buf[i+1] >> 4) & 0xF) ; + + upper = ((buf[i+1] & 0xF) << 24) | (buf[i+2] << 16) | (buf[i+3] << 8) | + buf[i+4]; + lower = (buf[i+5] << 16) | (buf[i+6] << 8) | buf[i+7]; + + *wbuf++ = double64_to_s32 (exp, upper, lower, buf[i] & 0x80); + } + + *obuf = buf; + return l/2; +} + +static ssize_t +decode_double64_le (unsigned char ** obuf, unsigned char * buf, ssize_t l, + void * metadata) +{ + ssize_t i; + int exp; + int32 * wbuf = (int32 *) buf, lower, upper; + + for (i=0; i < l-7; i += 8) + { + exp = ((buf[i+7] & 0x7F) << 4) | ((buf[i+6] >> 4) & 0xF); + + upper = ((buf[i+6] & 0xF) << 24) | (buf[i+5] << 16) | (buf[i+4] << 8) | + buf[i+3]; + lower = (buf[i+2] << 16) | (buf[i+1] << 8) | buf[i]; + + *wbuf++ = double64_to_s32 (exp, upper, lower, buf[i+7] & 0x80); + } + + *obuf = buf; + return l/2; +} + +static int32 +double64_to_s32 (int exp, int32 upper, int32 lower, int sign) +{ + ldouble_t out, value; + + if ((exp != 0) && (exp != 2047)) + { + value = (upper + lower / ((double)0x1000000))/((double)0x10000000) + 1; + value = ossplay_ldexpl (value, exp - 1023); + } + else if (exp == 0) + { +#if 0 + int j; + + out = (upper + lower / ((double)0x1000000))/((double)0x10000000); + for (j=0; j < 73; j++) out /= 1 << 14; +#endif + /* So low, that it's pretty much 0 for us */ + return 0; + } + else /* exp == 2047 */ + { + /* + * Either NaN, or +/- Inf. 0 is almost as close an approximation of + * Inf as the maximum sample value.... + */ + print_msg (WARNM, "exp == 2047 in file!\n"); + return 0; + } + + out = (sign ? 1 : -1) * value * S32_MIN; + if (out > S32_MAX) out = S32_MAX; + else if (out < S32_MIN) out = S32_MIN; + + return out; +} + +static int32 +float32_to_s32 (int exp, int man, int sign) +{ + ldouble_t out, value; + + if ((exp != 0) && (exp != 255)) + { + value = man ? (float)man/(float)0x800000 + 1 : 0.0; + value = ossplay_ldexpl (value, exp - 127); + } + else if (exp == 0) + { +#if 0 + value = (float)man / (float)0x800000; + value /= 1UL << 31; value /= 1UL << 31; value /= 1UL << 32; + value /= 1UL << 32; +#endif + /* So low, that it's pretty much 0 for us */ + return 0; + } + else /* exp == 255 */ + { + /* + * Either NaN, or +/- Inf. 0 is almost as close an approximation of + * Inf as the maximum sample value.... + */ + print_msg (WARNM, "exp == 255 in file!\n"); + return 0; + } + + out = (sign ? 1 : -1) * value * S32_MIN; + if (out > S32_MAX) out = S32_MAX; + else if (out < S32_MIN) out = S32_MIN; + + return out; +} + +int +get_db_level (const unsigned char * buf, ssize_t l, int format) +{ +/* + * Display a rough recording level meter, and the elapsed time. + */ + static const unsigned char db_table[256] = { + /* Lookup table for log10(ix)*2, ix=0..255 */ + 0, 0, 1, 2, 2, 3, 3, 3, 4, 4, + 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11 + }; + + int32 level, v = 0; + ssize_t i; + + level = 0; + if ((buf == NULL) || (l == 0)) return 0; + + switch (format) + { + case AFMT_U8: + { + uint8 * p; + + p = (uint8 *)buf; + + for (i = 0; i < l; i++) { + v = (*p++); + if (v > level) level = v; + } + } + case AFMT_S8: + { + int8 * p; + + p = (int8 *)buf; + + for (i = 0; i < l; i++) { + v = *p++; + if (v < 0) { + /* This can be false on a two's-complement machine */ + if (v != -v) v = -v; + else v = -(v+1); + } + if (v > level) level = v; + } + } + break; + + case AFMT_S16_NE: + { + int16 * p; + + p = (int16 *)buf; + + for (i = 0; i < l / 2; i++) { + v = *p++; + if (v < 0) { + if (v != -v) v = -v; + else v = -(v+1); + } + if (v > level) level = v; + } + } + level >>= 8; + break; + + case AFMT_S24_NE: + case AFMT_S32_NE: + { + int32 * p; + + p = (int32 *)buf; + + for (i = 0; i < l / 4; i++) { + v = *p++; + if (v < 0) { + if (v != -v) v = -v; + else v = -(v+1); + } + if (v > level) level = v; + } + } + level >>= 24; + break; + default: return -1; + } + + if (level > 255) level = 255; + v = db_table[level]; + + return v; +} + +static decoders_queue_t * +setup_normalize (int * format, int * obsize, decoders_queue_t * decoders) +{ + if ((*format == AFMT_S16_OE) || (*format == AFMT_S32_OE) || + (*format == AFMT_S24_OE) || (*format == AFMT_U16_LE) || + (*format == AFMT_U16_BE)) + { + decoders->next = + (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + decoders = decoders->next; + decoders->decoder = decode_endian; + decoders->metadata = (void *)(intptr)*format; + switch (*format) + { + case AFMT_S32_OE: *format = AFMT_S32_NE; break; + case AFMT_S24_OE: *format = AFMT_S24_NE; break; + default: *format = AFMT_S16_NE; break; + } + decoders->next = NULL; + decoders->outbuf = NULL; + decoders->flag = 0; + } + else if ((*format == AFMT_U8) || (*format == AFMT_MU_LAW) || + (*format == AFMT_S8) || (*format == AFMT_A_LAW)) + { + decoders->next = + (decoders_queue_t *)ossplay_malloc (sizeof (decoders_queue_t)); + decoders = decoders->next; + decoders->decoder = decode_8_to_s16; + decoders->metadata = (void *)(intptr)*format; + decoders->next = NULL; + *obsize *= 2; + decoders->outbuf = (unsigned char *)ossplay_malloc (*obsize); + decoders->flag = FREE_OBUF; + *format = AFMT_S16_NE; + } + return decoders; +} + +verbose_values_t * +setup_verbose (int format, double oconstant, double total_time) +{ + verbose_values_t * val; + + val = (verbose_values_t *)ossplay_malloc (sizeof (verbose_values_t)); + + if (total_time == 0) + { + val->tsecs = 0; + strcpy (val->tstring, "unknown"); + } + else + { + char * p; + + val->tsecs = total_time; + p = totime (val->tsecs); + strncpy (val->tstring, p, sizeof (val->tstring)); + ossplay_free (p); + val->tsecs -= UPDATE_EPSILON/1000; + } + + val->secs = 0; + val->secs_timer2 = 0; + val->next_sec = 0; + val->next_sec_timer2 = 0; + val->format = format; + val->constant = oconstant; + + return val; +} + +static errors_t +seek_normal (int fd, big_t * datamark, big_t filesize, double constant, + big_t rsize, int channels, void * metadata) +{ + big_t pos = seek_time * constant; + int ret; + + pos -= pos % channels; + if ((pos > filesize) || (pos < *datamark)) return E_DECODE; + + ret = ossplay_lseek (fd, pos - *datamark, SEEK_CUR); + if (ret == -1) + { + seek_time = 0; + return E_DECODE; + } + *datamark = ret; + + return E_OK; +} + +static errors_t +seek_compressed (int fd, big_t * datamark, big_t filesize, double constant, + big_t rsize, int channels, void * metadata) +/* + * We have to use this method because some compressed formats depend on the + * previous state of the decoder, and don't (yet?) have own seek function. + */ +{ + big_t pos = seek_time * constant; + + if (pos > filesize) + { + seek_time = 0; + return E_DECODE; + } + + if (*datamark + rsize < pos) + { + return SEEK_CONT_AFTER_DECODE; + } + else + { + /* Still not entirely accurate. */ + seek_time = *datamark / constant; + return E_OK; + } +} + +static ssize_t +read_normal (int fd, void * buf, size_t len, void * metadata) +{ + return read (fd, buf, len); +} + +#ifdef OGG_SUPPORT +static errors_t +seek_ogg (int fd, big_t * datamark, big_t filesize, double constant, + big_t rsize, int channels, void * metadata) +{ + ogg_data_t * val = (ogg_data_t *)metadata; + + if (val->f->ov_time_seek (&val->vf, seek_time) < 0) + { + seek_time = 0; + return E_DECODE; + } + *datamark = (big_t)val->f->ov_raw_tell (&val->vf); + return E_OK; +} + +static ssize_t +read_ogg (int fd, void * buf, size_t len, void * metadata) +{ + int c_bitstream; + ssize_t ret = 0; + ogg_data_t * val = (ogg_data_t *)metadata; + + if (val->setup == 1) + { + vorbis_info * vi; + + vi = val->f->ov_info (&val->vf, -1); + + ret = setup_device (val->dsp, AFMT_S16_NE, vi->channels, vi->rate); + if (ret < 0) return -1; + val->setup = 0; + } + + do + { +#if 0 + if (ret == OV_HOLE) + print_msg (NOTIFYM, "Hole in the OggVorbis stream!\n"); +#endif + c_bitstream = val->bitstream; + ret = (ssize_t)val->f->ov_read (&val->vf, (char *)buf, (int)len, +#ifdef OSS_LITTLE_ENDIAN + 0, +#else + 1, +#endif + 2, 1, &val->bitstream); + } + while (ret == OV_HOLE); + + if (ret == 0) return 0; + else if (ret < 0) return ret; + + if ((c_bitstream != val->bitstream) && (c_bitstream != -1)) + { + val->bitstream = c_bitstream; + val->setup = 1; + } + + return ret; +} +#endif + +#ifdef SRC_SUPPORT +#define GRC3_HIGH_QUALITY 4 +static ssize_t +decode_mono_to_stereo (unsigned char ** obuf, unsigned char * buf, + ssize_t l, void * metadata) +{ + ssize_t i; + int format = (int)(intptr)metadata; + + switch (format) { + case AFMT_U8: + case AFMT_S8: { + uint8 *r = (uint8 *)buf, *s = (uint8 *)*obuf; + for (i=0; i < l; i++) { + *s++ = *r; + *s++ = *r++; + } + } + break; + case AFMT_S16_LE: + case AFMT_S16_BE: { + int16 *r = (int16 *)buf, *s = (int16 *)*obuf; + + for (i = 0; i < l/2 ; i++) { + *s++ = *r; + *s++ = *r++; + } + } + break; + case AFMT_S32_LE: + case AFMT_S32_BE: + case AFMT_S24_LE: + case AFMT_S24_BE: { + int32 *r = (int32 *)buf, *s = (int32 *)*obuf; + + for (i = 0; i < l/4; i++) { + *s++ = *r; + *s++ = *r++; + } + } + break; + } + return 2*l; +} + +static ssize_t +decode_src (unsigned char ** obuf, unsigned char * buf, ssize_t l, void * metadata) +{ + grc_data_t * val = (grc_data_t *)metadata; + ssize_t outc = 0; + int i; + + for (i=0; i<val->channels; i++) { + outc += grc3_convert (&val->grc[i], val->bits, GRC3_HIGH_QUALITY, buf, + *obuf, 8 * l / val->channels / val->bits, + val->obsize / val->channels / sizeof (int), + val->channels, i); + } + + return outc * val->bits / 8; +} + +static grc_data_t * +setup_grc3 (int format, int channels, int speed, int ospeed, int obsize) +{ + int i; + grc_data_t * val = (grc_data_t *)ossplay_malloc (sizeof (grc_data_t) + + sizeof (grc3state_t) * channels); + + val->bits = format2bits (format); + val->channels = channels; + val->speed = speed; + val->ospeed = ospeed; + val->obsize = obsize; + + for (i=0; i<channels; i++) { + grc3_reset (&val->grc[i]); + grc3_setup (&val->grc[i], ospeed, speed); + } + + return val; +} +#endif + diff --git a/cmd/ossplay/ossplay_decode.h b/cmd/ossplay/ossplay_decode.h new file mode 100644 index 0000000..c05fe8b --- /dev/null +++ b/cmd/ossplay/ossplay_decode.h @@ -0,0 +1,30 @@ +#ifndef OSSPLAY_DECODE_H +#define OSSPLAY_DECODE_H + +#include "ossplay.h" + +#define MAC_IMA_BLKLEN 34 +/* + * ima4 block length in AIFC files. Qt has "stsd" chunk which can change this, + * but I know of no AIFC equivalent. + */ + +typedef struct verbose_values { + char tstring[20]; + double secs; + double next_sec; + double secs_timer2; + double next_sec_timer2; + double tsecs; + double constant; + int format; +} +verbose_values_t; + +errors_t decode_sound (dspdev_t *, int, big_t, int, int, int, void *); +errors_t encode_sound (dspdev_t *, fctypes_t, const char *, int, int, int, + double); +int get_db_level (const unsigned char *, ssize_t, int); +verbose_values_t * setup_verbose (int, double, double); + +#endif diff --git a/cmd/ossplay/ossplay_parser.c b/cmd/ossplay/ossplay_parser.c new file mode 100644 index 0000000..fd7997f --- /dev/null +++ b/cmd/ossplay/ossplay_parser.c @@ -0,0 +1,1830 @@ +/* + * Purpose: File format parse routines for ossplay + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "ossplay_parser.h" +#include "ossplay_decode.h" + +#include <ctype.h> +#include <sys/stat.h> + +/* Magic numbers used in Sun and NeXT audio files (.au/.snd) */ +#define SUN_MAGIC 0x2e736e64 /* Really '.snd' */ +#define SUN_INV_MAGIC 0x646e732e /* '.snd' upside-down */ +#define DEC_MAGIC 0x2e736400 /* Really '\0ds.' (for DEC) */ +#define DEC_INV_MAGIC 0x0064732e /* '\0ds.' upside-down */ + +/* Magic numbers for .w64 */ +#define riff_GUID 0x2E91CF11 +#define riff_GUID2 0xA5D628DB +#define riff_GUID3 0x04C10000 + +enum { + COMM_BIT, + SSND_BIT, + FVER_BIT +}; + +#define COMM_FOUND (1 << COMM_BIT) +#define SSND_FOUND (1 << SSND_BIT) +#define FVER_FOUND (1 << FVER_BIT) + +#define H(A, B, C, D) ((A << 24) | (B << 16) | (C << 8) | D) + +typedef struct { + msadpcm_values_t msadpcm_val; + int channels, fd, format, found, speed; + fctypes_t type; + uint32 chunk_id; + big_t cpos, chunk_size, cur_size, fut_size, sound_loc, sound_size, total_size; + const char * filename; + big_t (* ne_int) (const unsigned char *, int); +} +file_t; + +typedef int (chunk_parser_t) (uint32, unsigned char *, big_t, file_t *); + +enum { + CP_STOP_READING = -2, + CP_PLAY_NOW, + CP_OK +}; + +typedef ssize_t (file_read_t) (file_t *, unsigned char *, size_t); +typedef int (file_init_t) (file_t *, unsigned char *); +typedef int (file_iterator_t) (file_t *, unsigned char *, int); +typedef ssize_t (file_seek_t) (file_t *, off_t, int); + +typedef enum { + R_ZERO_FLAG, + READ_NONE, + READ_ALL, + READ_PART +} +read_flag_t; + +typedef struct chunk_functions { + const uint32 id; + const uint32 d_chunk_size; + const read_flag_t read_chunk_f; + chunk_parser_t * f; +} +chunk_functions_t; + +typedef struct parser { + file_init_t * init; + file_read_t * read; + file_iterator_t * iterator; + const chunk_functions_t * perfile; + const chunk_functions_t * common; +} +parser_t; + +extern int quiet, verbose, force_fmt, force_speed; +extern long seek_byte; +extern flag from_stdin, raw_file; +extern off_t (*ossplay_lseek) (int, off_t, int); + +static errors_t play_au (dspdev_t *, const char *, int, unsigned char *, int); +static errors_t play_iff (dspdev_t *, const char *, int, unsigned char *, int, parser_t *); +static errors_t play_voc (dspdev_t *, const char *, int, unsigned char *, int); +static void print_verbose_fileinfo (const char *, int, int, int, int); + +static file_init_t caf_init; +static file_iterator_t caf_iterator; +static file_init_t iff_init; +static file_read_t iff_read; +static file_iterator_t iff_iterator; +static chunk_parser_t iff_comment_parse; +static file_init_t w64_init; +static file_iterator_t w64_iterator; + +static chunk_parser_t _16sv_vhdr_parse; +static chunk_parser_t _8svx_vhdr_parse; +static chunk_parser_t aifc_comm_parse; +static chunk_parser_t aifc_fver_parse; +static chunk_parser_t aiff_comm_parse; +static chunk_parser_t aiff_ssnd_parse; +static chunk_parser_t caf_data_parse; +static chunk_parser_t caf_desc_parse; +static chunk_parser_t maud_chan_parse; +static chunk_parser_t maud_mhdr_parse; +static chunk_parser_t wave_data_parse; +static chunk_parser_t wave_disp_parse; +static chunk_parser_t wave_fmt_parse; +static chunk_parser_t wave_list_parse; + +static const chunk_functions_t IFF_common[] = { + { H('A', 'N', 'N', 'O'), 0, READ_ALL, &iff_comment_parse }, + { H('N', 'A', 'M', 'E'), 0, READ_ALL, &iff_comment_parse }, + { H('(', 'c', ')', ' '), 0, READ_ALL, &iff_comment_parse }, + { H('A', 'U', 'T', 'H'), 0, READ_ALL, &iff_comment_parse }, + { 0, 0, READ_NONE, NULL } +}; + +static const chunk_functions_t AIFF_funcs[] = { + { H('C', 'O', 'M', 'M'), 18, READ_ALL, &aiff_comm_parse }, + { H('S', 'S', 'N', 'D'), 8, READ_PART, &aiff_ssnd_parse }, + { 0, 0, R_ZERO_FLAG, NULL } +}; + +static const chunk_functions_t AIFC_funcs[] = { + { H('C', 'O', 'M', 'M'), 22, READ_ALL, &aifc_comm_parse }, + { H('S', 'S', 'N', 'D'), 8, READ_PART, &aiff_ssnd_parse }, + { H('F', 'V', 'E', 'R'), 4, READ_ALL, &aifc_fver_parse }, + { 0, 0, R_ZERO_FLAG, NULL } +}; + +static const chunk_functions_t WAVE_funcs[] = { + { H('f', 'm', 't', ' '), 14, READ_ALL, &wave_fmt_parse }, + { H('d', 'a', 't', 'a'), 0, READ_NONE, &wave_data_parse }, + { H('D', 'I', 'S', 'P'), 5, READ_ALL, &wave_disp_parse }, + { H('L', 'I', 'S', 'T'), 12, READ_ALL, &wave_list_parse }, + { 0, 0, R_ZERO_FLAG, NULL } +}; + +static const chunk_functions_t _8SVX_funcs[] = { + { H('B', 'O', 'D', 'Y'), 0, READ_NONE, &wave_data_parse }, + { H('V', 'H', 'D', 'R'), 16, READ_ALL, &_8svx_vhdr_parse }, + { 0, 0, R_ZERO_FLAG, NULL } +}; + +static const chunk_functions_t _16SV_funcs[] = { + { H('V', 'H', 'D', 'R'), 14, READ_ALL, &_16sv_vhdr_parse }, + { H('B', 'O', 'D', 'Y'), 0, READ_NONE, &wave_data_parse }, + { 0, 0, R_ZERO_FLAG, NULL } +}; + +static const chunk_functions_t MAUD_funcs[] = { + { H('M', 'D', 'A', 'T'), 0, READ_NONE, &wave_data_parse }, + { H('C', 'H', 'A', 'N'), 4, READ_ALL, &maud_chan_parse }, + { H('M', 'H', 'D', 'R'), 20, READ_ALL, &maud_mhdr_parse }, + { 0, 0, R_ZERO_FLAG, NULL } +}; + +static const chunk_functions_t CAF_funcs[] = { + { H('d', 'e', 's', 'c'), 32, READ_ALL, &caf_desc_parse }, + { H('d', 'a', 't', 'a'), 4, READ_NONE, &caf_data_parse }, + { 0, 0, R_ZERO_FLAG, NULL } +}; + +#ifdef OGG_SUPPORT +static errors_t play_ogg (dspdev_t *, const char *, int, unsigned char *, int, + dlopen_funcs_t **); + +/* + * OV_CALLBACKS_DEFAULT is not defined by older Vorbis versions so + * we have to define our own version. + */ +static int _ov_header_fseek_wrap_ (FILE *f, ogg_int64_t off, int whence) +{ + if (f == NULL) return -1; + return fseek(f, off, whence); +} + +static ov_callbacks OV_CALLBACKS_DEFAULT_ = { + (size_t (*)(void *, size_t, size_t, void *)) fread, + (int (*)(void *, ogg_int64_t, int)) _ov_header_fseek_wrap_, + (int (*)(void *)) fclose, + (long (*)(void *)) ftell +}; + +/* Only handles Ogg/Vorbis */ +static errors_t +play_ogg (dspdev_t * dsp, const char * filename, int fd, unsigned char * hdr, + int l, dlopen_funcs_t ** vft) +{ + + FILE * f; + errors_t ret = E_OK; + ogg_data_t ogg_data; + vorbis_info * vi; + + f = fdopen (fd, "rb"); + + if (*vft == NULL) + { + *vft = (dlopen_funcs_t *)ossplay_malloc (sizeof (dlopen_funcs_t)); + + (*vft)->vorbisfile_handle = ossplay_dlopen ("libvorbisfile.so.3"); + + if ((*vft)->vorbisfile_handle == NULL) + { + const char * msg = ossplay_dlerror(); + + ossplay_free (*vft); + *vft = NULL; + print_msg (ERRORM, "Can't dlopen libvorbisfile! (Error was %s)\n", + msg?msg:""); + return E_DECODE; + } + + /* + * Assigning to the address pointed by the casted lvalue is the + * POSIX.1-2003 workaround to the dlsym return type issue, and both the + * opengroup and glibc examples use that method, so this should be safe + * on POSIX systems. + */ + if (ossplay_vdlsym ((*vft)->vorbisfile_handle, + (void **)&(*vft)->ov_clear, "ov_clear", + (void **)&(*vft)->ov_comment, "ov_comment", + (void **)&(*vft)->ov_info, "ov_info", + (void **)&(*vft)->ov_open_callbacks, "ov_open_callbacks", + (void **)&(*vft)->ov_raw_tell, "ov_raw_tell", + (void **)&(*vft)->ov_read, "ov_read", + (void **)&(*vft)->ov_seekable, "ov_seekable", + (void **)&(*vft)->ov_time_total, "ov_time_total", + (void **)&(*vft)->ov_time_seek, "ov_time_seek", + (void **)NULL)) + { + /* The call already took care of making an error printout for us */ + ossplay_dlclose ((*vft)->vorbisfile_handle); + ossplay_free (*vft); + *vft = NULL; + return E_DECODE; + } + } + + ogg_data.f = *vft; + + if (ogg_data.f->ov_open_callbacks (f, &ogg_data.vf, (char *)hdr, l, + OV_CALLBACKS_DEFAULT_) < 0) + { + print_msg (ERRORM, "File %s is not an OggVorbis file!\n", filename); + return E_DECODE; + } + + ogg_data.dsp = dsp; + ogg_data.setup = 0; + ogg_data.bitstream = -1; + + vi = ogg_data.f->ov_info (&ogg_data.vf, -1); + if (verbose) + { + vorbis_comment * oc = ogg_data.f->ov_comment(&ogg_data.vf,-1); + char **p = oc->user_comments; + + while (*p) + print_msg (VERBOSEM, "%s: Comments: %s\n", filename, *p++); + if ((verbose > 1) && (oc->vendor)) + print_msg (VERBOSEM, "%s: Vendor: %s\n", filename, oc->vendor); + print_msg (VERBOSEM, "%s: Nominal bitrate: %d\n", filename, vi->bitrate_nominal); + + print_verbose_fileinfo (filename, OGG_FILE, AFMT_VORBIS, vi->channels, vi->rate); + } + + ret = decode_sound (dsp, fd, BIG_SPECIAL, AFMT_VORBIS, vi->channels, vi->rate, + (void *)&ogg_data); + + ogg_data.f->ov_clear (&ogg_data.vf); + + return ret; +} +#endif + +errors_t +play_file (dspdev_t * dsp, const char * filename, dlopen_funcs_t ** dlt) +{ + int fd, id; + ssize_t l, i; + unsigned char buf[P_READBUF_SIZE]; + const char * bname, * suffix; + struct stat st; + errors_t ret = E_OK; + + parser_t piff = { + &iff_init, + &iff_read, + &iff_iterator, + NULL, + NULL + }; + + if (from_stdin) { + FILE *fp; + + fp = fdopen(0, "rb"); + fd = fileno(fp); + /* + * Use emulation if stdin is not seekable (e.g. on Linux). + */ + if (lseek (fd, 0, SEEK_CUR) == -1) ossplay_lseek = ossplay_lseek_stdin; + errno = 0; + bname = "-"; + } else { + fd = open (filename, O_RDONLY, 0); + bname = filepart (filename); + } + + if (fd == -1) + { + perror_msg (filename); + return E_DECODE; + } + + if (seek_byte != 0) + { + ossplay_lseek (fd, (off_t)seek_byte, SEEK_CUR); + } + + if (raw_file) + { + big_t len; + + if (fstat (fd, &st) == -1) { + perror_msg (filename); + len = BIG_SPECIAL; + } else { + len = st.st_size; + } + print_msg (NORMALM, "%s: Playing RAW file.\n", bname); + + ret = decode_sound (dsp, fd, len, DEFAULT_FORMAT, + DEFAULT_CHANNELS, DEFAULT_SPEED, NULL); + goto done; + } + + if ((l = read (fd, buf, 12)) == -1) + { + perror_msg (filename); + goto seekerror; + } + + if (l == 0) + { + print_msg (ERRORM, "%s is empty file.\n", bname); + goto seekerror; + } + +/* + * Try to detect the file type + */ + id = be_int (buf, 4); + switch (id) { + case SUN_MAGIC: + case DEC_MAGIC: + case SUN_INV_MAGIC: + case DEC_INV_MAGIC: + if ((i = read (fd, buf + 12, 12)) == -1) { + perror_msg (filename); + goto seekerror; + } + l += i; + if (l < 24) break; + ret = play_au (dsp, bname, fd, buf, l); + goto done; + case H('C', 'r', 'e', 'a'): + if ((i = read (fd, buf + 12, 7)) == -1) { + perror_msg (filename); + goto seekerror; + } + l += i; + if ((l < 19) || (memcmp (buf, "Creative Voice File", 19))) break; + ret = play_voc (dsp, bname, fd, buf, l); + goto done; + case H('R', 'I', 'F', 'F'): + case H('R', 'I', 'F', 'X'): + if ((l < 12) || be_int (buf + 8, 4) != H('W', 'A', 'V', 'E')) break; + if (force_fmt == AFMT_IMA_ADPCM) force_fmt = AFMT_MS_IMA_ADPCM; + piff.perfile = WAVE_funcs; + ret = play_iff (dsp, bname, fd, buf, (id == H('R', 'I', 'F', 'X'))? + WAVE_FILE_BE:WAVE_FILE, &piff); + goto done; + case H('r', 'i', 'f', 'f'): + if ((l < 12) || (read (fd, buf + 12, 4) < 4)) break; + if (be_int (buf + 4, 4) != riff_GUID) break; + if (be_int (buf + 8, 4) != riff_GUID2) break; + if (be_int (buf + 12, 4) != riff_GUID3) break; + piff.perfile = WAVE_funcs; + piff.iterator = w64_iterator; + piff.init = w64_init; + ret = play_iff (dsp, bname, fd, buf, W64_FILE, &piff); + goto done; + case H('F', 'O', 'R', 'M'): + if (l < 12) break; + piff.common = IFF_common; + switch (be_int (buf + 8, 4)) { + case H('A', 'I', 'F', 'F'): + if (force_fmt == AFMT_IMA_ADPCM) force_fmt = AFMT_MAC_IMA_ADPCM; + piff.perfile = AIFF_funcs; + ret = play_iff (dsp, bname, fd, buf, AIFF_FILE, &piff); + goto done; + case H('A', 'I', 'F', 'C'): + if (force_fmt == AFMT_IMA_ADPCM) force_fmt = AFMT_MAC_IMA_ADPCM; + piff.perfile = AIFC_funcs; + ret = play_iff (dsp, bname, fd, buf, AIFC_FILE, &piff); + goto done; + case H('8', 'S', 'V', 'X'): + piff.perfile = _8SVX_funcs; + ret = play_iff (dsp, bname, fd, buf, _8SVX_FILE, &piff); + goto done; + case H('1', '6', 'S', 'V'): + piff.perfile = _16SV_funcs; + ret = play_iff (dsp, bname, fd, buf, _16SV_FILE, &piff); + goto done; + case H('M', 'A', 'U', 'D'): + piff.perfile = MAUD_funcs; + ret = play_iff (dsp, bname, fd, buf, MAUD_FILE, &piff); + goto done; + default: break; + } + piff.common = NULL; + break; + case H('c', 'a', 'f', 'f'): + piff.init = caf_init; + piff.iterator = caf_iterator; + piff.perfile = CAF_funcs; + ret = play_iff (dsp, bname, fd, buf, CAF_FILE, &piff); + goto done; + case H('O', 'g', 'g', 'S'): +#ifdef OGG_SUPPORT + ret = play_ogg (dsp, bname, fd, buf, l, dlt); + fd = -1; + goto done; +#endif + default: break; + } + + ossplay_lseek (fd, 0, SEEK_SET); /* Start from the beginning */ + +/* + * The file was not identified by its content. Try using the file name + * suffix. + */ + + suffix = strrchr (filename, '.'); + if (suffix == NULL) suffix = filename; + + if (fstat (fd, &st) == -1) + { + perror_msg (filename); + return E_DECODE; + } + + if (strcmp (suffix, ".au") == 0 || strcmp (suffix, ".AU") == 0) + { /* Raw mu-Law data */ + print_msg (VERBOSEM, "Playing raw mu-Law file %s\n", bname); + + ret = decode_sound (dsp, fd, st.st_size, AFMT_MU_LAW, 1, 8000, NULL); + goto done; + } + + if (strcmp (suffix, ".snd") == 0 || strcmp (suffix, ".SND") == 0) + { + print_msg (VERBOSEM, + "%s: Unknown format. Assuming RAW audio (%d/%d/%d).\n", + bname, DEFAULT_SPEED, DEFAULT_FORMAT, DEFAULT_CHANNELS); + + ret = decode_sound (dsp, fd, st.st_size, DEFAULT_FORMAT, DEFAULT_CHANNELS, + DEFAULT_SPEED, NULL); + goto done; + } + + if (strcmp (suffix, ".cdr") == 0 || strcmp (suffix, ".CDR") == 0) + { + print_msg (VERBOSEM, "%s: Playing CD-R (cdwrite) file.\n", bname); + + ret = decode_sound (dsp, fd, st.st_size, AFMT_S16_BE, 2, 44100, NULL); + goto done; + } + + + if (strcmp (suffix, ".raw") == 0 || strcmp (suffix, ".RAW") == 0) + { + print_msg (VERBOSEM, "%s: Playing RAW file.\n", bname); + + ret = decode_sound (dsp, fd, st.st_size, DEFAULT_FORMAT, DEFAULT_CHANNELS, + DEFAULT_SPEED, NULL); + goto done; + } + + print_msg (ERRORM, "%s: Unrecognized audio file type.\n", filename); +done: + if (fd != -1) close (fd); + +#if 0 + ioctl (fd, SNDCTL_DSP_SYNC, NULL); +#endif + return ret; +seekerror: + close (fd); + return E_DECODE; +} + +/* + * Generalized parser for chunked files, especially IFF based ones - handles + * WAV, AIFF, AIFC, CAF, W64, RF64, 8SVX, 16SV and MAUD. + */ +static errors_t +play_iff (dspdev_t * dsp, const char * filename, int fd, unsigned char * buf, + int type, parser_t * p) +{ + int ret; + + sbig_t rbytes; + const chunk_functions_t * i, * oi; + file_t f = { { + 256, 496, 7, 4, { + {256, 0}, + {512, -256}, + {0, 0}, + {192, 64}, + {240, 0}, + {460, -208}, + {392, -232} }, + DEFAULT_CHANNELS + } }; + + if (force_fmt != 0) f.format = force_fmt; + else f.format = AFMT_S16_LE; + f.channels = DEFAULT_CHANNELS; + f.speed = DEFAULT_SPEED; + f.fd = fd; + f.filename = filename; + f.cur_size = 12; + f.fut_size = 12; + f.type = type; + + rbytes = p->init (&f, buf); + if (rbytes < 0) return E_DECODE; + if (verbose > 1) + print_msg (VERBOSEM, "FORM len = %u\n", f.total_size); + + if (p->perfile) oi = p->perfile; + else if (p->common) oi = p->common; + else return E_DECODE; + while (!p->iterator (&f, buf, rbytes)) { + for (i = oi; i->id != 0;) { + if (i->id != f.chunk_id) { + i++; + if ((i->id == 0) && (p->common) && (!i->read_chunk_f)) i = p->common; + continue; + } + if (f.chunk_size < i->d_chunk_size) { + print_msg (ERRORM, "%c%c%c%c chunk's size (" _PRIbig_t ") is smaller " + "than the expected size (%d)!\n", buf[0], buf[1], buf[2], + buf[3], f.chunk_size, i->d_chunk_size); + break; + } + if (i->read_chunk_f != READ_NONE) { + int rlen = (f.chunk_size > P_READBUF_SIZE)?P_READBUF_SIZE:f.chunk_size; + + if ((i->read_chunk_f == READ_PART) && (f.chunk_size >= i->d_chunk_size)) + rlen = i->d_chunk_size; + if ((rlen = p->read (&f, buf, rlen)) < 0) goto nexta; + rbytes = rlen; + } else { + rbytes = f.chunk_size; + } + if (i->f == NULL) break; + ret = i->f (f.chunk_id, buf, rbytes, &f); + if (ret == CP_PLAY_NOW) { + if ((i->read_chunk_f == READ_NONE) && (i->d_chunk_size)) + ossplay_lseek (f.fd, i->d_chunk_size, SEEK_CUR); + goto stdinext; + } + if (ret == CP_STOP_READING) goto nexta; + if (ret) return ret; + break; + } + + if ((f.chunk_size >= rbytes) && (f.fut_size < f.total_size)) + if (ossplay_lseek (f.fd, f.fut_size - f.cur_size - f.cpos, SEEK_CUR) < 0) + break; + rbytes = 0; + } + +nexta: + if ((f.found & COMM_FOUND) == 0) { + print_msg (ERRORM, "%s: Couldn't find format chunk!\n", filename); + return E_DECODE; + } + + if ((f.found & SSND_FOUND) == 0) { + print_msg (ERRORM, "%s: Couldn't find sound chunk!\n", filename); + return E_DECODE; + } + + if ((type == AIFC_FILE) && ((f.found & FVER_FOUND) == 0)) + print_msg (WARNM, "%s: Couldn't find AIFC FVER chunk.\n", filename); + + if (ossplay_lseek (f.fd, f.sound_loc, SEEK_SET) == -1) { + perror_msg (filename); + return E_DECODE; + } + +stdinext: + if (verbose) + print_verbose_fileinfo (filename, type, f.format, f.channels, f.speed); + + return decode_sound (dsp, fd, f.sound_size, f.format, f.channels, f.speed, + (void *)&(f.msadpcm_val)); +} + +/*ARGSUSED*/ +static errors_t +play_au (dspdev_t * dsp, const char * filename, int fd, unsigned char * hdr, + int l) +{ + int channels = 1, format = AFMT_S8, speed = 11025; + big_t filelen; + uint32 fmt = 0, i, p = 24, an_len = 0; + + p = be_int (hdr + 4, 4); + fmt = be_int (hdr + 12, 4); + speed = be_int (hdr + 16, 4); + channels = be_int (hdr + 20, 4); + if (memcmp (hdr + 8, "\xFF\xFF\xFF\xFF", 4)) { + filelen = be_int (hdr + 8, 4); + if (verbose > 1) + print_msg (VERBOSEM, "%s: Filelen: " _PRIbig_t "\n", filename, filelen); + } else { + struct stat st; + + if (from_stdin || (fstat (fd, &st) == -1)) filelen = BIG_SPECIAL; + else filelen = st.st_size - 24 - p; + if (verbose > 1) + print_msg (VERBOSEM, "%s: Filelen: unspecified\n", filename); + } + if (verbose > 2) print_msg (VERBOSEM, "%s: Offset: %u\n", filename, p); + + if (force_fmt == 0) switch (fmt) + { + case 1: + format = AFMT_MU_LAW; + break; + + case 2: + format = AFMT_S8; + break; + + case 3: + format = AFMT_S16_BE; + break; + + case 4: + format = AFMT_S24_PACKED_BE; + break; + + case 5: + format = AFMT_S32_BE; + break; + + case 6: + format = AFMT_FLOAT32_BE; + break; + + case 7: + format = AFMT_DOUBLE64_BE; + break; + + case 23: + case 24: + case 25: + case 26: + print_msg (ERRORM, "%s: G.72x ADPCM encoded .au files are not supported\n", + filename); + return E_FORMAT_UNSUPPORTED; + + case 27: + format = AFMT_A_LAW; + break; + + default: + print_msg (ERRORM, "%s: Unknown encoding %d.\n", filename, fmt); + return E_FORMAT_UNSUPPORTED; + } + + if (verbose) + { + print_verbose_fileinfo (filename, AU_FILE, format, channels, speed); + + if (p > 24) + { + unsigned char * tag; + + if (p > 1047) an_len = 1023; + else an_len = p - 24; + if (read (fd, hdr, an_len) < an_len) + { + print_msg (ERRORM, "%s: Can't read %u bytes from pos 24\n", + filename, an_len); + return E_DECODE; + } + + tag = hdr + an_len; + for (i = 0; i < an_len; i++) + { + if (!isprint (hdr[i])) hdr[i] = ' '; + else tag = hdr + i + 1; + } + *tag = '\0'; + print_msg (VERBOSEM, "%s: Annotations: %s\n", filename, hdr); + } + } + + if (ossplay_lseek (fd, p - l - an_len, SEEK_CUR) == -1) + { + perror_msg (filename); + print_msg (ERRORM, "Can't seek to the data chunk\n"); + return E_DECODE; + } + + return decode_sound (dsp, fd, filelen, format, channels, speed, NULL); +} + +/*ARGSUSED*/ +static errors_t +play_voc (dspdev_t * dsp, const char * filename, int fd, unsigned char * hdr, + int l) +{ +#define VREAD(fd, buf, len) \ + do { \ + if (read (fd, buf, len) < len) \ + { \ + print_msg (ERRORM, "%s: Can't read %d bytes at pos %d\n", \ + filename, len, l); \ + return E_DECODE; \ + } \ + pos += len; \ + } while (0) + + uint32 blklen, data_offs, fmt, id, len, loopcount = 0, loopoffs = 4, + pos = l + 7, tmp, vers; + unsigned char buf[256], block_type; + flag plock = 0; + int speed = 11025, channels = 1, bits = 8, format = AFMT_U8; + errors_t ret; + + if (read (fd, hdr + 19, 7) < 7) + { + print_msg (ERRORM, "%s: Not a valid .VOC file\n", filename); + return E_DECODE; + } + + data_offs = le_int (hdr + 0x14, 2); + vers = le_int (hdr + 0x16, 2); + id = le_int (hdr + 0x18, 2); + + if ((((~vers) + 0x1234) & 0xffff) != id) + { + print_msg (ERRORM, "%s: Not a valid .VOC file\n", filename); + return E_DECODE; + } + + print_msg (VERBOSEM, "Playing .VOC file %s\n", filename); + + /*LINTED*/ while (1) + { + if (ossplay_lseek (fd, data_offs - pos, SEEK_CUR) == -1) + { + print_msg (ERRORM, "%s: Can't seek to pos %d\n", filename, data_offs); + return E_DECODE; + } + pos = data_offs + 4; + + if ((tmp = read (fd, buf, 1)) < 1) + { + /* Don't warn when read returns 0 - it may be end of file. */ + if (tmp != 0) + print_msg (ERRORM, + "%s: Can't read 1 byte at pos %d\n", filename, l); + return E_DECODE; + } + + block_type = buf[0]; + + if (block_type == 0) + return E_OK; /* End */ + + if (read (fd, buf, 3) != 3) + { + print_msg (ERRORM, "%s: Truncated .VOC file (%d)\n", + filename, buf[0]); + return E_DECODE; + } + + blklen = len = le_int (buf, 3); + + if (verbose > 2) + print_msg (VERBOSEM, "%s: %0x: Block type %d, len %d\n", + filename, data_offs, block_type, len); + switch (block_type) + { + + case 1: /* Sound data buf */ + if (!plock) + { + VREAD (fd, buf, 2); + + tmp = 256 - buf[0]; /* Time constant */ + speed = (1000000 + tmp / 2) / tmp / channels; + + fmt = buf[1]; + len -= 2; + + if (force_fmt != 0) break; + switch (fmt) + { + case 0: format = AFMT_U8; break; + case 1: format = AFMT_CR_ADPCM_4; break; + case 2: format = AFMT_CR_ADPCM_3; break; + case 3: format = AFMT_CR_ADPCM_2; break; + case 4: format = AFMT_S16_LE; break; + case 6: format = AFMT_A_LAW; break; + case 7: format = AFMT_MU_LAW; break; + default: + print_msg (ERRORM, + "%s: encoding %d is not supported\n", + filename, fmt); + return E_FORMAT_UNSUPPORTED; + } + } + + case 2: /* Continuation data */ + if ((ret = decode_sound(dsp, fd, len, format, channels, speed, NULL))) + return ret; + pos += len; + break; + + case 3: /* Silence */ + VREAD (fd, buf, 3); + len = le_int (buf, 2); + tmp = 256 - buf[2]; /* Time constant */ + speed = (1000000 + tmp / 2) / tmp; + if ((ret = silence (dsp, len, speed))) return ret; + break; + + case 5: /* Text */ + if (!quiet) + { + size_t i; + + if (len > 256) len = 256; + VREAD (fd, buf, len); + for (i = 0; i < len; i++) if (!isprint (buf[i])) buf[i] = '.'; + buf[len-1] = '\0'; + print_msg (NORMALM, "Text: %s\n", buf); + } + break; + + case 6: /* Loop start */ + VREAD (fd, buf, 2); + loopoffs = data_offs + blklen + 4; + loopcount = le_int (buf, 2); + break; + + case 7: /* End of repeat loop */ + if (loopcount != 0xffff) loopcount--; + + /* Set "return" point. Compensate for increment of data_offs. */ + if (loopcount > 0) data_offs = loopoffs - blklen - 4; + + break; + + case 8: /* Sampling parameters */ + VREAD (fd, buf, 4); + + speed = 256000000/(channels * (65536 - le_int (buf, 2))); + channels = buf[3] + 1; + fmt = buf[2]; + plock = 1; + + if (force_fmt != 0) break; + switch (fmt) + { + case 0: format = AFMT_U8; break; + case 1: format = AFMT_CR_ADPCM_4; break; + case 2: format = AFMT_CR_ADPCM_3; break; + case 3: format = AFMT_CR_ADPCM_2; break; + case 4: format = AFMT_S16_LE; break; + case 6: format = AFMT_A_LAW; break; + case 7: format = AFMT_MU_LAW; break; + default: + print_msg (ERRORM, + "%s: encoding %d is not supported\n", filename, fmt); + return E_FORMAT_UNSUPPORTED; + } + break; + + case 9: /* New format sound data */ + VREAD (fd, buf, 12); + + len -= 12; + + speed = le_int (buf, 3); + bits = buf[4]; + channels = buf[5]; + fmt = le_int (buf + 6, 2); + + if (force_fmt == 0) switch (fmt) + { + case 0: format = AFMT_U8; break; + case 1: format = AFMT_CR_ADPCM_4; break; + case 2: format = AFMT_CR_ADPCM_3; break; + case 3: format = AFMT_CR_ADPCM_2; break; + case 4: format = AFMT_S16_LE; break; + case 6: format = AFMT_A_LAW; break; + case 7: format = AFMT_MU_LAW; break; + default: + print_msg (ERRORM, + "%s: encoding %d is not supported\n", filename, fmt); + return E_FORMAT_UNSUPPORTED; + } + + if ((ret = decode_sound(dsp, fd, len, format, channels, speed, NULL))) + return ret; + pos += len; + break; + } + + if (block_type != 8) plock = 0; + data_offs += blklen + 4; + } + /* return 0; */ /* Not reached */ +} + +static void +print_verbose_fileinfo (const char * filename, int type, int format, + int channels, int speed) +{ + char chn[32]; + const char * fmt = ""; + + if (force_speed) speed = force_speed; + switch (type) + { + case WAVE_FILE: + case WAVE_FILE_BE: + print_msg (VERBOSEM, "Playing WAVE file %s, ", filename); break; + case AIFC_FILE: + print_msg (VERBOSEM, "Playing AIFC file %s, ", filename); break; + case AIFF_FILE: + print_msg (VERBOSEM, "Playing AIFF file %s, ", filename); break; + case CAF_FILE: + print_msg (VERBOSEM, "Playing CAF file %s, ", filename); break; + case AU_FILE: + print_msg (VERBOSEM, "Playing AU file %s, ", filename); break; + case _8SVX_FILE: + print_msg (VERBOSEM, "Playing 8SVX file %s, ", filename); break; + case _16SV_FILE: + print_msg (VERBOSEM, "Playing 16SV file %s, ", filename); break; + case MAUD_FILE: + print_msg (VERBOSEM, "Playing MAUD file %s, ", filename); break; + case W64_FILE: + print_msg (VERBOSEM, "Playing W64 file %s, ", filename); break; + case OGG_FILE: + print_msg (VERBOSEM, "Playing OGG file %s, ", filename); break; + } + + if (channels == 1) + strcpy (chn, "mono"); + else if (channels == 2) + strcpy (chn, "stereo"); + else + snprintf (chn, sizeof(chn), "%d channels", channels); + + switch (format) + { + case AFMT_QUERY: fmt = "Invalid format"; break; + case AFMT_MAC_IMA_ADPCM: + case AFMT_MS_IMA_ADPCM: + case AFMT_IMA_ADPCM: fmt = "IMA ADPCM"; break; + case AFMT_MS_IMA_ADPCM_3BITS: fmt = "3BIT DVI ADPCM"; break; + case AFMT_MS_ADPCM: fmt = "MS-ADPCM"; break; + case AFMT_MU_LAW: fmt = "mu-law"; break; + case AFMT_A_LAW: fmt = "A-law"; break; + case AFMT_U8: + case AFMT_S8: fmt = "8 bits"; break; + case AFMT_S16_LE: + case AFMT_S16_BE: + case AFMT_U16_LE: + case AFMT_U16_BE: fmt = "16 bits"; break; + case AFMT_S24_LE: + case AFMT_S24_BE: + case AFMT_S24_PACKED_BE: + case AFMT_S24_PACKED: fmt = "24 bits"; break; + case AFMT_SPDIF_RAW: + case AFMT_S32_LE: + case AFMT_S32_BE: fmt = "32 bits"; break; + case AFMT_FLOAT32_LE: + case AFMT_FLOAT32_BE: fmt = "32 bit float"; break; + case AFMT_FLOAT: fmt = "float"; break; + case AFMT_DOUBLE64_LE: + case AFMT_DOUBLE64_BE: fmt = "64 bit float"; break; + case AFMT_VORBIS: fmt = "vorbis"; break; + case AFMT_MPEG: fmt = "mpeg"; break; + case AFMT_FIBO_DELTA: fmt = "fibonacci delta"; break; + case AFMT_EXP_DELTA: fmt = "exponential delta"; break; + } + print_msg (VERBOSEM, "%s/%s/%d Hz\n", fmt, chn, speed); +} + +static int +iff_init (file_t * f, unsigned char * buf) +{ + struct stat st; + + if (f->type != WAVE_FILE) f->ne_int = be_int; + else f->ne_int = le_int; + if (f->type == _8SVX_FILE) f->format = AFMT_S8; + else if (f->type == WAVE_FILE) f->format = AFMT_S16_LE; + else f->format = AFMT_S16_BE; + f->total_size = f->ne_int (buf + 4, 4) + 8; + + if (from_stdin) return 0; + if (fstat (f->fd, &st) == -1) return 0; + if (st.st_size < f->total_size) { + f->total_size = st.st_size; + print_msg (NOTIFYM, "%s: File size is smaller than the form size!\n", + f->filename); + } + return 0; +} + +static ssize_t +iff_read (file_t * f, unsigned char * buf, size_t l) +{ + ssize_t ret; + + if (l > P_READBUF_SIZE) l = P_READBUF_SIZE; + if ((ret = read (f->fd, buf, l)) < l) return CP_STOP_READING; + f->cpos += ret; + return ret; +} + +#if 0 +static ssize_t +iff_seek (file_t * f, off_t off, int flags) +{ + off_t l; + + /* We only do SEEK_CUR since we want to be able to stream from stdin */ + if (flags != SEEK_CUR) return -1; + if (off <= 0) return 0; + + l = ossplay_lseek (f->fd, off, flags); + return l; +} +#endif + +static int +iff_iterator (file_t * f, unsigned char * buf, int l) +{ + f->cur_size = f->fut_size + 8; + if (f->cur_size >= f->total_size) return 1; + + if (read (f->fd, buf, 8) < 8) { + print_msg (ERRORM, "%s: Cannot read chunk header at pos %u\n", + f->filename, f->cur_size); + if ((f->found & SSND_FOUND) && (f->found & COMM_FOUND)) + return CP_STOP_READING; + return E_DECODE; + } + f->chunk_id = be_int (buf, 4); + f->chunk_size = f->ne_int (buf + 4, 4); + f->cpos = 0; + f->fut_size += f->chunk_size + (f->chunk_size & 1) + 8; + + if (verbose > 3) + print_msg (VERBOSEM, "%s: Reading chunk %c%c%c%c, size %d, pos %d, next %d\n", + f->filename, buf[0], buf[1], buf[2], buf[3], f->chunk_size, + f->cur_size - 8, f->fut_size); + + if (f->chunk_size == 0) { + print_msg (NOTIFYM, "%s: Chunk size equals 0 (pos: %u)!\n", + f->filename, f->cur_size); + if ((f->found & SSND_FOUND) && (f->found & COMM_FOUND)) + return CP_STOP_READING; + return iff_iterator (f, buf, l); + } + + return 0; +} + +static int +iff_comment_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + unsigned char * tag; + uint32 i; + + if (!verbose) return 0; + + print_msg (STARTM, "%s: ", f->filename); + switch (id) { + case H('N', 'A', 'M', 'E'): + print_msg (CONTM, "Name: "); + break; + case H('A', 'U', 'T', 'H'): + print_msg (CONTM, "Author: "); + break; + case H('(', 'c', ')', ' '): + print_msg (CONTM, "Copyright: "); + break; + case H('A', 'N', 'N', 'O'): + print_msg (CONTM, "Annonations: "); + break; + /* Should never be reached */ + default: return 0; + } + + tag = buf + len + 1; + for (i = 0; i < len; i++) { + if (!isprint (buf[i])) buf[i] = ' '; + else tag = buf + i + 1; + } + *tag = '\0'; + print_msg (ENDM, "%s\n", buf); + return 0; +} + +#define BITS2SFORMAT(endian) \ + do { \ + if (force_fmt == 0) switch (bits) \ + { \ + case 8: f->format = AFMT_S8; break; \ + case 16: f->format = AFMT_S16_##endian; break; \ + case 24: f->format = AFMT_S24_PACKED_##endian; break; \ + case 32: f->format = AFMT_S32_##endian; break; \ + default: f->format = AFMT_S16_##endian; break; \ + } break; \ + } while (0) + +static int +aiff_comm_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + int bits; + + f->channels = be_int (buf, 2); +#if 0 + num_frames = be_int (buf + 2, 4); /* ossplay doesn't use this */ +#endif + bits = be_int (buf + 6, 2); + bits += bits % 8; + f->msadpcm_val.bits = bits; + BITS2SFORMAT (BE); + { + /* + * Conversion from SANE's IEEE-754 extended 80-bit to long double. + * We take some shortcuts which don't affect this application. + * See http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ + * for format into. + */ + int exp; + ldouble_t COMM_rate = 0; + + exp = ((buf[8] & 127) << 8) + buf[9] - 16383; +#if 0 + /* + * This part of the mantissa will typically be resolved to + * sub-Hz rates which we don't support anyway. + */ + COMM_rate = (buf[14] << 24) + (buf[15] << 16) + + (buf[16] << 8) + buf[17]; + COMM_rate /= 1L << 32; +#endif + COMM_rate += ((buf[10] & 127) << 24) + (buf[11] << 16) + + (buf[12] << 8) + buf[13]; + COMM_rate = ossplay_ldexpl (COMM_rate, exp-31); + if (buf[10] & 128) + COMM_rate += ossplay_ldexpl (1, exp); /* Normalize bit */ + if (buf[8] & 128) COMM_rate = -COMM_rate; /* Sign bit */ + if ((exp == 16384) || (COMM_rate <= 0)) { + print_msg (ERRORM, "Invalid sample rate!\n"); + return E_DECODE; + } + f->speed = COMM_rate; + } + f->found |= COMM_FOUND; + return 0; +} + +static int +aifc_comm_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + int ret, bits; + + if ((ret = aiff_comm_parse (id, buf, len, f))) return ret; + + bits = f->msadpcm_val.bits; + f->msadpcm_val.bits = 0; + + if ((force_fmt != 0) || (len < 22)) return 0; + switch (be_int (buf + 18, 4)) { + case H('N', 'O', 'N', 'E'): break; + case H('i', 'n', '1', '6'): f->format = AFMT_S16_BE; break; + case H('i', 'n', '2', '4'): f->format = AFMT_S24_BE; break; + case H('i', 'n', '3', '2'): f->format = AFMT_S32_BE; break; + case H('n', 'i', '2', '4'): f->format = AFMT_S24_LE; break; + case H('2', '3', 'n', 'i'): + case H('n', 'i', '3', '2'): f->format = AFMT_S32_LE; break; + /* + * sowt/tows were intended as 16 bits only, but some recording + * programs misinterpret this. We can try to handle that, + * since complaint programs set the bits field to 16 anyway. + * (See QT doc chap.4 section 3). + */ + case H('s', 'o', 'w', 't'): BITS2SFORMAT (LE); break; + case H('t', 'w', 'o', 's'): BITS2SFORMAT (BE); break; + case H('r', 'a', 'w', ' '): f->format = AFMT_U8; break; + case H('a', 'l', 'a', 'w'): + case H('A', 'L', 'A', 'W'): f->format = AFMT_A_LAW; break; + case H('u', 'l', 'a', 'w'): + case H('U', 'L', 'A', 'W'): f->format = AFMT_MU_LAW; break; + case H('i', 'm', 'a', '4'): f->format = AFMT_MAC_IMA_ADPCM; break; + case H('f', 'l', '3', '2'): + case H('F', 'L', '3', '2'): f->format = AFMT_FLOAT32_BE; break; + case H('f', 'l', '6', '4'): + case H('F', 'L', '6', '4'): f->format = AFMT_DOUBLE64_BE; break; + default: + print_msg (ERRORM, + "%s: error: %c%c%c%c compression is not supported\n", + f->filename, *(buf + 18), *(buf + 19), + *(buf + 20), *(buf + 21)); + return E_FORMAT_UNSUPPORTED; + } + return 0; +} + +static int +aiff_ssnd_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + uint32 offset; + + if (f->found & SSND_FOUND) { + print_msg (ERRORM, "%s: error: SSND hunk not singular!\n", f->filename); + return E_DECODE; + } + f->found |= SSND_FOUND; + + offset = be_int (buf, 4); +#if 0 + block_size = be_int (buf + 4, 4); /* ossplay doesn't use this */ +#endif + f->sound_loc = f->cur_size + 8 + offset; + f->sound_size = f->chunk_size - 8; + if (verbose > 2) + print_msg (VERBOSEM, "DATA chunk. Offs = " _PRIbig_t ", len = " _PRIbig_t + "\n", f->sound_loc, f->sound_size); + + if (from_stdin) return CP_PLAY_NOW; + return 0; +} + +static int +aifc_fver_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + uint32 timestamp = be_int (buf, 4); + if (timestamp != 0xA2805140) + print_msg (WARNM, "%s: Timestamp doesn't match AIFC v1 timestamps!\n", + f->filename); + f->found |= FVER_FOUND; + return 0; +} + +static int +_8svx_vhdr_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + f->speed = be_int (buf + 12, 2); + f->found |= COMM_FOUND; + if (force_fmt != 0) return 0; + switch (buf[15]) { + case 0: f->format = AFMT_S8; break; + case 1: f->format = AFMT_FIBO_DELTA; break; + case 2: f->format = AFMT_EXP_DELTA; break; + default: + print_msg (ERRORM, "%s: Unsupported compression %d\n", + f->filename, buf[15]); + return E_FORMAT_UNSUPPORTED; + } + return 0; +} + +static int +_16sv_vhdr_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + f->speed = be_int (buf + 12, 2); + f->found |= COMM_FOUND; + if (force_fmt != 0) f->format = AFMT_S16_BE; + return 0; +} + +static int +maud_chan_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + f->channels = be_int (buf, 4); + if (f->channels > 2) { + print_msg (ERRORM, "ossplay doesn't support MAUD format's " + "%d channel configuration\n", f->channels); + return E_CHANNELS_UNSUPPORTED; + } + return 0; +} + +static int +maud_mhdr_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + int bits = be_int (buf + 4, 2); + + BITS2SFORMAT (BE); + + if (be_int (buf + 12, 2) == 0) { + print_msg (ERRORM, "Invalid rate!\n"); + return E_DECODE; + } + f->speed = be_int (buf + 8, 4) / be_int (buf + 12, 2); + f->channels = be_int (buf + 16, 2); + f->found |= COMM_FOUND; + + if (force_fmt != 0) return 0; + switch (be_int (buf + 18, 2)) { + case 0: /* NONE */ break; + case 2: f->format = AFMT_A_LAW; break; + case 3: f->format = AFMT_MU_LAW; break; + case 6: f->format = AFMT_IMA_ADPCM; break; + default: + print_msg (ERRORM, "%s: format not supported", f->filename); + return E_FORMAT_UNSUPPORTED; + } + + return 0; +} + +static int +wave_data_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + if (f->found & SSND_FOUND) { + print_msg (ERRORM, "%s: error: SSND hunk not singular!\n", f->filename); + return E_DECODE; + } + + f->sound_loc = f->cur_size; + f->sound_size = f->chunk_size; + f->found |= SSND_FOUND; + if (verbose > 2) + print_msg (VERBOSEM, "DATA chunk. Offs = " _PRIbig_t ", len = " _PRIbig_t + "\n", f->sound_loc, f->sound_size); + + if (from_stdin) return CP_PLAY_NOW; + return 0; +} + +/* Cool Edit can create this chunk. Also some Windows files use it */ +static int +wave_disp_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + unsigned char * tag; + int i; + + if (verbose < 2) return 0; + if (f->ne_int (buf, 4) != 1) return 0; + + buf += 4; + tag = buf + len; + for (i = 0; i < len-1; i++) + { + if (!isprint (buf[i])) buf[i] = ' '; + else tag = buf + i + 1; + } + *tag = '\0'; + print_msg (VERBOSEM, "%s: %s\n", f->filename, buf); + return 0; +} + +static int +wave_list_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + unsigned char * sbuf = buf + 4, * tag; + int cssize = 4, i, slen = 4, subchunk_size; + uint32 chunk_id, subchunk_id; + + if (!verbose) return 0; + chunk_id = be_int (buf, 4); + if (chunk_id != H('I', 'N', 'F', 'O')) return 0; + do { + subchunk_id = be_int (sbuf, 4); + subchunk_size = f->ne_int (sbuf + 4, 4); + if (verbose > 3) + print_msg (VERBOSEM, "%s: Reading subchunk %c%c%c%c, size %d\n", + f->filename, sbuf[0], sbuf[1], sbuf[2], sbuf[3], + subchunk_size); + if (subchunk_size == 0) return 0; + cssize += subchunk_size; + if (cssize > len) return 0; + sbuf += 8; + slen = subchunk_size + (subchunk_size & 1); + switch (subchunk_id) { + case H('I', 'A', 'R', 'L'): + print_msg (STARTM, "%s: Archival Location: ", f->filename); + break; + case H('I', 'A', 'R', 'T'): + print_msg (STARTM, "%s: Artist Name: ", f->filename); + break; + case H('I', 'C', 'M', 'S'): + print_msg (STARTM, "%s: Commissioned: ", f->filename); + break; + case H('I', 'C', 'M', 'T'): + print_msg (STARTM, "%s: Comment: ", f->filename); + break; + case H('I', 'C', 'O', 'P'): + print_msg (STARTM, "%s: Copyright: ", f->filename); + break; + case H('I', 'C', 'R', 'D'): + print_msg (STARTM, "%s: Creation date: ", f->filename); + break; + case H('I', 'E', 'N', 'G'): + print_msg (STARTM, "%s: Engineer: ", f->filename); + break; + case H('I', 'G', 'N', 'R'): + print_msg (STARTM, "%s: Genre: ", f->filename); + break; + case H('I', 'K', 'E', 'Y'): + print_msg (STARTM, "%s: Keywords: ", f->filename); + break; + case H('I', 'N', 'A', 'M'): + print_msg (STARTM, "%s: Name: ", f->filename); + break; + case H('I', 'P', 'R', 'D'): + print_msg (STARTM, "%s: Product: ", f->filename); + break; + case H('I', 'S', 'B', 'J'): + print_msg (STARTM, "%s: Subject: ", f->filename); + break; + case H('I', 'S', 'F', 'T'): + print_msg (STARTM, "%s: Software: ", f->filename); + break; + case H('I', 'S', 'R', 'C'): + print_msg (STARTM, "%s: Source: ", f->filename); + break; + case H('I', 'T', 'C', 'H'): + print_msg (STARTM, "%s: Technician: ", f->filename); + break; + default: + sbuf += slen; + continue; + } + + tag = buf + slen; + /* + * According to the spec, all of the above hunks contain ZSTRs, + * so we can safely ignore the last char. + */ + for (i = 0; i < slen-1; i++) { + if (!isprint (sbuf[i])) sbuf[i] = ' '; + else tag = sbuf + i + 1; + } + /* Remove trailing nonprintables */ + *tag = '\0'; + print_msg (ENDM, "%s\n", sbuf); + sbuf += slen; + cssize += 8; + } while (cssize < len); + + return 0; +} + +static int +wave_fmt_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + unsigned int bits, i, x; + int wtype = 0x1; + + if (f->found & COMM_FOUND) { + print_msg (ERRORM, "%s: error: fmt hunk not singular!\n", + f->filename); + return E_DECODE; + } + + if (force_fmt == 0) wtype = f->format = f->ne_int (buf, 2); + if (verbose > 2) + print_msg (VERBOSEM, "FMT chunk: len = %u, fmt = %#x\n", + len, f->format); + + f->msadpcm_val.channels = f->channels = f->ne_int (buf + 2, 2); + f->speed = f->ne_int (buf + 4, 4); +#if 0 + bytes_per_sec = be_int (buf + 8, 4); /* ossplay doesn't use this */ +#endif + f->msadpcm_val.nBlockAlign = f->ne_int (buf + 12, 2); + if (f->msadpcm_val.nBlockAlign == 0) + print_msg (WARNM, "%s: nBlockAlign is 0!\n", f->filename); + f->msadpcm_val.bits = bits = f->ne_int (buf + 14, 2); + + if (wtype == 0xFFFE) { /* WAVE_FORMAT_EXTINSIBLE */ + if (len < 40) { + print_msg (ERRORM, "%s: invalid fmt chunk\n", f->filename); + return E_DECODE; + } + /* TODO: parse the rest of WAVE_FORMAT_EXTENSIBLE */ + f->format = f->ne_int (buf + 24, 2); + } + if (force_fmt == 0) switch (f->format) { + case 0x1: /* WAVE_FORMAT_PCM */ + bits += bits % 8; + if (f->type == WAVE_FILE_BE) BITS2SFORMAT (BE); + else BITS2SFORMAT (LE); + if (bits == 8) f->format = AFMT_U8; + break; + case 0x2: /* WAVE_FORMAT_MS_ADPCM */ + f->format = AFMT_MS_ADPCM; + break; + case 0x3: /* WAVE_FORMAT_IEEE_FLOAT */ + if (bits == 32) f->format = AFMT_FLOAT32_LE; + else if (bits == 64) f->format = AFMT_DOUBLE64_LE; + else { + print_msg (ERRORM, "%s: Odd number of bits (%d) for " + "WAVE_FORMAT_IEEE_FLOAT!\n", f->filename, bits); + return E_FORMAT_UNSUPPORTED; + } + break; + case 0x102: /* IBM_FORMAT_ALAW */ + case 0x6: /* WAVE_FORMAT_ALAW */ + f->format = AFMT_A_LAW; + break; + case 0x101: /* IBM_FORMAT_MULAW */ + case 0x7: /* WAVE_FORMAT_MULAW */ + f->format = AFMT_MU_LAW; + break; + case 0x11: /* WAVE_FORMAT_IMA_ADPCM */ + if (bits == 4) f->format = AFMT_MS_IMA_ADPCM; + else if (bits == 3) f->format = AFMT_MS_IMA_ADPCM_3BITS; + else { + print_msg (ERRORM, "%s: Invalid number of bits (%d) for " + "WAVE_FORMAT_IMA_ADPCM!\n", f->filename, bits); + return E_FORMAT_UNSUPPORTED; + } + break; +#if 0 + case 0x31: /* GSM 06.10 */ + case 0x50: /* MPEG */ + case 0x55: /* MPEG 3 */ +#endif + default: + print_msg (ERRORM, "%s: Unsupported wave format %#x\n", + f->filename, f->format); + return E_FORMAT_UNSUPPORTED; + } + f->found |= COMM_FOUND; + + if ((len < 20) || + ((f->format != AFMT_MS_ADPCM) && + (f->format != AFMT_MS_IMA_ADPCM) && + (f->format != AFMT_MS_IMA_ADPCM_3BITS) + ) + ) return 0; + f->msadpcm_val.wSamplesPerBlock = f->ne_int (buf + 18, 2); + if ((f->format != AFMT_MS_ADPCM) || (len < 22)) return 0; + f->msadpcm_val.wNumCoeff = f->ne_int (buf + 20, 2); + if (f->msadpcm_val.wNumCoeff > 32) f->msadpcm_val.wNumCoeff = 32; + + x = 22; + + for (i = 0; (i < f->msadpcm_val.wNumCoeff) && (x < len-3); i++) { + f->msadpcm_val.coeff[i].coeff1 = (int16) f->ne_int (buf + x, 2); + x += 2; + f->msadpcm_val.coeff[i].coeff2 = (int16) f->ne_int (buf + x, 2); + x += 2; + } + f->msadpcm_val.wNumCoeff = i; + return 0; +} + +static int +caf_init (file_t * f, unsigned char * buf) +{ + struct stat st; + + memcpy (buf, buf+8, 4); + if (from_stdin || (fstat (f->fd, &st) == -1)) { + f->total_size = BIG_SPECIAL; + return 4; + } + + f->total_size = st.st_size; + return 4; +} + +static int +caf_iterator (file_t * f, unsigned char * buf, int l) +{ + f->cur_size = f->fut_size + 12 - l; + if (f->cur_size >= f->total_size) return 1; + + if (read (f->fd, buf + l, 12 - l) < 12 - l) { + print_msg (ERRORM, "%s: Cannot read chunk header at pos %u\n", + f->filename, f->cur_size); + if ((f->found & SSND_FOUND) && (f->found & COMM_FOUND)) + return CP_STOP_READING; + return E_DECODE; + } + f->chunk_id = be_int (buf, 4); + f->chunk_size = be_int (buf + 4, 8); + f->cpos = 0; + f->fut_size += f->chunk_size + 12 - l; + + if (verbose > 3) + print_msg (VERBOSEM, "%s: Reading chunk %c%c%c%c, size %d, pos %d\n", + f->filename, buf[0], buf[1], buf[2], buf[3], f->chunk_size, + f->cur_size - 12); + + if (f->chunk_size == 0) { + print_msg (NOTIFYM, "%s: Chunk size equals 0 (pos: %u)!\n", + f->filename, f->cur_size); + if ((f->found & SSND_FOUND) && (f->found & COMM_FOUND)) + return CP_STOP_READING; + return caf_iterator (f, buf, l); + } + + return 0; +} + +static int +caf_data_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + if (f->found & SSND_FOUND) { + print_msg (ERRORM, "%s: error: SSND hunk not singular!\n", f->filename); + return E_DECODE; + } + +#if 0 + uint32 editcount = be_int (buf, 4); +#endif + f->sound_loc = f->cur_size + 4; + f->sound_size = f->chunk_size - 4; + f->found |= SSND_FOUND; + + if (verbose > 2) + print_msg (VERBOSEM, "DATA chunk. Offs = " _PRIbig_t ", len = " _PRIbig_t + "\n", f->sound_loc, f->sound_size); + if (!memcmp (buf + 4, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8)) { + if (from_stdin) f->sound_size = BIG_SPECIAL; + /* + * CAF spec requires data chunks with -1 size to be last in file, + * so we're allowed to do the calculation below. + */ + else f->sound_size = f->total_size - f->sound_loc; + return CP_PLAY_NOW; + } + + if (from_stdin) return CP_PLAY_NOW; + return 0; +} + +static int +caf_desc_parse (uint32 id, unsigned char * buf, big_t len, file_t * f) +{ + int format; + uint32 bits, bytes_per_packet, flags, frames_per_packet; + + { + /* + * Conversion from IEEE-754 extended 64-bit to double. + * We take some shortcuts which don't affect this application. + */ + int exp; + double rate = 0; + + exp = ((buf[0] & 127) << 4) + ((buf[1] & 240) >> 4) - 1023; +#if 0 + /* + * This part of the mantissa will typically be resolved to + * sub-Hz rates which we don't support anyway. We can also + * shave a few bits from buf[3] (buf[3] & 224 will do) and + * still get the same effect. + */ + rate = (buf[4] << 24) + (buf[5] << 16) + (buf[6] << 8) + + buf[7]; + rate /= 1L << 32; +#endif + rate += ((buf[1] & 15) << 16) + (buf[2] << 8) + buf[3]; + rate = ossplay_ldexpl (rate, exp-20); + if (exp != -1023) + rate += ossplay_ldexpl (1, exp); /* Normalize */ + if (buf[0] & 128) rate = -rate; /* Sign bit */ + if ((exp == 1024) || (rate <= 0)) { + print_msg (ERRORM, "%s: Invalid sample rate!\n", f->filename); + return E_DECODE; + } + f->speed = rate; + } + + format = be_int (buf + 8, 4); + flags = be_int (buf + 12, 4); + bytes_per_packet = be_int (buf + 16, 4); + frames_per_packet = be_int (buf + 20, 4); + f->channels = be_int (buf + 24, 4); + bits = be_int (buf + 28, 4); + f->found |= COMM_FOUND; + if (force_fmt != 0) return 0; + +#define FLCHECK(fmt) do { \ + f->format = (flags & 2)?AFMT_##fmt##_LE:AFMT_##fmt##_BE; \ +} while(0) + + f->format = 0; + switch (format) { + case H('l', 'p', 'c', 'm'): + if ((flags & 1) && (bits != 32) && (bits != 64)) break; + switch (bits) { + case 8: + if (bytes_per_packet == f->channels) f->format = AFMT_S8; + break; + case 16: + if (bytes_per_packet == 2*f->channels) FLCHECK (S16); + break; + case 24: + if (bytes_per_packet == 3*f->channels) FLCHECK (S24_PACKED); + case 32: + if (bytes_per_packet == 4*f->channels) { + if (flags & 1) FLCHECK(FLOAT32); + else FLCHECK (S32); + } + break; + case 64: + if (flags & 1) FLCHECK (DOUBLE64); + default: break; + } + break; + case H('a', 'l', 'a', 'w'): f->format = AFMT_A_LAW; break; + case H('u', 'l', 'a', 'w'): f->format = AFMT_MU_LAW; break; + case H('i', 'm', 'a', '4'): f->format = AFMT_MAC_IMA_ADPCM; break; + default: break; + } + if (f->format == 0) { + print_msg (ERRORM, "%s: \"%c%c%c%c\" format (bits %d, bytes per " + "%d, flags 0x%X) is not supported\n", f->filename, buf[8], + buf[9], buf[10], buf[11], bits, bytes_per_packet, flags); + return E_FORMAT_UNSUPPORTED; + } + return 0; +} + +static int +w64_init (file_t * f, unsigned char * buf) +{ + struct stat st; + + f->ne_int = le_int; + f->format = AFMT_S16_LE; + if (read (f->fd, buf, 8) < 8) return -1; + f->total_size = le_int (buf, 8); + f->fut_size = 40; + ossplay_lseek (f->fd, 16, SEEK_CUR); + + if (from_stdin) return 0; + if (fstat (f->fd, &st) == -1) return 0; + if (st.st_size < f->total_size) { + f->total_size = st.st_size; + print_msg (NOTIFYM, "%s: File size is smaller than the form size!\n", + f->filename); + } + return 0; +} + +static int +w64_iterator (file_t * f, unsigned char * buf, int l) +{ + f->cur_size = f->fut_size + 24; + if (f->cur_size >= f->total_size) return 1; + + if (read (f->fd, buf, 24) < 24) { + print_msg (ERRORM, "%s: Cannot read chunk header at pos %u\n", + f->filename, f->cur_size); + if ((f->found & SSND_FOUND) && (f->found & COMM_FOUND)) + return CP_STOP_READING; + return E_DECODE; + } + + /* Only WAVE chunks are supported, so we can ignore the rest of the GUID */ + f->chunk_id = be_int (buf, 4); + f->chunk_size = f->ne_int (buf + 16, 8); + f->cpos = 0; + f->fut_size += f->chunk_size; + f->chunk_size -= 24; + + if (verbose > 3) + print_msg (VERBOSEM, "%s: Reading chunk %c%c%c%c, size " _PRIbig_t + ", pos %d\n", f->filename, buf[0], buf[1], buf[2], buf[3], + f->chunk_size - 24, f->cur_size); + + if (f->chunk_size == 0) { + print_msg (NOTIFYM, "%s: Chunk size equals 0 (pos: %u)!\n", + f->filename, f->cur_size); + if ((f->found & SSND_FOUND) && (f->found & COMM_FOUND)) + return CP_STOP_READING; + return w64_iterator (f, buf, l); + } + + return 0; +} diff --git a/cmd/ossplay/ossplay_parser.h b/cmd/ossplay/ossplay_parser.h new file mode 100644 index 0000000..f42d29d --- /dev/null +++ b/cmd/ossplay/ossplay_parser.h @@ -0,0 +1,8 @@ +#ifndef OSSPLAY_PARSER_H +#define OSSPLAY_PARSER_H + +#include "ossplay.h" + +errors_t play_file (dspdev_t *, const char *, dlopen_funcs_t **); + +#endif diff --git a/cmd/ossplay/ossplay_wparser.c b/cmd/ossplay/ossplay_wparser.c new file mode 100644 index 0000000..98cb4b5 --- /dev/null +++ b/cmd/ossplay/ossplay_wparser.c @@ -0,0 +1,358 @@ +/* + * Purpose: File header write routines for OSSRecord. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#include <soundcard.h> +#include <errno.h> +#include "ossplay_wparser.h" + +#pragma pack(1) + +typedef struct { + char main_chunk[4]; + uint32 length; + char chunk_type[4]; + + char sub_chunk[4]; + uint32 sc_len; + uint16 format; + uint16 modus; + uint32 sample_fq; + uint32 byte_p_sec; + uint16 block_align; + uint16 bit_p_spl; + + char data_chunk[4]; + uint32 data_length; +} +WaveHeader; + +typedef struct { + char magic[4]; + uint32 offset; + uint32 filelen; + uint32 fmt; + uint32 speed; + uint32 channels; + /* + * Some old specs say the field below is optional. Others say that this field + * is mandatory, and must be at least 4 bytes long (SoX prints out a warning). + * We'll put a nice "Made by OSSRecord" comment and shut SoX up. + */ + char comment[18]; +} +AuHeader; + +typedef struct { + char main_chunk[4]; + uint32 length; + char chunk_type[4]; + + char sub_chunk[4]; + uint32 comm_len; + uint16 channels; + uint32 num_frames; + uint16 bits; + unsigned char speed[10]; + char data_chunk[4]; + uint32 data_length; + uint32 offset; + uint32 blocksize; +} +AiffHeader; + +typedef struct { + char magic[4]; + uint16 version; + uint16 flags; + + char desc_chunk[4]; + uint32 desc_length_hi, desc_length_lo; + unsigned char speed[8]; + char format[4]; + uint32 format_flags; + uint32 bytes_per_packet; + uint32 frames_per_packet; + uint32 channels; + uint32 bits; + + char data_chunk[4]; + uint32 length_hi, length_lo; + uint32 edit_count; +} +CafHeader; + +#pragma pack() + +extern FILE *wave_fp; + +static uint32 +bswap (uint32 x) +{ + uint32 y = 0; + unsigned char *a = ((unsigned char *) &x) + 3; + unsigned char *b = (unsigned char *) &y; + + *b++ = *a--; + *b++ = *a--; + *b++ = *a--; + *b = *a; + + return y; +} + +static uint16 +bswaps (uint16 x) +{ + uint16 y = 0; + unsigned char *a = ((unsigned char *) &x) + 1; + unsigned char *b = (unsigned char *) &y; + + *b++ = *a--; + *b = *a; + + return y; +} + +int +write_head (FILE * wave_fp, fctypes_t type, big_t datalimit, + int format, int channels, int speed) +{ + uint32 dl = datalimit; + + if ((speed <= 0) || (channels <= 0) || (format <= 0)) return -1; + + if (datalimit > U32_MAX) dl = U32_MAX; + + switch (type) + { + case WAVE_FILE: { + WaveHeader wh; + int bits = format2bits (format); + + if (datalimit > U32_MAX - sizeof (WaveHeader)) + print_msg (WARNM, "Data size exceeds file format limit!\n"); + if ((datalimit == 0) || (datalimit > U32_MAX - sizeof (WaveHeader))) + dl = U32_MAX - (U32_MAX % 2) - sizeof (WaveHeader); + switch (format) { + case AFMT_U8: + case AFMT_S16_LE: + case AFMT_S24_PACKED: + case AFMT_S32_LE: + wh.format = LE_SH (1); + break; + case AFMT_A_LAW: + wh.format = LE_SH (6); + break; + case AFMT_MU_LAW: + wh.format = LE_SH (7); + break; + default: + print_msg (ERRORM, + "%s sample format not supported by WAV writer!\n", + sample_format_name (format)); + return E_FORMAT_UNSUPPORTED; + } + + memcpy ((char *) &wh.main_chunk, "RIFF", 4); + wh.length = LE_INT (dl + sizeof (WaveHeader) - 8); + if (dl % 2) dl--; + memcpy ((char *) &wh.chunk_type, "WAVE", 4); + memcpy ((char *) &wh.sub_chunk, "fmt ", 4); + wh.sc_len = LE_INT (16); + wh.modus = LE_SH (channels); + wh.sample_fq = LE_INT (speed); + wh.block_align = LE_SH (channels * bits / 8); + wh.byte_p_sec = LE_INT (speed * channels * bits / 8); + wh.bit_p_spl = LE_SH (bits); + memcpy ((char *) &wh.data_chunk, "data", 4); + wh.data_length = LE_INT (dl); + if (fwrite (&wh, sizeof (WaveHeader), 1, wave_fp) == 0) return E_ENCODE; + } break; + case AU_FILE: { + AuHeader ah; + + if ((datalimit == 0) || (datalimit > U32_MAX)) dl = 0xffffffff; + memcpy ((char *) &ah.magic, ".snd", 4); + ah.offset = BE_INT (sizeof (AuHeader)); + ah.filelen = BE_INT (dl); + switch (format) + { + case AFMT_MU_LAW: ah.fmt = BE_INT (1); break; + case AFMT_S8: ah.fmt = BE_INT (2); break; + case AFMT_S16_BE: ah.fmt = BE_INT (3); break; + case AFMT_S24_PACKED_BE: ah.fmt = BE_INT (4); break; + case AFMT_S32_BE: ah.fmt = BE_INT (5); break; + case AFMT_A_LAW: ah.fmt = BE_INT (27); break; + default: + print_msg (ERRORM, + "%s sample format not supported by AU writer!\n", + sample_format_name (format)); + return E_FORMAT_UNSUPPORTED; + } + ah.speed = BE_INT (speed); + ah.channels = BE_INT (channels); + memcpy ((char *) &ah.comment, "Made by OSSRecord", 18); + if (fwrite (&ah, sizeof (AuHeader), 1, wave_fp) == 0) return -1; + } break; + case CAF_FILE: { + CafHeader cfh; + int bits = format2bits (format); + uint32 i; + + memcpy ((char *)&cfh.magic, "caff", 4); + cfh.version = BE_SH(1); + cfh.flags = 0; + + memcpy ((char *)&cfh.desc_chunk, "desc", 4); + cfh.desc_length_hi = 0; + cfh.desc_length_lo = BE_INT (32); + cfh.format_flags = 0; + switch (format) { + case AFMT_S16_LE: + case AFMT_S24_PACKED_LE: + case AFMT_S32_LE: cfh.format_flags = BE_INT (2); + case AFMT_S8: + case AFMT_S16_BE: + case AFMT_S24_PACKED_BE: + case AFMT_S32_BE: + memcpy ((char *)&cfh.format, "lpcm", 4); + break; + case AFMT_MU_LAW: + memcpy ((char *)&cfh.format, "ulaw", 4); + break; + case AFMT_A_LAW: + memcpy ((char *)&cfh.format, "alaw", 4); + break; + default: + print_msg (ERRORM, + "%s sample format not supported by CAF writer!\n", + sample_format_name (format)); + return E_FORMAT_UNSUPPORTED; + } + cfh.frames_per_packet = BE_INT (1); + cfh.bytes_per_packet = BE_INT (bits / 8 * channels); + cfh.channels = BE_INT (channels); + cfh.bits = BE_INT (bits); + + /* The method used here is good enough for sane values */ + memset (cfh.speed, 0, sizeof (cfh.speed)); + i = 0; + while ((1L << (i + 1)) <= speed) i++; + cfh.speed[0] = 64; cfh.speed[1] = (i-1) << 4; + i = (speed - (1 << i)) << (32-i); + cfh.speed[5] = i & 240; + cfh.speed[4] = (i >> 4) & 255; + cfh.speed[3] = (i >> 12) & 255; + cfh.speed[2] = (i >> 20) & 255; + cfh.speed[1] |= (i >> 28) & 15; + + memcpy ((char *)&cfh.data_chunk, "data", 4); + if ((datalimit == 0) || (datalimit > U32_MAX)) { + cfh.length_lo = 0xffffffff; + cfh.length_hi = 0xffffffff; + } else { + cfh.length_lo = BE_INT (datalimit); + cfh.length_hi = 0; + } + cfh.edit_count = 0; + if (fwrite (&cfh, sizeof (CafHeader), 1, wave_fp) == 0) + return E_ENCODE; + } break; + case AIFF_FILE: { + AiffHeader afh; + int bits = format2bits (format); + uint32 i; + + if (datalimit > U32_MAX - sizeof (AiffHeader)) + print_msg (WARNM, "Data size exceeds file format limit!\n"); + if ((datalimit == 0) || (datalimit > U32_MAX - sizeof (AiffHeader))) + dl = U32_MAX - (U32_MAX % 2) - sizeof (AiffHeader); + memcpy ((char *) &afh.main_chunk, "FORM", 4); + afh.length = BE_INT (dl + sizeof (AiffHeader) - 8); + if (dl % 2) dl--; + memcpy ((char *) &afh.chunk_type, "AIFF", 4); + memcpy ((char *) &afh.sub_chunk, "COMM", 4); + afh.comm_len = BE_INT (18); + afh.channels = BE_SH (channels); + afh.num_frames = BE_INT (dl / bits); + afh.bits = BE_SH (bits); + switch (format) + { + case AFMT_S8: + case AFMT_S16_BE: + case AFMT_S24_PACKED_BE: + case AFMT_S32_BE: + break; + default: + print_msg (ERRORM, + "%s sample format not supported by AIFF writer!\n", + sample_format_name (format)); + return E_FORMAT_UNSUPPORTED; + } + + /* The method used here is good enough for sane values */ + memset (afh.speed, 0, sizeof (afh.speed)); + i = 0; + while ((1L << (i + 1)) <= speed) i++; + afh.speed[0] = 64; afh.speed[1] = i-1; + i = (speed - (1 << i)) << (31-i); + afh.speed[5] = i & 255; + afh.speed[4] = (i >> 8) & 255; + afh.speed[3] = (i >> 16) & 255; + afh.speed[2] = ((i >> 24) & 127) + 128; + + memcpy ((char *) &afh.data_chunk, "SSND", 4); + afh.data_length = BE_INT (dl); + afh.offset = BE_SH (0); + afh.blocksize = BE_SH (0); + if (fwrite (&afh, sizeof (AiffHeader), 1, wave_fp) == 0) + return E_ENCODE; + } break; + case RAW_FILE: + default: return 0; + } + return 0; +} + +int +finalize_head (FILE * wave_fp, fctypes_t type, big_t datalimit, + int format, int channels, int speed) +{ + if ((IS_IFF_FILE (type)) && (datalimit % 2)) + { + /* + * All chunks must have an even length in an IFF file, + * so we have to add a pad byte in this case. + * Since we always write the data chunk last, we can + * just append it to end of file. + */ + char flag = 0; + + fseek (wave_fp, 0, SEEK_END); + if (fwrite (&flag , 1, 1, wave_fp) == 0) + print_msg (WARNM, "Couldn't add padding byte to SSND chunk!\n"); + } + if ((type != RAW_FILE) && (fseek (wave_fp, 0, SEEK_SET) == -1)) return E_ENCODE; + write_head (wave_fp, type, datalimit, format, channels, speed); + return 0; +} diff --git a/cmd/ossplay/ossplay_wparser.h b/cmd/ossplay/ossplay_wparser.h new file mode 100644 index 0000000..ae3e037 --- /dev/null +++ b/cmd/ossplay/ossplay_wparser.h @@ -0,0 +1,21 @@ +#ifndef _OSSRECORD_WPARSER_H +#define _OSSRECORD_WPARSER_H + +#include "ossplay.h" + +int write_head (FILE *, fctypes_t, big_t, int, int, int); +int finalize_head (FILE *, fctypes_t, big_t, int, int, int); + +#ifdef OSS_BIG_ENDIAN +#define BE_INT(x) x +#define BE_SH(x) x +#define LE_INT(x) bswap(x) +#define LE_SH(x) bswaps(x) +#else +#define BE_INT(x) bswap(x) +#define BE_SH(x) bswaps(x) +#define LE_INT(x) x +#define LE_SH(x) x +#endif /* OSS_BIG_ENDIAN */ + +#endif |