/* * madplay - MPEG audio decoder and player * Copyright (C) 2000-2004 Robert Leslie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: audio.c,v 1.36 2004/01/23 09:41:31 rob Exp $ */ # ifdef HAVE_CONFIG_H # include "config.h" # endif # include "global.h" # include # include # include "audio.h" char const *audio_error; static struct audio_dither left_dither, right_dither; # if defined(_MSC_VER) # pragma warning(disable: 4550) /* expression evaluates to a function which is missing an argument list */ # endif /* * NAME: audio_output() * DESCRIPTION: choose an audio output module from a specifier pathname */ audio_ctlfunc_t *audio_output(char const **path) { char const *ext; int i; struct map { char const *name; audio_ctlfunc_t *module; }; struct map const prefixes[] = { { "cdda", audio_cdda }, { "aiff", audio_aiff }, { "wave", audio_wave }, { "wav", audio_wave }, { "snd", audio_snd }, { "au", audio_snd }, { "raw", audio_raw }, { "pcm", audio_raw }, { "hex", audio_hex }, # if defined(HAVE_LIBESD) { "esd", audio_esd }, # endif # if defined(HAVE_LIBAUDIO) { "nas", audio_nas }, # endif { "null", audio_null }, { "nul", audio_null } }; struct map const extensions[] = { { "cdr", audio_cdda }, { "cda", audio_cdda }, { "cdda", audio_cdda }, { "aif", audio_aiff }, { "aiff", audio_aiff }, { "wav", audio_wave }, { "snd", audio_snd }, { "au", audio_snd }, { "raw", audio_raw }, { "pcm", audio_raw }, { "out", audio_raw }, { "bin", audio_raw }, { "hex", audio_hex }, { "txt", audio_hex } }; if (path == 0) return AUDIO_DEFAULT; /* check for prefix specifier */ ext = strchr(*path, ':'); if (ext) { char const *type; type = *path; *path = ext + 1; for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); ++i) { if (strncasecmp(type, prefixes[i].name, ext - type) == 0 && strlen(prefixes[i].name) == ext - type) return prefixes[i].module; } *path = type; return 0; } if (strcmp(*path, "/dev/null") == 0) return audio_null; if (strncmp(*path, "/dev/", 5) == 0) return AUDIO_DEFAULT; /* check for file extension specifier */ ext = strrchr(*path, '.'); if (ext) { ++ext; for (i = 0; i < sizeof(extensions) / sizeof(extensions[0]); ++i) { if (strcasecmp(ext, extensions[i].name) == 0) return extensions[i].module; } } return 0; } /* * NAME: audio_control_init() * DESCRIPTION: initialize an audio control structure */ void audio_control_init(union audio_control *control, enum audio_command command) { switch (control->command = command) { case AUDIO_COMMAND_INIT: control->init.path = 0; break; case AUDIO_COMMAND_CONFIG: control->config.channels = 0; control->config.speed = 0; control->config.precision = 0; break; case AUDIO_COMMAND_PLAY: control->play.nsamples = 0; control->play.samples[0] = 0; control->play.samples[1] = 0; control->play.mode = AUDIO_MODE_DITHER; control->play.stats = 0; break; case AUDIO_COMMAND_STOP: control->stop.flush = 0; break; case AUDIO_COMMAND_FINISH: break; } } /* * NAME: clip() * DESCRIPTION: gather signal statistics while clipping */ static inline void clip(mad_fixed_t *sample, struct audio_stats *stats) { enum { MIN = -MAD_F_ONE, MAX = MAD_F_ONE - 1 }; if (*sample >= stats->peak_sample) { if (*sample > MAX) { ++stats->clipped_samples; if (*sample - MAX > stats->peak_clipping) stats->peak_clipping = *sample - MAX; *sample = MAX; } stats->peak_sample = *sample; } else if (*sample < -stats->peak_sample) { if (*sample < MIN) { ++stats->clipped_samples; if (MIN - *sample > stats->peak_clipping) stats->peak_clipping = MIN - *sample; *sample = MIN; } stats->peak_sample = -*sample; } } /* * NAME: audio_linear_round() * DESCRIPTION: generic linear sample quantize routine */ # if defined(_MSC_VER) extern /* needed to satisfy bizarre MSVC++ interaction with inline */ # endif inline signed long audio_linear_round(unsigned int bits, mad_fixed_t sample, struct audio_stats *stats) { /* round */ sample += (1L << (MAD_F_FRACBITS - bits)); /* clip */ clip(&sample, stats); /* quantize and scale */ return sample >> (MAD_F_FRACBITS + 1 - bits); } /* * NAME: prng() * DESCRIPTION: 32-bit pseudo-random number generator */ static inline unsigned long prng(unsigned long state) { return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; } /* * NAME: audio_linear_dither() * DESCRIPTION: generic linear sample quantize and dither routine */ # if defined(_MSC_VER) extern /* needed to satisfy bizarre MSVC++ interaction with inline */ # endif inline signed long audio_linear_dither(unsigned int bits, mad_fixed_t sample, struct audio_dither *dither, struct audio_stats *stats) { unsigned int scalebits; mad_fixed_t output, mask, random; enum { MIN = -MAD_F_ONE, MAX = MAD_F_ONE - 1 }; /* noise shape */ sample += dither->error[0] - dither->error[1] + dither->error[2]; dither->error[2] = dither->error[1]; dither->error[1] = dither->error[0] / 2; /* bias */ output = sample + (1L << (MAD_F_FRACBITS + 1 - bits - 1)); scalebits = MAD_F_FRACBITS + 1 - bits; mask = (1L << scalebits) - 1; /* dither */ random = prng(dither->random); output += (random & mask) - (dither->random & mask); dither->random = random; /* clip */ if (output >= stats->peak_sample) { if (output > MAX) { ++stats->clipped_samples; if (output - MAX > stats->peak_clipping) stats->peak_clipping = output - MAX; output = MAX; if (sample > MAX) sample = MAX; } stats->peak_sample = output; } else if (output < -stats->peak_sample) { if (output < MIN) { ++stats->clipped_samples; if (MIN - output > stats->peak_clipping) stats->peak_clipping = MIN - output; output = MIN; if (sample < MIN) sample = MIN; } stats->peak_sample = -output; } /* quantize */ output &= ~mask; /* error feedback */ dither->error[0] = sample - output; /* scale */ return output >> scalebits; } /* * NAME: audio_pcm_u8() * DESCRIPTION: write a block of unsigned 8-bit PCM samples */ unsigned int audio_pcm_u8(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { data[0] = audio_linear_round(8, *left++, stats) ^ 0x80; data[1] = audio_linear_round(8, *right++, stats) ^ 0x80; data += 2; } break; case AUDIO_MODE_DITHER: while (len--) { data[0] = audio_linear_dither(8, *left++, &left_dither, stats) ^ 0x80; data[1] = audio_linear_dither(8, *right++, &right_dither, stats) ^ 0x80; data += 2; } break; default: return 0; } return nsamples * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) *data++ = audio_linear_round(8, *left++, stats) ^ 0x80; break; case AUDIO_MODE_DITHER: while (len--) *data++ = audio_linear_dither(8, *left++, &left_dither, stats) ^ 0x80; break; default: return 0; } return nsamples; } } /* * NAME: audio_pcm_s8() * DESCRIPTION: write a block of signed 8-bit PCM samples */ unsigned int audio_pcm_s8(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { data[0] = audio_linear_round(8, *left++, stats); data[1] = audio_linear_round(8, *right++, stats); data += 2; } break; case AUDIO_MODE_DITHER: while (len--) { data[0] = audio_linear_dither(8, *left++, &left_dither, stats); data[1] = audio_linear_dither(8, *right++, &right_dither, stats); data += 2; } break; default: return 0; } return nsamples * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) *data++ = audio_linear_round(8, *left++, stats); break; case AUDIO_MODE_DITHER: while (len--) *data++ = audio_linear_dither(8, *left++, &left_dither, stats); break; default: return 0; } return nsamples; } } /* * NAME: audio_pcm_s16le() * DESCRIPTION: write a block of signed 16-bit little-endian PCM samples */ unsigned int audio_pcm_s16le(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; register signed int sample0, sample1; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(16, *left++, stats); sample1 = audio_linear_round(16, *right++, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data[2] = sample1 >> 0; data[3] = sample1 >> 8; data += 4; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(16, *left++, &left_dither, stats); sample1 = audio_linear_dither(16, *right++, &right_dither, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data[2] = sample1 >> 0; data[3] = sample1 >> 8; data += 4; } break; default: return 0; } return nsamples * 2 * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(16, *left++, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data += 2; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(16, *left++, &left_dither, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data += 2; } break; default: return 0; } return nsamples * 2; } } /* * NAME: audio_pcm_s16be() * DESCRIPTION: write a block of signed 16-bit big-endian PCM samples */ unsigned int audio_pcm_s16be(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; register signed int sample0, sample1; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(16, *left++, stats); sample1 = audio_linear_round(16, *right++, stats); data[0] = sample0 >> 8; data[1] = sample0 >> 0; data[2] = sample1 >> 8; data[3] = sample1 >> 0; data += 4; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(16, *left++, &left_dither, stats); sample1 = audio_linear_dither(16, *right++, &right_dither, stats); data[0] = sample0 >> 8; data[1] = sample0 >> 0; data[2] = sample1 >> 8; data[3] = sample1 >> 0; data += 4; } break; default: return 0; } return nsamples * 2 * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(16, *left++, stats); data[0] = sample0 >> 8; data[1] = sample0 >> 0; data += 2; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(16, *left++, &left_dither, stats); data[0] = sample0 >> 8; data[1] = sample0 >> 0; data += 2; } break; default: return 0; } return nsamples * 2; } } /* * NAME: audio_pcm_s24le() * DESCRIPTION: write a block of signed 24-bit little-endian PCM samples */ unsigned int audio_pcm_s24le(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; register signed long sample0, sample1; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); sample1 = audio_linear_round(24, *right++, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data[2] = sample0 >> 16; data[3] = sample1 >> 0; data[4] = sample1 >> 8; data[5] = sample1 >> 16; data += 6; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(24, *left++, &left_dither, stats); sample1 = audio_linear_dither(24, *right++, &right_dither, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data[2] = sample0 >> 16; data[3] = sample1 >> 0; data[4] = sample1 >> 8; data[5] = sample1 >> 16; data += 6; } break; default: return 0; } return nsamples * 3 * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data[2] = sample0 >> 16; data += 3; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(24, *left++, &left_dither, stats); data[0] = sample0 >> 0; data[1] = sample0 >> 8; data[2] = sample0 >> 16; data += 3; } break; default: return 0; } return nsamples * 3; } } /* * NAME: audio_pcm_s24be() * DESCRIPTION: write a block of signed 24-bit big-endian PCM samples */ unsigned int audio_pcm_s24be(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; register signed long sample0, sample1; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); sample1 = audio_linear_round(24, *right++, stats); data[0] = sample0 >> 16; data[1] = sample0 >> 8; data[2] = sample0 >> 0; data[3] = sample1 >> 16; data[4] = sample1 >> 8; data[5] = sample1 >> 0; data += 6; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(24, *left++, &left_dither, stats); sample1 = audio_linear_dither(24, *right++, &right_dither, stats); data[0] = sample0 >> 16; data[1] = sample0 >> 8; data[2] = sample0 >> 0; data[3] = sample1 >> 16; data[4] = sample1 >> 8; data[5] = sample1 >> 0; data += 6; } break; default: return 0; } return nsamples * 3 * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); data[0] = sample0 >> 16; data[1] = sample0 >> 8; data[2] = sample0 >> 0; data += 3; } break; case AUDIO_MODE_DITHER: while (len--) { sample1 = audio_linear_dither(24, *left++, &left_dither, stats); data[0] = sample1 >> 16; data[1] = sample1 >> 8; data[2] = sample1 >> 0; data += 3; } break; default: return 0; } return nsamples * 3; } } /* * NAME: audio_pcm_s32le() * DESCRIPTION: write a block of signed 32-bit little-endian PCM samples */ unsigned int audio_pcm_s32le(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; register signed long sample0, sample1; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); sample1 = audio_linear_round(24, *right++, stats); data[0] = 0; data[1] = sample0 >> 0; data[2] = sample0 >> 8; data[3] = sample0 >> 16; data[4] = 0; data[5] = sample1 >> 0; data[6] = sample1 >> 8; data[7] = sample1 >> 16; data += 8; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(24, *left++, &left_dither, stats); sample1 = audio_linear_dither(24, *right++, &right_dither, stats); data[0] = 0; data[1] = sample0 >> 0; data[2] = sample0 >> 8; data[3] = sample0 >> 16; data[4] = 0; data[5] = sample1 >> 0; data[6] = sample1 >> 8; data[7] = sample1 >> 16; data += 8; } break; default: return 0; } return nsamples * 4 * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); data[0] = 0; data[1] = sample0 >> 0; data[2] = sample0 >> 8; data[3] = sample0 >> 16; data += 4; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(24, *left++, &left_dither, stats); data[0] = 0; data[1] = sample0 >> 0; data[2] = sample0 >> 8; data[3] = sample0 >> 16; data += 4; } break; default: return 0; } return nsamples * 4; } } /* * NAME: audio_pcm_s32be() * DESCRIPTION: write a block of signed 32-bit big-endian PCM samples */ unsigned int audio_pcm_s32be(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; register signed long sample0, sample1; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); sample1 = audio_linear_round(24, *right++, stats); data[0] = sample0 >> 16; data[1] = sample0 >> 8; data[2] = sample0 >> 0; data[3] = 0; data[4] = sample1 >> 16; data[5] = sample1 >> 8; data[6] = sample1 >> 0; data[7] = 0; data += 8; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(24, *left++, &left_dither, stats); sample1 = audio_linear_dither(24, *right++, &right_dither, stats); data[0] = sample0 >> 16; data[1] = sample0 >> 8; data[2] = sample0 >> 0; data[3] = 0; data[4] = sample1 >> 16; data[5] = sample1 >> 8; data[6] = sample1 >> 0; data[7] = 0; data += 8; } break; default: return 0; } return nsamples * 4 * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { sample0 = audio_linear_round(24, *left++, stats); data[0] = sample0 >> 16; data[1] = sample0 >> 8; data[2] = sample0 >> 0; data[3] = 0; data += 4; } break; case AUDIO_MODE_DITHER: while (len--) { sample0 = audio_linear_dither(24, *left++, &left_dither, stats); data[0] = sample0 >> 16; data[1] = sample0 >> 8; data[2] = sample0 >> 0; data[3] = 0; data += 4; } break; default: return 0; } return nsamples * 4; } } static unsigned char linear2mulaw(mad_fixed_t sample) { unsigned int sign, mulaw; enum { BIAS = (mad_fixed_t) ((0x10 << 1) + 1) << (MAD_F_FRACBITS - 13) }; if (sample < 0) { sample = BIAS - sample; sign = 0x7f; } else { sample = BIAS + sample; sign = 0xff; } mulaw = 0x7f; if (sample < MAD_F_ONE) { unsigned int segment; unsigned long mask; segment = 7; for (mask = 1L << (MAD_F_FRACBITS - 1); !(sample & mask); mask >>= 1) --segment; mulaw = ((segment << 4) | ((sample >> (MAD_F_FRACBITS - 1 - (7 - segment) - 4)) & 0x0f)); } mulaw ^= sign; # if 0 if (mulaw == 0x00) mulaw = 0x02; # endif return mulaw; } static mad_fixed_t mulaw2linear(unsigned char mulaw) { int sign, segment, mantissa, value; enum { BIAS = (0x10 << 1) + 1 }; mulaw = ~mulaw; sign = (mulaw >> 7) & 0x01; segment = (mulaw >> 4) & 0x07; mantissa = (mulaw >> 0) & 0x0f; value = ((0x21 | (mantissa << 1)) << segment) - BIAS; if (sign) value = -value; return (mad_fixed_t) value << (MAD_F_FRACBITS - 13); } /* * NAME: audio_mulaw_round() * DESCRIPTION: convert a linear PCM value to 8-bit ISDN mu-law */ inline unsigned char audio_mulaw_round(mad_fixed_t sample, struct audio_stats *stats) { clip(&sample, stats); return linear2mulaw(sample); } /* * NAME: audio_mulaw_dither() * DESCRIPTION: convert a linear PCM value to dithered 8-bit ISDN mu-law */ inline unsigned char audio_mulaw_dither(mad_fixed_t sample, struct audio_dither *dither, struct audio_stats *stats) { unsigned char mulaw; /* noise shape */ sample += dither->error[0]; clip(&sample, stats); mulaw = linear2mulaw(sample); /* error feedback */ dither->error[0] = sample - mulaw2linear(mulaw); return mulaw; } /* * NAME: audio_pcm_mulaw() * DESCRIPTION: write a block of 8-bit mu-law encoded samples */ unsigned int audio_pcm_mulaw(unsigned char *data, unsigned int nsamples, mad_fixed_t const *left, mad_fixed_t const *right, enum audio_mode mode, struct audio_stats *stats) { unsigned int len; len = nsamples; if (right) { /* stereo */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) { data[0] = audio_mulaw_round(*left++, stats); data[1] = audio_mulaw_round(*right++, stats); data += 2; } break; case AUDIO_MODE_DITHER: while (len--) { data[0] = audio_mulaw_dither(*left++, &left_dither, stats); data[1] = audio_mulaw_dither(*right++, &right_dither, stats); data += 2; } break; default: return 0; } return nsamples * 2; } else { /* mono */ switch (mode) { case AUDIO_MODE_ROUND: while (len--) *data++ = audio_mulaw_round(*left++, stats); break; case AUDIO_MODE_DITHER: while (len--) *data++ = audio_mulaw_dither(*left++, &left_dither, stats); break; default: return 0; } return nsamples; } }