/*-
* Copyright (c) 2008 Ariff Abdullah <ariff@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <assert.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#ifdef SND_FEEDER_FULL_MULTIFORMAT
#undef SND_FEEDER_MULTIFORMAT
#define SND_FEEDER_MULTIFORMAT 1
#endif
#include "version.h"
#include "oss_compat.h"
#include "pcm.h"
#include "matrix.h"
#include "matrix_map.h"
#include "g711.h"
#include "intpcm.h"
#include "waveutil.h"
#define SND_USE_FXDIV
#include "snd_fxdiv_gen.h"
#include "feeder_compat.h"
#include "feeder_rate.c"
#include "feeder_stdio.c"
#include "feeder_format.c"
#include "feeder_matrix.c"
#include "feeder_volume.c"
#include "feeder_eq.c"
#if !defined(AFMT_S_NE)
#if defined(Z_16) || (defined(Z_SPEEX) && !defined(FLOATING_POINT)) || \
defined(Z_CG) || defined(Z_ORION)
#define AFMT_S_NE AFMT_S16_NE
#else
#define AFMT_S_NE AFMT_S32_NE
#endif
#endif
#ifndef Z_POLYPHASE_MAX
#define Z_POLYPHASE_MAX 0
static int feeder_rate_polyphase_max = 0;
#endif
enum {
FEEDER_IO_TYPE_UNKNOWN,
FEEDER_IO_TYPE_RAW,
FEEDER_IO_TYPE_WAVE,
FEEDER_IO_TYPE_DSP
};
struct feeder_global_opts {
uint32_t bufsize;
uint32_t volume;
uint32_t afmt_ne;
int32_t eqpreamp;
int quality;
int matrix_swap;
int verbose;
int eqtreble, eqbass;
int bit_perfect;
int direction;
int expensive;
};
struct feeder_io_opts {
struct wave_info waveinfo;
uint32_t afmt;
int type, running, exclusive, latency;
char *file;
};
struct feeder_chain_desc {
struct {
uint32_t afmt;
uint32_t channels;
uint32_t rate;
uint32_t maxsize;
int quality;
} current;
struct {
uint32_t afmt;
uint32_t channels;
uint32_t rate;
uint32_t maxsize;
int quality;
} target;
struct {
int use;
int treble, bass;
int preamp;
} eq;
struct pcm_feederdesc desc;
struct pcm_channel *channel;
struct pcmchan_matrix m_in, m_out;
uint32_t matrix_swap;
uint32_t afmt_ne;
int use_volume;
int expensive;
};
static const struct {
uint32_t afmt;
const char *sfmt, *alias;
int prefered;
} afmt_tab[] = {
{ AFMT_S8, "s8", NULL, 0 },
{ AFMT_S16_LE, "s16le", "16", 1 },
{ AFMT_S24_LE, "s24le", "24", 1 },
{ AFMT_S32_LE, "s32le", "32", 1 },
{ AFMT_S16_BE, "s16be", NULL, 0 },
{ AFMT_S24_BE, "s24be", NULL, 0 },
{ AFMT_S32_BE, "s32be", NULL, 0 },
{ AFMT_U8, "u8", "8", 1 },
{ AFMT_U16_LE, "u16le", NULL, 0 },
{ AFMT_U24_LE, "u24le", NULL, 0 },
{ AFMT_U32_LE, "u32le", NULL, 0 },
{ AFMT_U16_BE, "u16be", NULL, 0 },
{ AFMT_U24_BE, "u24be", NULL, 0 },
{ AFMT_U32_BE, "u32be", NULL, 0 },
{ AFMT_A_LAW, "alaw", NULL, 0 },
{ AFMT_MU_LAW, "mulaw", "ulaw", 0 },
{ AFMT_AC3, "ac3", NULL, 0 },
};
#define AFMT_TAB_SIZE ((int)(sizeof(afmt_tab) / sizeof(afmt_tab[0])))
static void
sfmt_swap_sign(char *sfmt)
{
if (sfmt[0] == 'u')
sfmt[0] = 's';
else if (sfmt[0] == 's')
sfmt[0] = 'u';
}
static void
sfmt_swap_endian(char *sfmt)
{
if (strlen(sfmt) == 5) {
if (sfmt[3] == 'l')
sfmt[3] = 'b';
else if (sfmt[3] == 'b')
sfmt[3] = 'l';
}
}
static uint32_t
sfmt2afmt(const char *sfmt)
{
int i;
for (i = 0; i < AFMT_TAB_SIZE; i++) {
if (strcmp(sfmt, afmt_tab[i].sfmt) == 0 ||
(afmt_tab[i].alias != NULL &&
strcmp(sfmt, afmt_tab[i].alias) == 0))
return (afmt_tab[i].afmt);
}
return (0x00000000);
}
static uint32_t
bit2afmt(int bit)
{
int i;
for (i = 0; i < AFMT_TAB_SIZE; i++) {
if (AFMT_BIT(afmt_tab[i].afmt) == bit &&
afmt_tab[i].prefered != 0)
return (afmt_tab[i].afmt);
}
return (0x00000000);
}
static const char *
afmt2sfmt(uint32_t afmt)
{
int i;
for (i = 0; i < AFMT_TAB_SIZE; i++) {
if (AFMT_ENCODING(afmt) == afmt_tab[i].afmt)
return (afmt_tab[i].sfmt);
}
return (NULL);
}
static uint32_t
afmt_swap_sign(uint32_t afmt)
{
const char *sfmt;
char buf[8];
sfmt = afmt2sfmt(afmt);
if (sfmt != NULL) {
strcpy(buf, sfmt);
sfmt_swap_sign(buf);
return (sfmt2afmt(buf));
}
return (afmt);
}
static uint32_t
afmt_swap_endian(uint32_t afmt)
{
const char *sfmt;
char buf[8];
sfmt = afmt2sfmt(afmt);
if (sfmt != NULL) {
strcpy(buf, sfmt);
sfmt_swap_endian(buf);
return (sfmt2afmt(buf));
}
return (afmt);
}
static struct pcm_channel *pcmchannel = NULL;
static struct feeder_global_opts g_opt;
static struct feeder_io_opts in_opt, out_opt;
static struct timeval tv1, tv2;
static int benchmark = 0;
static void
feeder_benchmark_start(void)
{
benchmark = 1;
(void)gettimeofday(&tv1, NULL);
}
static void
feeder_benchmark_stop(void)
{
double elapsed;
if (benchmark == 0)
return;
(void)gettimeofday(&tv2, NULL);
benchmark = 0;
elapsed = (double)(((tv2.tv_sec - tv1.tv_sec) * 1000000) +
(tv2.tv_usec - tv1.tv_usec)) / 1000000.0;
fprintf(stderr, "\n Conversion Time: %.4f sec [%.4f x realtime]",
elapsed, (double)out_opt.waveinfo.data_size /
(double)(out_opt.waveinfo.rate * (out_opt.waveinfo.bit >> 3) *
out_opt.waveinfo.channels) / elapsed);
fprintf(stderr, "\n Output Frames: %d",
out_opt.waveinfo.data_size / ((out_opt.waveinfo.bit >> 3) *
out_opt.waveinfo.channels));
fprintf(stderr, "\n Buffering: bufsize=%u , total=%u bytes\n\n",
g_opt.bufsize, out_opt.waveinfo.data_size);
if (pcmchannel != NULL && pcmchannel->feeder != NULL) {
fprintf(stderr, " Feeders:-\n");
FEEDER_DUMP(pcmchannel->feeder);
fprintf(stderr, "\n");
}
}
static void
sigint_handler(int sig)
{
fprintf(stderr, "\n\nAborting...\n");
feeder_benchmark_stop();
if (out_opt.type == FEEDER_IO_TYPE_WAVE && out_opt.running != 0) {
out_opt.waveinfo.seekable = 1;
(void)wave_header_write(&(out_opt.waveinfo));
}
if (pcmchannel != NULL)
chn_destroy(pcmchannel);
exit(sig);
}
static int
usage(void)
{
fprintf(stderr, "usage: %s [-b -B bufsize -q quality -n -h "
"[-d direction] "
"-e treble:bass -p eq_preamp -F -P -R roundhz -S -v left:right] "
"[-t type -f format -c channels -r rate -l latency -X] infile "
"[-t type -f format -c channels -r rate -l latency -X] outfile\n",
getprogname());
return (EX_USAGE);
}
static int
parse_options(int *argc, char ***argv,
struct feeder_global_opts *gopt, struct feeder_io_opts *iopt)
{
long optl;
int i, ch;
optind = 1;
optreset = 1;
while ((ch = getopt(*argc, *argv,
"B:bc:d:e:EFf:l:p:q:Qr:t:v:hnPR:SVX")) != -1) {
switch (ch) {
case 'b':
gopt->bit_perfect = 1;
break;
case 'B':
optl = strtol(optarg, NULL, 10);
if (optl > SND_FXDIV_MAX)
optl = SND_FXDIV_MAX;
else if (optl < 1)
optl = 1;
gopt->bufsize = (uint32_t)optl;
break;
case 'c':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"Supported channels: %d to %d, "
"mono, stereo, swap\n",
SND_CHN_MIN, SND_CHN_MAX);
return (EX_USAGE);
} else if (strcmp(optarg, "mono") == 0)
optl = 1;
else if (strcmp(optarg, "stereo") == 0)
optl = 2;
else if (strcmp(optarg, "swap") == 0) {
gopt->matrix_swap = 1;
if (iopt->waveinfo.channels != 0)
optl = iopt->waveinfo.channels;
else
break;
} else
optl = strtol(optarg, NULL, 10);
if (optl < SND_CHN_MIN || optl > SND_CHN_MAX) {
warnx("total channels out of range -- %ld",
optl);
return (EINVAL);
}
iopt->waveinfo.channels = (uint32_t)optl;
if (iopt->afmt & AFMT_16BIT)
iopt->waveinfo.format =
(iopt->waveinfo.channels > 2) ?
WAVE_FORMAT_EXT : WAVE_FORMAT_PCM;
break;
case 'd':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"Direction: play, rec\n");
return (EX_USAGE);
}
if (strcasecmp(optarg, "play") == 0)
g_opt.direction = PCMDIR_PLAY;
else if (strcasecmp(optarg, "rec") == 0)
g_opt.direction = PCMDIR_REC;
else {
warnx("invalid direction -- %s", optarg);
return (EINVAL);
}
break;
case 'e':
{
int left, right;
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"Bass/Treble gain: "
"<left 0-100:<right 0-100>\n");
return (EX_USAGE);
}
i = sscanf(optarg, "%d:%d", &left, &right);
if ((i == 1 || i == 2) &&
(left >= 0 && left <= 100) && (i == 1 ||
(right >= 0 && right <= 100))) {
gopt->eqtreble = left;
gopt->eqbass = (i == 1) ? left : right;
} else {
warnx("invalid volume -- %s", optarg);
return (EINVAL);
}
}
break;
case 'E':
g_opt.expensive = 1;
break;
case 'f':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr, "Supported format: ");
for (i = 0; i < AFMT_TAB_SIZE; i++) {
fprintf(stderr, "%s%s",
afmt_tab[i].sfmt,
(i < (AFMT_TAB_SIZE - 1)) ?
", " : "");
}
fprintf(stderr, "\n");
return (EX_USAGE);
}
iopt->afmt = sfmt2afmt(optarg);
if (iopt->afmt == 0) {
warnx("unsupported format -- %s", optarg);
return (EINVAL);
}
iopt->waveinfo.bit = AFMT_BIT(iopt->afmt);
if (iopt->afmt & AFMT_BIGENDIAN)
iopt->waveinfo.endian = WAVE_BIG_ENDIAN;
else
iopt->waveinfo.endian = WAVE_LITTLE_ENDIAN;
if (iopt->afmt == AFMT_MU_LAW)
iopt->waveinfo.format = WAVE_FORMAT_ULAW;
else if (iopt->afmt == AFMT_A_LAW)
iopt->waveinfo.format = WAVE_FORMAT_ALAW;
else if (iopt->afmt & (AFMT_24BIT | AFMT_32BIT))
iopt->waveinfo.format = WAVE_FORMAT_EXT;
else if (iopt->afmt & AFMT_16BIT)
iopt->waveinfo.format =
(iopt->waveinfo.channels > 2) ?
WAVE_FORMAT_EXT : WAVE_FORMAT_PCM;
break;
case 'F':
feeder_rate_polyphase_max = -1;
break;
case 'l':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"Latency (OSS \"Policy\"): 0 - 10\n");
return (EX_USAGE);
}
optl = strtol(optarg, NULL, 10);
if (optl < 0 || optl > 10) {
warnx("Latency (OSS \"Policy\") out of range"
"-- %s", optarg);
return (EX_USAGE);
}
iopt->latency = (int)optl;
break;
case 'p':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"EQ Preamp: -/+ %d.0dB, %d.%ddB step\n",
FEEDEQ_GAIN_MAX,
FEEDEQ_GAIN_STEP / FEEDEQ_GAIN_DIV,
FEEDEQ_GAIN_STEP -
((FEEDEQ_GAIN_STEP / FEEDEQ_GAIN_DIV) *
FEEDEQ_GAIN_DIV));
return (EX_USAGE);
}
optl = (int32_t)feed_eq_scan_preamp_arg(optarg);
if (optl < FEEDEQ_PREAMP_MIN ||
optl > FEEDEQ_PREAMP_MAX) {
warnx("EQ Preamp out of range -- %s", optarg);
return (EINVAL);
}
gopt->eqpreamp = (int32_t)optl;
break;
case 'q':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"Resampling quality: %d to %d, "
"zoh, linear, sinc\n",
Z_QUALITY_MIN, Z_QUALITY_MAX);
return (EX_USAGE);
} else if (strcmp(optarg, "zoh") == 0)
optl = Z_QUALITY_ZOH;
else if (strcmp(optarg, "linear") == 0)
optl = Z_QUALITY_LINEAR;
else if (strcmp(optarg, "sinc") == 0)
optl = Z_QUALITY_SINC;
else
optl = strtol(optarg, NULL, 10);
if (optl < Z_QUALITY_MIN || optl > Z_QUALITY_MAX) {
warnx("resampling quality out of range -- %ld",
optl);
return (EINVAL);
}
gopt->quality = (int)optl;
break;
case 'Q':
gopt->verbose = 0;
break;
case 'r':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr, "Supported sampling rate: "
"%d to %d Hz\n",
FEEDRATE_RATEMIN, FEEDRATE_RATEMAX);
#ifdef Z_MASK
fprintf(stderr, "(Maximum factor: %d)\n",
Z_MASK);
#endif
return (EX_USAGE);
}
optl = strtol(optarg, NULL, 10);
if (optl < FEEDRATE_RATEMIN ||
optl > FEEDRATE_RATEMAX) {
warnx("sampling rate out of range -- %ld",
optl);
return (EINVAL);
}
iopt->waveinfo.rate = (uint32_t)optl;
break;
case 't':
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"Supported file type: raw, wav, cd, dsp\n");
return (EX_USAGE);
} else if (strcmp(optarg, "raw") == 0)
iopt->type = FEEDER_IO_TYPE_RAW;
else if (strcmp(optarg, "wav") == 0 ||
strcmp(optarg, "wave") == 0)
iopt->type = FEEDER_IO_TYPE_WAVE;
else if (strcmp(optarg, "cd") == 0) {
iopt->type = FEEDER_IO_TYPE_RAW;
iopt->afmt = AFMT_S16_LE;
iopt->waveinfo.bit = 16;
iopt->waveinfo.rate = 44100;
iopt->waveinfo.channels = 2;
} else if (strcmp(optarg, "dsp") == 0 ||
strcmp(optarg, "oss") == 0)
iopt->type = FEEDER_IO_TYPE_DSP;
else {
warnx("unknown file type -- %s", optarg);
return (EINVAL);
}
break;
case 'v':
{
int left, right;
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"Volume +/-%%: "
"<left 0-100:<right 0-100>\n");
return (EX_USAGE);
}
i = sscanf(optarg, "%d:%d", &left, &right);
if ((i == 1 || i == 2) &&
(left >= -100 && left <= 100) && (i == 1 ||
(right >= -100 && right <= 100))) {
gopt->volume = ((left + 100) << 8);
gopt->volume |= ((i == 2) ? right : left) +
100;
} else {
warnx("invalid volume -- %s", optarg);
return (EINVAL);
}
}
break;
case 'n':
gopt->afmt_ne = 0;
break;
case 'P':
feeder_rate_polyphase_max = INT32_MAX >> 2;
break;
case 'R':
#if defined(Z_ROUNDHZ) && defined(Z_ROUNDHZ_MIN) && defined(Z_ROUNDHZ_MAX)
if (strcmp(optarg, "help") == 0) {
fprintf(stderr,
"ROUNDHZ min=%d <-> max=%d\n",
Z_ROUNDHZ_MIN, Z_ROUNDHZ_MAX);
return (EX_USAGE);
}
optl = strtol(optarg, NULL, 10);
if (optl < Z_ROUNDHZ_MIN || optl > Z_ROUNDHZ_MAX) {
warnx("roundhz out of range -- %ld", optl);
return (EINVAL);
}
feeder_rate_round = (int)optl;
#else
fprintf(stderr, "ROUNDHZ not supported.\n");
return (EX_USAGE);
#endif
break;
case 'S':
feeder_rate_polyphase_max = 0;
break;
case 'V':
fprintf(stderr, "%s version "
__XSTRING(SND_DRV_VERSION)"/"MACHINE_ARCH"\n",
getprogname());
return (EX_USAGE);
case 'X':
iopt->exclusive = O_EXCL;
break;
case 'h':
case '?':
default:
return (usage());
break;
}
}
*argc -= optind;
*argv += optind;
if (*argc > 0) {
iopt->file = *(*argv);
if (iopt->type == FEEDER_IO_TYPE_UNKNOWN &&
iopt->file != NULL && strlen(iopt->file) >= 5 &&
strcasecmp(iopt->file + strlen(iopt->file) - 4,
".wav") == 0)
iopt->type = FEEDER_IO_TYPE_WAVE;
}
if (iopt->file == NULL) {
warnx("require input/output file argument");
return (usage());
}
return (0);
}
static int
feeder_build_stdin(struct feeder_chain_desc *cdesc)
{
struct pcm_feeder *f;
cdesc->desc.type = FEEDER_STDIO;
cdesc->desc.in = cdesc->current.afmt;
cdesc->desc.out = cdesc->desc.in;
f = FEEDER_CREATE(feeder_stdin, &(cdesc->desc));
if (f == NULL) {
warnx("failed to create feeder_stdin");
return (EX_IOERR);
}
if (cdesc->current.maxsize != 0 &&
FEEDER_SET(f, FEEDSTDIO_MAXBYTES, cdesc->current.maxsize) != 0) {
warnx("failed to set maximum bytes on feeder_stdin -- %u",
cdesc->current.maxsize);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
return (chn_addfeeder(cdesc->channel, f));
}
static int
feeder_build_stdout(struct feeder_chain_desc *cdesc)
{
struct pcm_feeder *f;
cdesc->desc.type = FEEDER_STDIO;
cdesc->desc.in = cdesc->current.afmt;
cdesc->desc.out = cdesc->desc.in;
f = FEEDER_CREATE(feeder_stdout, &(cdesc->desc));
if (f == NULL) {
warnx("could not create stdout feeder");
return (EX_IOERR);
}
return (chn_addfeeder(cdesc->channel, f));
}
static int
feeder_build_format(struct feeder_chain_desc *cdesc)
{
struct pcm_feeder *f;
cdesc->desc.type = FEEDER_FORMAT;
cdesc->desc.in = cdesc->current.afmt;
cdesc->desc.out = cdesc->target.afmt;
f = FEEDER_CREATE(feeder_format, &(cdesc->desc));
if (f == NULL) {
warnx("failed to create feeder_format");
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDFORMAT_CHANNELS,
cdesc->current.channels) != 0) {
warnx("failed to set channels on feeder_format -- %u",
cdesc->current.channels);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
cdesc->current.afmt = cdesc->target.afmt;
return (chn_addfeeder(cdesc->channel, f));
}
static int
feeder_build_formatne(struct feeder_chain_desc *cdesc, uint32_t nformat)
{
uint32_t oafmt, ochannels;
int ret;
if (nformat == 0)
nformat = cdesc->afmt_ne;
if (nformat == 0 || AFMT_ENCODING(cdesc->current.afmt) == nformat)
return (0);
oafmt = cdesc->target.afmt;
ochannels = cdesc->target.channels;
cdesc->target.afmt = SND_FORMAT(nformat, cdesc->current.channels,
AFMT_EXTCHANNEL(cdesc->current.afmt));
ret = feeder_build_format(cdesc);
if (ret != 0)
return (ret);
cdesc->target.afmt = oafmt;
cdesc->target.channels = ochannels;
return (0);
}
static int
matrix_swap(struct pcmchan_matrix *src)
{
struct pcmchan_matrix dst;
uint32_t i;
dst = *src;
dst.id = SND_CHN_MATRIX_MISC;
for (i = 0; i < dst.channels; i++)
dst.map[i] = src->map[src->channels - i - 1];
for (i = 0; i < (sizeof(dst.offset) / sizeof(dst.offset[0])); i++)
dst.offset[i] = -1;
for (i = 0; dst.map[i].type != SND_CHN_T_MAX; i++)
dst.offset[dst.map[i].type] = i;
*src = dst;
return (0);
}
static int
feeder_build_matrix(struct feeder_chain_desc *cdesc)
{
struct pcm_feeder *f;
int ret;
ret = feeder_build_formatne(cdesc, 0);
if (ret != 0)
return (ret);
cdesc->desc.type = FEEDER_MATRIX;
cdesc->desc.in = cdesc->current.afmt;
cdesc->desc.out = SND_FORMAT(cdesc->current.afmt,
cdesc->target.channels, AFMT_EXTCHANNEL(cdesc->target.afmt));
f = FEEDER_CREATE(feeder_matrix, &(cdesc->desc));
if (f == NULL) {
warnx("failed to create feeder_matrix");
return (EX_IOERR);
}
if (feeder_matrix_setup(f, &cdesc->m_in, &cdesc->m_out) != 0) {
warnx("feeder_matrix_setup() failed");
return (EX_IOERR);
}
cdesc->current.channels = cdesc->target.channels;
cdesc->current.afmt = cdesc->desc.out;
cdesc->m_in = cdesc->m_out;
cdesc->matrix_swap = 0;
return (chn_addfeeder(cdesc->channel, f));
}
static int
feeder_build_rate(struct feeder_chain_desc *cdesc)
{
struct pcm_feeder *f;
int ret;
ret = feeder_build_formatne(cdesc, 0);
if (ret != 0)
return (ret);
cdesc->desc.type = FEEDER_RATE;
cdesc->desc.in = cdesc->current.afmt;
cdesc->desc.out = cdesc->desc.in;
f = FEEDER_CREATE(feeder_rate, &(cdesc->desc));
if (f == NULL) {
warnx("failed to create feeder_rate");
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDRATE_SRC, cdesc->current.rate) != 0) {
warnx("failed to set source rate on feeder_rate -- %u",
cdesc->current.rate);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDRATE_DST, cdesc->target.rate) != 0) {
warnx("failed to set destination rate on feeder_rate "
"-- %u", cdesc->target.rate);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDRATE_QUALITY, cdesc->target.quality) != 0) {
warnx("failed to set resampling quality on feeder_rate "
"-- %d", cdesc->target.quality);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDRATE_CHANNELS, cdesc->current.channels) != 0) {
warnx("failed to set channels on feeder_rate -- %u",
cdesc->current.channels);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
cdesc->current.rate = cdesc->target.rate;
#ifdef SND_DIAGNOSTIC
g_opt.verbose = 0;
#endif
return (chn_addfeeder(cdesc->channel, f));
}
static int
feeder_build_volume(struct feeder_chain_desc *cdesc)
{
struct pcm_feeder *f;
int ret;
ret = feeder_build_formatne(cdesc, 0);
if (ret != 0)
return (ret);
cdesc->desc.type = FEEDER_VOLUME;
cdesc->desc.in = cdesc->current.afmt;
cdesc->desc.out = cdesc->desc.in;
f = FEEDER_CREATE(feeder_volume, &(cdesc->desc));
if (f == NULL) {
warnx("failed to create feeder_volume");
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDVOLUME_CLASS, SND_VOL_C_PCM) != 0) {
warnx("failed to set volume class on feeder_volume");
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDVOLUME_CHANNELS, cdesc->current.channels) != 0) {
warnx("failed to set channels on feeder_volume -- %u",
cdesc->current.channels);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
ret = feeder_volume_apply_matrix(f, &cdesc->m_in);
if (ret != 0)
return (ret);
cdesc->use_volume = 0;
return (chn_addfeeder(cdesc->channel, f));
}
static int
feeder_build_eq(struct feeder_chain_desc *cdesc)
{
struct pcm_feeder *f;
int ret;
ret = feeder_build_formatne(cdesc, 0);
if (ret != 0)
return (ret);
cdesc->desc.type = FEEDER_EQ;
cdesc->desc.in = cdesc->current.afmt;
cdesc->desc.out = cdesc->desc.in;
f = FEEDER_CREATE(feeder_eq, &(cdesc->desc));
if (f == NULL) {
warnx("failed to create feeder_eq");
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDEQ_CHANNELS, cdesc->current.channels) != 0) {
warnx("failed to set channels on feeder_eq -- %u",
cdesc->current.channels);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDEQ_RATE, cdesc->current.rate) != 0) {
warnx("failed to set rate on feeder_eq -- %u",
cdesc->current.rate);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDEQ_TREBLE, cdesc->eq.treble) != 0) {
warnx("failed to set treble on feeder_eq -- %u",
cdesc->eq.treble);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDEQ_BASS, cdesc->eq.bass) != 0) {
warnx("failed to set bass on feeder_eq -- %u",
cdesc->eq.bass);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDEQ_PREAMP, cdesc->eq.preamp) != 0) {
warnx("failed to set preamp on feeder_eq -- %d",
cdesc->eq.preamp);
FEEDER_DESTROY(f);
return (EX_IOERR);
}
if (FEEDER_SET(f, FEEDEQ_STATE, FEEDEQ_ENABLE) != 0) {
warnx("failed to enable feeder_eq");
FEEDER_DESTROY(f);
return (EX_IOERR);
}
cdesc->eq.use = 0;
return (chn_addfeeder(cdesc->channel, f));
}
static uint32_t feeder_fmtopts[] = {
#if defined(SND_FEEDER_FULL_MULTIFORMAT)
AFMT_S8, AFMT_U8,
#endif
#if defined(SND_FEEDER_MULTIFORMAT) || defined(SND_FEEDER_FULL_MULTIFORMAT)
AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE,
AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE,
AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE,
#else
AFMT_S16_NE, AFMT_S32_NE,
#endif
0
};
#define FEEDER_BW(c, t) ((c)->t.channels * (c)->t.rate)
#define FEEDRATE_UP(c) ((c)->target.rate > (c)->current.rate)
#define FEEDRATE_DOWN(c) ((c)->target.rate < (c)->current.rate)
#define FEEDRATE_REQUIRED(c) (FEEDRATE_UP(c) || FEEDRATE_DOWN(c))
#define FEEDMATRIX_UP(c) ((c)->target.channels > (c)->current.channels)
#define FEEDMATRIX_DOWN(c) ((c)->target.channels < (c)->current.channels)
#define FEEDMATRIX_REQUIRED(c) (FEEDMATRIX_UP(c) || \
FEEDMATRIX_DOWN(c) || \
((c)->matrix_swap != 0 && \
(c)->current.channels > 1))
#define FEEDFORMAT_REQUIRED(c) (AFMT_ENCODING((c)->current.afmt) != \
AFMT_ENCODING((c)->target.afmt))
#define FEEDVOLUME_REQUIRED(c) ((c)->use_volume != 0)
#define FEEDEQ_VALIDRATE(c, t) (feeder_eq_validrate((c)->t.rate) != 0)
#define FEEDEQ_ECONOMY(c) (FEEDER_BW(c, current) < FEEDER_BW(c, target))
#define FEEDEQ_REQUIRED(c) ((c)->eq.use != 0 && \
FEEDEQ_VALIDRATE(c, current))
#if defined(SND_FEEDER_FULL_MULTIFORMAT)
#define FEEDFORMAT_NE_REQUIRED(c) ((c)->afmt_ne != AFMT_S_NE)
#elif defined(FEEDER_MULTIFORMAT)
#define FEEDFORMAT_NE_REQUIRED(c) ((c)->afmt_ne != AFMT_S_NE && \
((c)->current.afmt & AFMT_8BIT))
#else
#define FEEDFORMAT_NE_REQUIRED(c) ((c)->afmt_ne != AFMT_S_NE && \
!(cdesc.current.afmt & \
(AFMT_S16_NE | AFMT_S32_NE | \
AFMT_S_NE)))
#endif
static int
feeder_build_chain(struct pcm_channel *c)
{
struct pcmchan_matrix *m;
struct feeder_chain_desc cdesc;
int i, left, right, center, ret;
cdesc.current.afmt = feeder_matrix_default_format(
SND_FORMAT(in_opt.afmt,
in_opt.waveinfo.channels,
AFMT_EXTCHANNEL(in_opt.afmt)));
cdesc.current.channels = in_opt.waveinfo.channels;
cdesc.current.rate = in_opt.waveinfo.rate;
cdesc.current.quality = g_opt.quality;
cdesc.current.maxsize = 0;
cdesc.target.afmt = feeder_matrix_default_format(
SND_FORMAT(out_opt.afmt,
out_opt.waveinfo.channels,
AFMT_EXTCHANNEL(out_opt.afmt)));
cdesc.target.channels = out_opt.waveinfo.channels;
cdesc.target.rate = out_opt.waveinfo.rate;
cdesc.target.quality = g_opt.quality;
cdesc.target.maxsize = 0;
cdesc.channel = c;
cdesc.use_volume = 0;
cdesc.afmt_ne = g_opt.afmt_ne;
cdesc.matrix_swap = g_opt.matrix_swap;
cdesc.eq.use = (g_opt.eqtreble != -1 && g_opt.eqbass != -1) ? 1 : 0;
cdesc.eq.treble = g_opt.eqtreble;
cdesc.eq.bass = g_opt.eqbass;
cdesc.eq.preamp = g_opt.eqpreamp;
cdesc.expensive = g_opt.expensive;
#if !defined(Z_MULTIFORMAT) || Z_MULTIFORMAT == 0
if (FEEDRATE_REQUIRED(&cdesc))
cdesc.afmt_ne = AFMT_S_NE;
#endif
/*{
static uint32_t keke[] = {
SND_FORMAT(S16_LE, 1),
SND_FORMAT(S16_LE, 2),
SND_FORMAT(S16_LE, 4),
SND_FORMAT(S32_LE, 1),
SND_FORMAT(S32_LE, 2),
SND_FORMAT(S32_LE, 4),
SND_FORMAT(S32_LE, 6),
SND_FORMAT(S32_LE, 8),
0
};
uint32_t ff, bst;
ff = SND_FORMAT(U16_BE, 3);
bst = chn_fmtbest(ff, keke);
warnx("0x%08x -> 0x%08x", ff, bst);
}*/
if (FEEDFORMAT_NE_REQUIRED(&cdesc)) {
cdesc.afmt_ne = chn_fmtbest(AFMT_ENCODING(cdesc.target.afmt),
feeder_fmtopts);
if (cdesc.afmt_ne == 0) {
warnx("chn_fmtbest failed!");
cdesc.afmt_ne = (cdesc.target.afmt &
(AFMT_24BIT | AFMT_32BIT)) ?
AFMT_S32_NE : AFMT_S16_NE;
}
}
left = (g_opt.volume & 0xff00) >> 8;
if (left > 100) {
left = ((left - 100) * (SND_VOL_0DB_MAX - SND_VOL_0DB_PCM)) /
100;
left += SND_VOL_0DB_PCM;
} else if (left < 100)
left = (left * SND_VOL_0DB_PCM) / 100;
else
left = SND_VOL_0DB_PCM;
right = g_opt.volume & 0xff;
if (right > 100) {
right = ((right - 100) * (SND_VOL_0DB_MAX - SND_VOL_0DB_PCM)) /
100;
right += SND_VOL_0DB_PCM;
} else if (right < 100)
right = (right * SND_VOL_0DB_PCM) / 100;
else
right = SND_VOL_0DB_PCM;
center = (left + right) >> 1;
for (i = 0; i != SND_CHN_T_MAX; i++) {
if ((1 << i) & SND_CHN_LEFT_MASK)
CHN_SETVOLUME(c, SND_VOL_C_PCM, i, left);
else if ((1 << i) & SND_CHN_RIGHT_MASK)
CHN_SETVOLUME(c, SND_VOL_C_PCM, i, right);
else
CHN_SETVOLUME(c, SND_VOL_C_PCM, i, center);
}
if (left != SND_VOL_0DB_PCM || right != SND_VOL_0DB_PCM)
cdesc.use_volume = 1;
bzero(&(cdesc.desc), sizeof(cdesc.desc));
if (in_opt.type == FEEDER_IO_TYPE_WAVE)
cdesc.current.maxsize = in_opt.waveinfo.data_size;
m = feeder_matrix_format_map(cdesc.current.afmt);
if (m == NULL)
return (EINVAL);
cdesc.m_in = *m;
m = feeder_matrix_format_map(cdesc.target.afmt);
if (m == NULL)
return (EINVAL);
cdesc.m_out = *m;
/*{
struct pcmchan_matrix xx;
xx = cdesc.m_in;
xx.channels[0].members &= ~SND_CHN_T_MASK_FC;
warnx("%d", feeder_matrix_compare(&xx, &cdesc.m_in));
}*/
if (cdesc.matrix_swap != 0) {
if (c->direction == PCMDIR_PLAY) {
if (cdesc.target.channels > 1)
matrix_swap(&cdesc.m_out);
else
cdesc.matrix_swap = 0;
} else {
if (cdesc.current.channels > 1)
matrix_swap(&cdesc.m_in);
else
cdesc.matrix_swap = 0;
}
}
#define FEEDER_BUILD(t) do { \
ret = feeder_build_##t(&cdesc); \
if (ret != 0) \
return (ret); \
} while (0)
FEEDER_BUILD(stdin);
if (c->direction == PCMDIR_PLAY)
goto feeder_chain_pcmdir_play;
else if (c->direction == PCMDIR_REC)
goto feeder_chain_pcmdir_rec;
else
goto feeder_chain_out;
feeder_chain_pcmdir_play:
/* PCMDIR_PLAY */
if (FEEDMATRIX_UP(&cdesc)) {
if (FEEDEQ_REQUIRED(&cdesc) &&
(!FEEDEQ_VALIDRATE(&cdesc, target) ||
(cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc))))
FEEDER_BUILD(eq);
if (FEEDRATE_REQUIRED(&cdesc))
FEEDER_BUILD(rate);
FEEDER_BUILD(matrix);
if (FEEDVOLUME_REQUIRED(&cdesc))
FEEDER_BUILD(volume);
/*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
FEEDER_BUILD(matrixswap);*/
if (FEEDEQ_REQUIRED(&cdesc))
FEEDER_BUILD(eq);
} else if (FEEDMATRIX_DOWN(&cdesc)) {
FEEDER_BUILD(matrix);
if (FEEDVOLUME_REQUIRED(&cdesc))
FEEDER_BUILD(volume);
/*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
FEEDER_BUILD(matrixswap);*/
if (FEEDEQ_REQUIRED(&cdesc) &&
(!FEEDEQ_VALIDRATE(&cdesc, target) ||
FEEDEQ_ECONOMY(&cdesc)))
FEEDER_BUILD(eq);
if (FEEDRATE_REQUIRED(&cdesc))
FEEDER_BUILD(rate);
if (FEEDEQ_REQUIRED(&cdesc))
FEEDER_BUILD(eq);
} else {
if (FEEDRATE_DOWN(&cdesc)) {
if (FEEDEQ_REQUIRED(&cdesc) &&
!FEEDEQ_VALIDRATE(&cdesc, target)) {
if (FEEDVOLUME_REQUIRED(&cdesc))
FEEDER_BUILD(volume);
FEEDER_BUILD(eq);
}
FEEDER_BUILD(rate);
}
if (FEEDMATRIX_REQUIRED(&cdesc))
FEEDER_BUILD(matrix);
if (FEEDVOLUME_REQUIRED(&cdesc))
FEEDER_BUILD(volume);
/*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
FEEDER_BUILD(matrixswap);*/
if (FEEDRATE_UP(&cdesc)) {
if (FEEDEQ_REQUIRED(&cdesc) &&
!FEEDEQ_VALIDRATE(&cdesc, target))
FEEDER_BUILD(eq);
FEEDER_BUILD(rate);
}
if (FEEDEQ_REQUIRED(&cdesc))
FEEDER_BUILD(eq);
}
goto feeder_chain_out;
feeder_chain_pcmdir_rec:
/* PCMDIR_REC */
if (FEEDMATRIX_UP(&cdesc)) {
if (FEEDEQ_REQUIRED(&cdesc) &&
(!FEEDEQ_VALIDRATE(&cdesc, target) ||
(cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc))))
FEEDER_BUILD(eq);
if (FEEDRATE_REQUIRED(&cdesc))
FEEDER_BUILD(rate);
/*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
FEEDER_BUILD(matrixswap);*/
FEEDER_BUILD(matrix);
if (FEEDVOLUME_REQUIRED(&cdesc))
FEEDER_BUILD(volume);
if (FEEDEQ_REQUIRED(&cdesc))
FEEDER_BUILD(eq);
} else if (FEEDMATRIX_DOWN(&cdesc)) {
/*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
FEEDER_BUILD(matrixswap);*/
FEEDER_BUILD(matrix);
if (FEEDVOLUME_REQUIRED(&cdesc))
FEEDER_BUILD(volume);
if (FEEDEQ_REQUIRED(&cdesc) &&
(!FEEDEQ_VALIDRATE(&cdesc, target) ||
FEEDEQ_ECONOMY(&cdesc)))
FEEDER_BUILD(eq);
if (FEEDRATE_REQUIRED(&cdesc))
FEEDER_BUILD(rate);
if (FEEDEQ_REQUIRED(&cdesc))
FEEDER_BUILD(eq);
} else {
if (FEEDRATE_DOWN(&cdesc)) {
if (FEEDEQ_REQUIRED(&cdesc) &&
!FEEDEQ_VALIDRATE(&cdesc, target))
FEEDER_BUILD(eq);
FEEDER_BUILD(rate);
}
if (FEEDMATRIX_REQUIRED(&cdesc))
FEEDER_BUILD(matrix);
/*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
FEEDER_BUILD(matrixswap);*/
if (FEEDVOLUME_REQUIRED(&cdesc))
FEEDER_BUILD(volume);
if (FEEDRATE_UP(&cdesc)) {
if (FEEDEQ_REQUIRED(&cdesc) &&
!FEEDEQ_VALIDRATE(&cdesc, target))
FEEDER_BUILD(eq);
FEEDER_BUILD(rate);
}
if (FEEDEQ_REQUIRED(&cdesc))
FEEDER_BUILD(eq);
}
feeder_chain_out:
if (FEEDFORMAT_REQUIRED(&cdesc))
FEEDER_BUILD(format);
FEEDER_BUILD(stdout);
return (0);
}
int
main(int argc, char **argv)
{
uint32_t sec1, sec8;
int ret;
if (0)
afmt_swap_sign(0x00000000);
bzero(&g_opt, sizeof(g_opt));
bzero(&in_opt, sizeof(in_opt));
bzero(&out_opt, sizeof(out_opt));
g_opt.afmt_ne = AFMT_S_NE;
g_opt.bufsize = FEEDSTDIO_BUFSIZE;
g_opt.quality = Z_QUALITY_SINC;
g_opt.matrix_swap = 0;
g_opt.verbose = 1;
g_opt.volume = (100 << 8) | 100;
g_opt.eqtreble = -1;
g_opt.eqbass = -1;
g_opt.eqpreamp = FEEDEQ_PREAMP_DEFAULT;
g_opt.direction = PCMDIR_PLAY;
g_opt.expensive = 0;
#define IO_FILENO(x) fileno((x).waveinfo.fp)
in_opt.waveinfo.fp = stdin;
in_opt.latency = 5;
out_opt.waveinfo.fp = stdout;
out_opt.latency = 5;
feeder_rate_polyphase_max = 0;
ret = parse_options(&argc, &argv, &g_opt, &in_opt);
if (ret != 0)
return (ret);
ret = parse_options(&argc, &argv, &g_opt, &out_opt);
if (ret != 0)
return (ret);
if (argc != 1)
return (usage());
if (strcmp(in_opt.file, "-") != 0) {
int fd;
fd = open(in_opt.file, O_RDONLY | in_opt.exclusive);
if (fd == -1) {
warn("open() file=%s", in_opt.file);
return (EX_IOERR);
}
in_opt.waveinfo.fp = fdopen(fd, "r");
if (in_opt.waveinfo.fp == NULL) {
warn("fdopen()");
close(fd);
return (EX_IOERR);
}
}
#define FIXUP_IO(o) do { \
if ((o).type != FEEDER_IO_TYPE_DSP) { \
struct stat st; \
int dummy = 0; \
if (fstat(IO_FILENO(o), &st) == 0 && \
S_ISCHR(st.st_mode) != 0 && \
ioctl(IO_FILENO(o), SNDCTL_DSP_CHANNELS, &dummy) != \
-1) \
(o).type = FEEDER_IO_TYPE_DSP; \
} \
} while (0)
FIXUP_IO(in_opt);
if (in_opt.type == FEEDER_IO_TYPE_UNKNOWN)
in_opt.type = FEEDER_IO_TYPE_RAW;
if (in_opt.type == FEEDER_IO_TYPE_RAW &&
(in_opt.afmt == 0 || in_opt.waveinfo.channels == 0 ||
in_opt.waveinfo.rate == 0)) {
warnx("raw io input require format, channels and "
"sampling rate arguments");
if (in_opt.waveinfo.fp != stdin)
fclose(in_opt.waveinfo.fp);
return (usage());
}
switch (in_opt.type) {
case FEEDER_IO_TYPE_WAVE:
ret = wave_header_read(&(in_opt.waveinfo));
if (ret != 0) {
if (in_opt.waveinfo.fp != stdin)
fclose(in_opt.waveinfo.fp);
return (ret);
}
switch (in_opt.waveinfo.format) {
case WAVE_FORMAT_PCM:
case WAVE_FORMAT_EXT:
in_opt.afmt = bit2afmt(in_opt.waveinfo.bit);
if (in_opt.waveinfo.endian == WAVE_BIG_ENDIAN)
in_opt.afmt = afmt_swap_endian(in_opt.afmt);
break;
case WAVE_FORMAT_ALAW:
in_opt.afmt = AFMT_A_LAW;
break;
case WAVE_FORMAT_ULAW:
in_opt.afmt = AFMT_MU_LAW;
break;
default:
warnx("unsupported format -- 0x%04x",
in_opt.waveinfo.format);
if (in_opt.waveinfo.fp != stdin)
fclose(in_opt.waveinfo.fp);
return (EFTYPE);
break;
}
if (in_opt.afmt == 0 ||
in_opt.waveinfo.channels < SND_CHN_MIN ||
in_opt.waveinfo.channels > SND_CHN_MAX ||
in_opt.waveinfo.rate < FEEDRATE_RATEMIN ||
in_opt.waveinfo.rate > FEEDRATE_RATEMAX) {
warnx("Don't know how to handle wave "
"afmt=0x%08x channels=%u rate=%u",
in_opt.afmt, in_opt.waveinfo.channels,
in_opt.waveinfo.rate);
if (in_opt.waveinfo.fp != stdin)
fclose(in_opt.waveinfo.fp);
return (EFTYPE);
}
break;
case FEEDER_IO_TYPE_DSP:
if (in_opt.afmt == 0)
in_opt.afmt = AFMT_S16_LE;
if (in_opt.waveinfo.channels == 0)
in_opt.waveinfo.channels = 2;
if (in_opt.waveinfo.rate == 0)
in_opt.waveinfo.rate = 44100;
in_opt.waveinfo.bit = AFMT_BIT(in_opt.afmt);
if ((in_opt.afmt & (AFMT_24BIT | AFMT_32BIT)) ||
(in_opt.waveinfo.channels > 2))
in_opt.waveinfo.format = WAVE_FORMAT_EXT;
else
in_opt.waveinfo.format = WAVE_FORMAT_PCM;
if (ioctl(IO_FILENO(in_opt), SNDCTL_DSP_SETFMT,
&(in_opt.afmt)) == -1 ||
ioctl(IO_FILENO(in_opt), SNDCTL_DSP_CHANNELS,
&(in_opt.waveinfo.channels)) == -1 ||
ioctl(IO_FILENO(in_opt), SNDCTL_DSP_SPEED,
&(in_opt.waveinfo.rate)) == -1 ||
ioctl(IO_FILENO(in_opt), SNDCTL_DSP_POLICY,
&(in_opt.latency)) == -1) {
warn("DSP ioctl()");
fclose(in_opt.waveinfo.fp);
return (EX_IOERR);
}
break;
default:
break;
}
if (out_opt.type == FEEDER_IO_TYPE_UNKNOWN)
out_opt.type = in_opt.type;
if (out_opt.afmt == 0)
out_opt.afmt = in_opt.afmt;
if (out_opt.waveinfo.format == 0)
out_opt.waveinfo.format = in_opt.waveinfo.format;
if (out_opt.waveinfo.bit == 0)
out_opt.waveinfo.bit = in_opt.waveinfo.bit;
if (out_opt.waveinfo.channels == 0)
out_opt.waveinfo.channels = in_opt.waveinfo.channels;
if (out_opt.waveinfo.rate == 0)
out_opt.waveinfo.rate = in_opt.waveinfo.rate;
if (out_opt.waveinfo.endian == 0)
out_opt.waveinfo.endian = in_opt.waveinfo.endian;
if (g_opt.bufsize < WAVE_BLOCK_ALIGN(&(out_opt.waveinfo)))
g_opt.bufsize = WAVE_BLOCK_ALIGN(&(out_opt.waveinfo));
g_opt.bufsize -= g_opt.bufsize % WAVE_BLOCK_ALIGN(&(out_opt.waveinfo));
pcmchannel = chn_create(g_opt.direction, g_opt.bufsize);
if (pcmchannel == NULL) {
warn("chn_create()");
if (in_opt.waveinfo.fp != stdin)
fclose(in_opt.waveinfo.fp);
return (ENOMEM);
}
pcmchannel->in_fp = in_opt.waveinfo.fp;
ret = feeder_build_chain(pcmchannel);
if (ret != 0) {
chn_destroy(pcmchannel);
return (ret);
}
if (strcmp(out_opt.file, "-") != 0) {
struct stat st;
int fd;
if (stat(out_opt.file, &st) == 0 && S_ISCHR(st.st_mode))
fd = open(out_opt.file, O_WRONLY | out_opt.exclusive);
else
fd = open(out_opt.file, O_WRONLY | O_CREAT | O_TRUNC);
if (fd == -1) {
warn("open() file=%s", out_opt.file);
chn_destroy(pcmchannel);
return (EX_IOERR);
}
out_opt.waveinfo.fp = fdopen(fd, "w");
if (out_opt.waveinfo.fp == NULL) {
warn("fdopen()");
close(fd);
chn_destroy(pcmchannel);
return (EX_IOERR);
}
}
pcmchannel->out_fp = out_opt.waveinfo.fp;
signal(SIGINT, sigint_handler);
FIXUP_IO(out_opt);
if (out_opt.type == FEEDER_IO_TYPE_DSP) {
g_opt.bit_perfect ^= 1;
(void)ioctl(IO_FILENO(out_opt), SNDCTL_DSP_COOKEDMODE,
&(g_opt.bit_perfect));
if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_SETFMT,
&(out_opt.afmt)) == -1)
warn("DSP ioctl(SETFMT)");
if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_CHANNELS,
&(out_opt.waveinfo.channels)) == -1)
warn("DSP ioctl(CHANNELS)");
if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_SPEED,
&(out_opt.waveinfo.rate)) == -1)
warn("DSP ioctl(SPEED)");
if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_POLICY,
&(out_opt.latency)) == -1)
warn("DSP ioctl(POLICY)");
out_opt.type = FEEDER_IO_TYPE_RAW;
if (0) {
unsigned long long chnorder;
int xret;
chnorder = 0x0000000000000000ULL;
xret = ioctl(IO_FILENO(out_opt),
SNDCTL_DSP_GET_CHNORDER, &chnorder);
warnx("Channel order: %d 0x%016llx", xret, chnorder);
/*chnorder = 0x0000000087654321ULL;
xret = ioctl(IO_FILENO(out_opt),
SNDCTL_DSP_SET_CHNORDER, &chnorder);
warnx("Channel order: %d 0x%016llx", xret, chnorder);*/
}
}
if (out_opt.type == FEEDER_IO_TYPE_WAVE) {
out_opt.waveinfo.seekable = 0;
out_opt.waveinfo.data_size = WAVE_DATA_SIZE_BIG;
(void)wave_header_write(&(out_opt.waveinfo));
out_opt.waveinfo.data_size = 0;
if (ftell(out_opt.waveinfo.fp) == -1) {
warn("impossible to fix wave header");
out_opt.type = FEEDER_IO_TYPE_RAW;
} else
out_opt.running = 1;
}
sec1 = out_opt.waveinfo.rate * out_opt.waveinfo.channels *
(out_opt.waveinfo.bit >> 3);
if (sec1 < 1)
sec1 = 1;
sec8 = sec1 >> 3;
if (sec8 < 1)
sec8 = 1;
if (g_opt.verbose != 0) {
#define VOL_LEFT() ((g_opt.volume >> 8) & 0xff)
#define VOL_RIGHT() (g_opt.volume & 0xff)
fprintf(stderr, "\n Files: %s -> %s\n",
in_opt.file, out_opt.file);
fprintf(stderr, " Conversion: %s (%u Hz) -> %s (%u Hz)\n",
afmt2sfmt(in_opt.afmt), in_opt.waveinfo.rate,
afmt2sfmt(out_opt.afmt), out_opt.waveinfo.rate);
fprintf(stderr, " Channels: %u -> %u\n",
in_opt.waveinfo.channels, out_opt.waveinfo.channels);
if (VOL_LEFT() != 100 || VOL_RIGHT() != 100)
fprintf(stderr, " Gain/Attenuation: L %d%% / R %d%%\n",
VOL_LEFT() - 100, VOL_RIGHT() - 100);
if (g_opt.eqtreble != -1 || g_opt.eqbass != -1) {
fprintf(stderr, " Treble/Bass: %d%% / %d%%\n",
g_opt.eqtreble, g_opt.eqbass);
if (g_opt.eqpreamp != FEEDEQ_PREAMP_DEFAULT)
fprintf(stderr,
" EQ Preamp: %c%d.%ddB\n",
FEEDEQ_PREAMP_SIGNMARK(g_opt.eqpreamp),
FEEDEQ_PREAMP_IPART(g_opt.eqpreamp),
FEEDEQ_PREAMP_FPART(g_opt.eqpreamp));
}
if (in_opt.waveinfo.rate != out_opt.waveinfo.rate)
fprintf(stderr, " Resample Quality: %d (%s)\n",
g_opt.quality, (g_opt.quality == 0) ?
"zero-order hold" : ((g_opt.quality < 2) ?
"linear interpolation" : "sinc interpolation"));
fprintf(stderr, "\n");
}
feeder_benchmark_start();
while ((ret = chn_feed(pcmchannel)) != 0) {
if (g_opt.verbose != 0) {
static const char throbber[4] = "-\\|/";
static uint32_t idx = 0, cnt1 = 0, cnt2 = 0;
static uint32_t sc = 0;
#define print_progress() fprintf(stderr, \
" Progress: [%10u Bytes] " \
"%c [Time: %02u:%02u:%02u]\r", \
out_opt.waveinfo.data_size, \
throbber[idx], \
sc / (60 * 60), (sc / 60) % 60, \
sc % 60)
if (out_opt.waveinfo.data_size == 0)
print_progress();
cnt1 += ret;
cnt2 += ret;
if (cnt1 >= sec1) {
cnt1 %= sec1;
sc++;
print_progress();
}
if (cnt2 >= sec8) {
cnt2 %= sec8;
idx++;
idx &= 3;
print_progress();
}
}
out_opt.waveinfo.data_size += ret;
}
if (g_opt.verbose != 0)
fprintf(stderr, "\n");
feeder_benchmark_stop();
if (out_opt.type == FEEDER_IO_TYPE_WAVE) {
out_opt.waveinfo.seekable = 1;
(void)wave_header_write(&(out_opt.waveinfo));
out_opt.running = 0;
}
chn_destroy(pcmchannel);
pcmchannel = NULL;
return (EX_OK);
}