1955 lines
58 KiB
C
1955 lines
58 KiB
C
|
/*
|
||
|
out123: stream data from libmpg123 or libsyn123 to an audio output device
|
||
|
|
||
|
copyright 1995-2023 by the mpg123 project,
|
||
|
free software under the terms of the LGPL 2.1
|
||
|
see COPYING and AUTHORS files in distribution or http://mpg123.org
|
||
|
|
||
|
initially written by Thomas Orgis (extracted from mpg123.c)
|
||
|
|
||
|
This is a stripped down mpg123 that only uses libout123 to write standard
|
||
|
input to an audio device. Of course, it got some enhancements with the
|
||
|
advent of libsyn123.
|
||
|
|
||
|
Please bear in mind that the code started out as a nasty hack on a very
|
||
|
old piece made out of nasty hacks and plain ugly code. Some nastiness
|
||
|
(like lax parameter checking) even serves a purpose: Test the robustness
|
||
|
of our libraries in catching bad caller behaviour.
|
||
|
|
||
|
TODO: Add basic parsing of WAV headers to be able to pipe in WAV files, especially
|
||
|
from something like mpg123 -w -.
|
||
|
|
||
|
TODO: Add option for phase shift between channels (delaying the second one).
|
||
|
This might be useful with generated signals, to locate left/right speakers
|
||
|
or just generally enhance the experience ... compensating for speaker locations.
|
||
|
This also means the option of mixing, channel attentuation. This is not
|
||
|
too hard to implement and might be useful for debugging outputs.
|
||
|
*/
|
||
|
|
||
|
#define ME "out123"
|
||
|
#include "config.h"
|
||
|
#include "version.h"
|
||
|
#include "compat/compat.h"
|
||
|
#include <ctype.h>
|
||
|
#if _WIN32
|
||
|
#include "win32_support.h"
|
||
|
#endif
|
||
|
#if defined(_WIN32) && defined(DYNAMIC_BUILD)
|
||
|
#define LINK_MPG123_DLL
|
||
|
#endif
|
||
|
#include "out123.h"
|
||
|
|
||
|
#include "local.h"
|
||
|
|
||
|
#ifdef HAVE_SYS_WAIT_H
|
||
|
#include <sys/wait.h>
|
||
|
#endif
|
||
|
#ifdef HAVE_SYS_RESOURCE_H
|
||
|
#include <sys/resource.h>
|
||
|
#endif
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <string.h>
|
||
|
#include <time.h>
|
||
|
|
||
|
#ifdef HAVE_SCHED_H
|
||
|
#include <sched.h>
|
||
|
#endif
|
||
|
|
||
|
#include "sysutil.h"
|
||
|
#include "getlopt.h"
|
||
|
|
||
|
#include "syn123.h"
|
||
|
#include "filters.h"
|
||
|
|
||
|
#include "common/debug.h"
|
||
|
|
||
|
/* be paranoid about setpriority support */
|
||
|
#ifndef PRIO_PROCESS
|
||
|
#undef HAVE_SETPRIORITY
|
||
|
#endif
|
||
|
|
||
|
static int intflag = FALSE;
|
||
|
|
||
|
static void usage(int err);
|
||
|
static void want_usage(char* arg, topt *opts);
|
||
|
static void long_usage(int err);
|
||
|
static void want_long_usage(char* arg, topt *opts);
|
||
|
static void print_title(FILE* o);
|
||
|
static void give_version(char* arg, topt *opts);
|
||
|
static void give_libversion(char* arg, topt *);
|
||
|
|
||
|
static int verbose = 0;
|
||
|
static int quiet = FALSE;
|
||
|
enum runmodes
|
||
|
{
|
||
|
RUN_MAIN = 0
|
||
|
, LIST_MODULES
|
||
|
, LIST_DEVICES
|
||
|
};
|
||
|
|
||
|
static int runmode = RUN_MAIN;
|
||
|
|
||
|
static FILE* input = NULL;
|
||
|
static char *encoding_name = NULL;
|
||
|
static int encoding = MPG123_ENC_SIGNED_16;
|
||
|
static char *inputenc_name = NULL;
|
||
|
static int inputenc = 0;
|
||
|
static int mixenc = -1;
|
||
|
static int channels = 2;
|
||
|
static int inputch = 0;
|
||
|
static long rate = 44100;
|
||
|
static long inputrate = 0;
|
||
|
static double speed = 1.;
|
||
|
static long outputrate = 0;
|
||
|
static char *driver = NULL;
|
||
|
static char *device = NULL;
|
||
|
int also_stdout = FALSE;
|
||
|
size_t buffer_kb = 0;
|
||
|
static int realtime = FALSE;
|
||
|
#ifdef HAVE_WINDOWS_H
|
||
|
static int w32_priority = 0;
|
||
|
#endif
|
||
|
#ifdef HAVE_SETPRIORITY
|
||
|
static int aggressive = FALSE;
|
||
|
#endif
|
||
|
static double preload = 0.2;
|
||
|
static long outflags = 0;
|
||
|
double preamp = 0.;
|
||
|
double preamp_factor = 1.;
|
||
|
double preamp_offset = 0.;
|
||
|
static const char *name = NULL; /* Let the out123 library choose "out123". */
|
||
|
static double device_buffer; /* output device buffer */
|
||
|
long timelimit_samples = -1;
|
||
|
double timelimit_seconds = -1.;
|
||
|
off_t offset = 0;
|
||
|
off_t timelimit = -1;
|
||
|
const char *clip_mode = "implicit";
|
||
|
int soft_clip = FALSE;
|
||
|
int do_clip = FALSE;
|
||
|
int do_filter = FALSE;
|
||
|
int do_preamp = FALSE;
|
||
|
int do_resample = FALSE;
|
||
|
int dither = 0;
|
||
|
double clip_limit = 1.;
|
||
|
double clip_width = 0.0234;
|
||
|
|
||
|
char *wave_patterns = NULL;
|
||
|
char *wave_freqs = NULL;
|
||
|
char *wave_phases = NULL;
|
||
|
char *wave_direction = NULL;
|
||
|
double sweep_freq = 0.;
|
||
|
double sweep_time = 0.;
|
||
|
int sweep_hard = 0;
|
||
|
long sweep_count = -1;
|
||
|
const char *sweep_type = "quad";
|
||
|
const char *signal_source = "file";
|
||
|
/* Default to around 2 MiB memory for the table. */
|
||
|
long wave_limit = 300000;
|
||
|
int pink_rows = 0;
|
||
|
double geiger_activity = 17;
|
||
|
char *resampler = "fine";
|
||
|
|
||
|
char *filter_spec = NULL;
|
||
|
|
||
|
size_t pcmblock = 1152; /* samples (pcm frames) we treat en bloc */
|
||
|
size_t resample_block = 0; /* resample that many samples in one go */
|
||
|
/* To be set after settling format. */
|
||
|
size_t pcmframe = 0; // Bytes for one PCM frame of output format.
|
||
|
size_t pcminframe = 0; // ... of input format
|
||
|
size_t mixframe = 0; // ... of mixing format
|
||
|
unsigned char *audio = NULL; // in/output buffer
|
||
|
unsigned char *inaudio = NULL; // separate input buffer
|
||
|
unsigned char *mixaudio = NULL; // separate mixing buffer
|
||
|
float *resaudio = NULL; // separate resampling buffer, always 32 bit float
|
||
|
char *mixmat_string = NULL;
|
||
|
double *mixmat = NULL;
|
||
|
|
||
|
// Option to play some oscillatory test signals.
|
||
|
// Also used for conversions.
|
||
|
syn123_handle *waver = NULL;
|
||
|
int generate = FALSE; // Wheter to use the syn123 generator.
|
||
|
// Input and output byte swappery.
|
||
|
long byte_in_flags = 0;
|
||
|
long byte_out_flags = 0;
|
||
|
|
||
|
out123_handle *ao = NULL;
|
||
|
char *cmd_name = NULL;
|
||
|
/* ThOr: pointers are not TRUE or FALSE */
|
||
|
char *equalfile = NULL;
|
||
|
int fresh = TRUE;
|
||
|
|
||
|
char *fullprogname = NULL; /* Copy of argv[0]. */
|
||
|
char *binpath; /* Path to myself. */
|
||
|
|
||
|
/* File-global storage of command line arguments.
|
||
|
They may be needed for cleanup after charset conversion. */
|
||
|
static char **argv = NULL;
|
||
|
static int argc = 0;
|
||
|
|
||
|
/* Drain output device/buffer, but still give the option to interrupt things. */
|
||
|
static void controlled_drain(void)
|
||
|
{
|
||
|
int framesize;
|
||
|
long rate;
|
||
|
size_t drain_block;
|
||
|
|
||
|
if(intflag || !out123_buffered(ao))
|
||
|
return;
|
||
|
if(out123_getformat(ao, &rate, NULL, NULL, &framesize))
|
||
|
return;
|
||
|
drain_block = 1024*framesize;
|
||
|
if(!quiet)
|
||
|
fprintf( stderr
|
||
|
, "\n"ME": draining buffer of %.1f s (you may interrupt)\n"
|
||
|
, (double)out123_buffered(ao)/framesize/rate );
|
||
|
do {
|
||
|
out123_ndrain(ao, drain_block);
|
||
|
} while(!intflag && out123_buffered(ao));
|
||
|
}
|
||
|
|
||
|
static void safe_exit(int code)
|
||
|
{
|
||
|
char *dummy, *dammy;
|
||
|
|
||
|
if(input && input != stdin)
|
||
|
INT123_compat_fclose(input);
|
||
|
|
||
|
if(!code)
|
||
|
controlled_drain();
|
||
|
if(intflag || code)
|
||
|
out123_drop(ao);
|
||
|
out123_del(ao);
|
||
|
#ifdef WANT_WIN32_UNICODE
|
||
|
win32_cmdline_free(argc, argv); /* This handles the premature argv == NULL, too. */
|
||
|
#endif
|
||
|
/* It's ugly... but let's just fix this still-reachable memory chunk of static char*. */
|
||
|
split_dir_file("", &dummy, &dammy);
|
||
|
if(fullprogname) free(fullprogname);
|
||
|
if(mixmat) free(mixmat);
|
||
|
if(inaudio && inaudio != audio) free(inaudio);
|
||
|
if(audio) free(audio);
|
||
|
if(mixaudio) free(mixaudio);
|
||
|
if(resaudio) free(resaudio);
|
||
|
if(waver) syn123_del(waver);
|
||
|
exit(code);
|
||
|
}
|
||
|
|
||
|
static void check_fatal_output(int code)
|
||
|
{
|
||
|
if(code)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
error2( "out123 error %i: %s"
|
||
|
, out123_errcode(ao), out123_strerror(ao) );
|
||
|
safe_exit(133);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void check_fatal_syn(const char *step, int code)
|
||
|
{
|
||
|
if(code)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("%s: syn123 error %i: %s", step, code, syn123_strerror(code));
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void set_output_module(char *arg, topt *opts)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
/* Search for a colon and set the device if found */
|
||
|
for(i=0; i< strlen( arg ); i++) {
|
||
|
if (arg[i] == ':') {
|
||
|
arg[i] = 0;
|
||
|
getlopt_set_char(opts, "audiodevice", &arg[i+1]);
|
||
|
debug1("Setting output device: %s", device);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
/* Set the output module */
|
||
|
driver = arg;
|
||
|
debug1("Setting output module: %s", driver );
|
||
|
}
|
||
|
|
||
|
static void set_output_flag(int flag)
|
||
|
{
|
||
|
if(outflags <= 0) outflags = flag;
|
||
|
else outflags |= flag;
|
||
|
}
|
||
|
|
||
|
static void set_output_h(char *a, topt *opts)
|
||
|
{
|
||
|
set_output_flag(OUT123_HEADPHONES);
|
||
|
}
|
||
|
|
||
|
static void set_output_s(char *a, topt *opts)
|
||
|
{
|
||
|
set_output_flag(OUT123_INTERNAL_SPEAKER);
|
||
|
}
|
||
|
|
||
|
static void set_output_l(char *a, topt *opts)
|
||
|
{
|
||
|
set_output_flag(OUT123_LINE_OUT);
|
||
|
}
|
||
|
|
||
|
static void set_output(char *arg, topt *opts)
|
||
|
{
|
||
|
/* If single letter, it's the legacy output switch for AIX/HP/Sun.
|
||
|
If longer, it's module[:device] . If zero length, it's rubbish. */
|
||
|
if(strlen(arg) <= 1) switch(arg[0])
|
||
|
{
|
||
|
case 'h': set_output_h(arg, opts); break;
|
||
|
case 's': set_output_s(arg, opts); break;
|
||
|
case 'l': set_output_l(arg, opts); break;
|
||
|
default:
|
||
|
error1("\"%s\" is no valid output", arg);
|
||
|
safe_exit(1);
|
||
|
}
|
||
|
else set_output_module(arg, opts);
|
||
|
}
|
||
|
|
||
|
enum byteflags
|
||
|
{
|
||
|
byte_nothing = 0
|
||
|
, byte_native = 1
|
||
|
, byte_swap = 2
|
||
|
, byte_little = 4
|
||
|
, byte_big = 8
|
||
|
};
|
||
|
|
||
|
// Input has same endianess as output unless something was specified.
|
||
|
static void finish_endian(void)
|
||
|
{
|
||
|
long outbyte = byte_out_flags & (byte_big|byte_little|byte_native);
|
||
|
if(outbyte && !byte_in_flags)
|
||
|
byte_in_flags = outbyte;
|
||
|
}
|
||
|
|
||
|
static void set_endian(char *arg, long *flags)
|
||
|
{
|
||
|
*flags &= ~(byte_big|byte_little|byte_native);
|
||
|
if(!strcmp(arg, "big"))
|
||
|
*flags |= byte_big;
|
||
|
else if(!strcmp(arg, "little"))
|
||
|
*flags |= byte_little;
|
||
|
else if(!strcmp(arg, "native"))
|
||
|
*flags |= byte_native;
|
||
|
else
|
||
|
merror("bad endianess choice: %s", arg);
|
||
|
}
|
||
|
|
||
|
static void set_in_endian(char *arg, topt *opts)
|
||
|
{
|
||
|
set_endian(arg, &byte_in_flags);
|
||
|
}
|
||
|
|
||
|
static void set_out_endian(char *arg, topt *opts)
|
||
|
{
|
||
|
set_endian(arg, &byte_out_flags);
|
||
|
}
|
||
|
|
||
|
static void set_byteswap(char *arg, topt *opts)
|
||
|
{
|
||
|
byte_out_flags |= byte_swap;
|
||
|
}
|
||
|
|
||
|
static void set_verbose (char *arg, topt *opts)
|
||
|
{
|
||
|
verbose++;
|
||
|
}
|
||
|
|
||
|
static void set_quiet (char *arg, topt *opts)
|
||
|
{
|
||
|
verbose=0;
|
||
|
quiet=TRUE;
|
||
|
}
|
||
|
|
||
|
static void set_out_wav(char *arg, topt *opts)
|
||
|
{
|
||
|
driver = "wav";
|
||
|
getlopt_set_char(opts, "audiodevice", arg);
|
||
|
}
|
||
|
|
||
|
void set_out_cdr(char *arg, topt *opts)
|
||
|
{
|
||
|
driver = "cdr";
|
||
|
getlopt_set_char(opts, "audiodevice", arg);
|
||
|
}
|
||
|
|
||
|
void set_out_au(char *arg, topt *opts)
|
||
|
{
|
||
|
driver = "au";
|
||
|
getlopt_set_char(opts, "audiodevice", arg);
|
||
|
}
|
||
|
|
||
|
void set_out_test(char *arg, topt *opts)
|
||
|
{
|
||
|
driver = "test";
|
||
|
getlopt_set_char(opts, "audiodevice", NULL);
|
||
|
}
|
||
|
|
||
|
static void set_out_file(char *arg, topt *opts)
|
||
|
{
|
||
|
driver = "raw";
|
||
|
getlopt_set_char(opts, "audiodevice", arg);
|
||
|
}
|
||
|
|
||
|
static void set_out_stdout(char *arg, topt *opts)
|
||
|
{
|
||
|
driver = "raw";
|
||
|
getlopt_set_char(opts, "audiodevice", NULL);
|
||
|
}
|
||
|
|
||
|
static void set_out_stdout1(char *arg, topt *opts)
|
||
|
{
|
||
|
also_stdout = TRUE;
|
||
|
}
|
||
|
|
||
|
#if !defined (HAVE_SCHED_SETSCHEDULER) && !defined (HAVE_WINDOWS_H)
|
||
|
static void realtime_not_compiled(char *arg, topt *opts)
|
||
|
{
|
||
|
fprintf(stderr, ME": Option '-T / --realtime' not compiled into this binary.\n");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void list_output_modules(void)
|
||
|
{
|
||
|
char **names = NULL;
|
||
|
char **descr = NULL;
|
||
|
int count = -1;
|
||
|
out123_handle *lao;
|
||
|
|
||
|
if((lao=out123_new()))
|
||
|
{
|
||
|
out123_param_string(lao, OUT123_BINDIR, binpath);
|
||
|
out123_param_int(lao, OUT123_VERBOSE, verbose);
|
||
|
if(quiet)
|
||
|
out123_param_int(lao, OUT123_FLAGS, OUT123_QUIET);
|
||
|
if((count=out123_drivers(lao, &names, &descr)) >= 0)
|
||
|
{
|
||
|
int i;
|
||
|
for(i=0; i<count; ++i)
|
||
|
{
|
||
|
printf( "%-15s\t%s\n", names[i], descr[i] );
|
||
|
free(names[i]);
|
||
|
free(descr[i]);
|
||
|
}
|
||
|
free(names);
|
||
|
free(descr);
|
||
|
}
|
||
|
out123_del(lao);
|
||
|
}
|
||
|
else if(!quiet)
|
||
|
error("Failed to create an out123 handle.");
|
||
|
exit(count >= 0 ? 0 : 1);
|
||
|
}
|
||
|
|
||
|
static void list_output_devices(void)
|
||
|
{
|
||
|
int count = 0;
|
||
|
char **names = NULL;
|
||
|
char **descr = NULL;
|
||
|
out123_handle *ao = NULL;
|
||
|
ao = out123_new();
|
||
|
char *real_driver = NULL;
|
||
|
count = out123_devices(ao, driver, &names, &descr, &real_driver);
|
||
|
if(count >= 0)
|
||
|
{
|
||
|
printf("Devices for output module %s:\n", real_driver);
|
||
|
for(int i=0; i<count; ++i)
|
||
|
{
|
||
|
// Always print like it's a terminal to avoid confusion with extra line breaks.
|
||
|
print_outstr(stdout, names[i], 1, 1);
|
||
|
printf("\t");
|
||
|
print_outstr(stdout, descr[i], 1, 1);
|
||
|
printf("\n");
|
||
|
}
|
||
|
out123_stringlists_free(names, descr, count);
|
||
|
free(real_driver);
|
||
|
} else
|
||
|
{
|
||
|
merror("Failed to enumerate output devices: %s", out123_strerror(ao));
|
||
|
}
|
||
|
out123_del(ao);
|
||
|
exit(count < 0 ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
static void list_encodings(char *arg, topt *opts)
|
||
|
{
|
||
|
int i;
|
||
|
int enc_count = 0;
|
||
|
int *enc_codes = NULL;
|
||
|
|
||
|
enc_count = out123_enc_list(&enc_codes);
|
||
|
/* All of the returned encodings have to have proper names!
|
||
|
It is a libout123 bug if not, and it should be quickly caught. */
|
||
|
for(i=0;i<enc_count;++i)
|
||
|
printf( "%s:\t%s\n"
|
||
|
, out123_enc_name(enc_codes[i]), out123_enc_longname(enc_codes[i]) );
|
||
|
free(enc_codes);
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
static int getencs(void)
|
||
|
{
|
||
|
int encs = 0;
|
||
|
out123_handle *lao;
|
||
|
if(verbose)
|
||
|
fprintf( stderr
|
||
|
, ME": getting supported encodings for %li Hz, %i channels\n"
|
||
|
, rate, channels );
|
||
|
if((lao=out123_new()))
|
||
|
{
|
||
|
out123_param_int(lao, OUT123_VERBOSE, verbose);
|
||
|
if(quiet)
|
||
|
out123_param_int(lao, OUT123_FLAGS, OUT123_QUIET);
|
||
|
if(!out123_open(lao, driver, device))
|
||
|
encs = out123_encodings(lao, rate, channels);
|
||
|
else if(!quiet)
|
||
|
error1("cannot open driver: %s", out123_strerror(lao));
|
||
|
out123_del(lao);
|
||
|
}
|
||
|
else if(!quiet)
|
||
|
error("Failed to create an out123 handle.");
|
||
|
return encs;
|
||
|
}
|
||
|
|
||
|
static void test_format(char *arg, topt *opts)
|
||
|
{
|
||
|
int encs;
|
||
|
encs = getencs();
|
||
|
exit((encs & encoding) ? 0 : -1);
|
||
|
}
|
||
|
|
||
|
static void test_encodings(char *arg, topt *opts)
|
||
|
{
|
||
|
int encs, enc_count, *enc_codes, i;
|
||
|
|
||
|
encs = getencs();
|
||
|
enc_count = out123_enc_list(&enc_codes);
|
||
|
for(i=0;i<enc_count;++i)
|
||
|
{
|
||
|
if((encs & enc_codes[i]) == enc_codes[i])
|
||
|
printf("%s\n", out123_enc_name(enc_codes[i]));
|
||
|
}
|
||
|
free(enc_codes);
|
||
|
exit(!encs);
|
||
|
}
|
||
|
|
||
|
static void query_format(char *arg, topt *opts)
|
||
|
{
|
||
|
out123_handle *lao;
|
||
|
|
||
|
if(verbose)
|
||
|
fprintf(stderr, ME": querying default format\n");
|
||
|
if((lao=out123_new()))
|
||
|
{
|
||
|
out123_param_int(lao, OUT123_VERBOSE, verbose);
|
||
|
if(quiet)
|
||
|
out123_param_int(lao, OUT123_FLAGS, OUT123_QUIET);
|
||
|
if(!out123_open(lao, driver, device))
|
||
|
{
|
||
|
struct mpg123_fmt *fmts = NULL;
|
||
|
int count;
|
||
|
count = out123_formats(lao, NULL, 0, 0, 0, &fmts);
|
||
|
if(count > 0 && fmts[0].encoding > 0)
|
||
|
{
|
||
|
const char *encname = out123_enc_name(fmts[0].encoding);
|
||
|
printf( "--rate %li --channels %i --encoding %s\n"
|
||
|
, fmts[0].rate, fmts[0].channels
|
||
|
, encname ? encname : "???" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(verbose)
|
||
|
fprintf(stderr, ME": no default format found\n");
|
||
|
}
|
||
|
free(fmts);
|
||
|
}
|
||
|
else if(!quiet)
|
||
|
error1("cannot open driver: %s", out123_strerror(lao));
|
||
|
out123_del(lao);
|
||
|
}
|
||
|
else if(!quiet)
|
||
|
error("Failed to create an out123 handle.");
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
void set_wave_freqs(char *arg, topt *opts)
|
||
|
{
|
||
|
getlopt_set_char(opts, "source", "wave");
|
||
|
wave_freqs = arg;
|
||
|
}
|
||
|
|
||
|
void set_pink_rows(char *arg, topt *opts)
|
||
|
{
|
||
|
getlopt_set_char(opts, "source", "pink");
|
||
|
pink_rows = atoi(arg);
|
||
|
}
|
||
|
|
||
|
void set_geiger_act(char *arg, topt *opts)
|
||
|
{
|
||
|
getlopt_set_char(opts, "source", "geiger");
|
||
|
geiger_activity = atof(arg);
|
||
|
}
|
||
|
|
||
|
void set_sweep_freq(char *arg, topt *opts)
|
||
|
{
|
||
|
getlopt_set_char(opts, "source", "sweep");
|
||
|
sweep_freq = atof(arg);
|
||
|
}
|
||
|
|
||
|
/* Please note: GLO_NUM expects point to LONG! */
|
||
|
/* ThOr:
|
||
|
* Yeah, and despite that numerous addresses to int variables were
|
||
|
passed.
|
||
|
* That's not good on my Alpha machine with int=32bit and long=64bit!
|
||
|
* Introduced GLO_INT and GLO_LONG as different bits to make that clear.
|
||
|
* GLO_NUM no longer exists.
|
||
|
*/
|
||
|
topt opts[] = {
|
||
|
{'t', "test", GLO_INT, set_out_test, NULL, 0},
|
||
|
{'s', "stdout", GLO_INT, set_out_stdout, NULL, 0},
|
||
|
{'S', "STDOUT", GLO_INT, set_out_stdout1, NULL, 0},
|
||
|
{'O', "outfile", GLO_ARG | GLO_CHAR, set_out_file, NULL, 0},
|
||
|
{'v', "verbose", 0, set_verbose, 0, 0},
|
||
|
{'q', "quiet", 0, set_quiet, 0, 0},
|
||
|
{'m', "mono", GLO_INT, 0, &channels, 1},
|
||
|
{0, "stereo", GLO_INT, 0, &channels, 2},
|
||
|
{'c', "channels", GLO_ARG | GLO_INT, 0, &channels, 0},
|
||
|
{'C', "inputch", GLO_ARG | GLO_INT, 0, &inputch, 0},
|
||
|
{'M', "mix", GLO_ARG | GLO_CHAR, 0, &mixmat_string, 0},
|
||
|
{0, "filter", GLO_ARG | GLO_CHAR, 0, &filter_spec, 0},
|
||
|
{'P', "preamp", GLO_ARG | GLO_DOUBLE, 0, &preamp, 0},
|
||
|
{0, "offset", GLO_ARG | GLO_DOUBLE, 0, &preamp_offset, 0},
|
||
|
{'r', "rate", GLO_ARG | GLO_LONG, 0, &rate, 0},
|
||
|
{'R', "inputrate", GLO_ARG | GLO_LONG, 0, &inputrate, 0},
|
||
|
{0, "speed", GLO_ARG | GLO_DOUBLE, 0, &speed, 0},
|
||
|
{0, "clip", GLO_ARG | GLO_CHAR, 0, &clip_mode, 0},
|
||
|
{0, "dither", GLO_INT, 0, &dither, 1},
|
||
|
{0, "headphones", 0, set_output_h, 0,0},
|
||
|
{0, "speaker", 0, set_output_s, 0,0},
|
||
|
{0, "lineout", 0, set_output_l, 0,0},
|
||
|
{'o', "output", GLO_ARG | GLO_CHAR, set_output, 0, 0},
|
||
|
{0, "list-modules",GLO_INT, 0, &runmode, LIST_MODULES},
|
||
|
{0, "list-devices",GLO_INT, 0, &runmode, LIST_DEVICES},
|
||
|
{'a', "audiodevice", GLO_ARG | GLO_CHAR, 0, &device, 0},
|
||
|
#ifndef NOXFERMEM
|
||
|
{'b', "buffer", GLO_ARG | GLO_LONG, 0, &buffer_kb, 0},
|
||
|
{0, "preload", GLO_ARG|GLO_DOUBLE, 0, &preload, 0},
|
||
|
#endif
|
||
|
#ifdef HAVE_SETPRIORITY
|
||
|
{0, "aggressive", GLO_INT, 0, &aggressive, 2},
|
||
|
#endif
|
||
|
#if defined (HAVE_SCHED_SETSCHEDULER) || defined (HAVE_WINDOWS_H)
|
||
|
/* check why this should be a long variable instead of int! */
|
||
|
{'T', "realtime", GLO_INT, 0, &realtime, TRUE },
|
||
|
#else
|
||
|
{'T', "realtime", 0, realtime_not_compiled, 0, 0 },
|
||
|
#endif
|
||
|
#ifdef HAVE_WINDOWS_H
|
||
|
{0, "priority", GLO_ARG | GLO_INT, 0, &w32_priority, 0},
|
||
|
#endif
|
||
|
{'w', "wav", GLO_ARG | GLO_CHAR, set_out_wav, 0, 0 },
|
||
|
{0, "cdr", GLO_ARG | GLO_CHAR, set_out_cdr, 0, 0 },
|
||
|
{0, "au", GLO_ARG | GLO_CHAR, set_out_au, 0, 0 },
|
||
|
{'?', "help", 0, want_usage, 0, 0 },
|
||
|
{0 , "longhelp" , 0, want_long_usage, 0, 0 },
|
||
|
{0 , "version" , 0, give_version, 0, 0 },
|
||
|
{0 , "libversion" , 0, give_libversion, 0, 0 },
|
||
|
{'e', "encoding", GLO_ARG|GLO_CHAR, 0, &encoding_name, 0},
|
||
|
{0, "endian", GLO_ARG|GLO_CHAR, set_out_endian, 0, 0},
|
||
|
{'E', "inputenc", GLO_ARG|GLO_CHAR, 0, &inputenc_name, 0},
|
||
|
{0, "inputend", GLO_ARG|GLO_CHAR, set_in_endian, 0, 0},
|
||
|
{0, "byteswap", 0, set_byteswap, 0, 0},
|
||
|
{0, "list-encodings", 0, list_encodings, 0, 0 },
|
||
|
{0, "test-format", 0, test_format, 0, 0 },
|
||
|
{0, "test-encodings", 0, test_encodings, 0, 0},
|
||
|
{0, "query-format", 0, query_format, 0, 0},
|
||
|
{0, "name", GLO_ARG|GLO_CHAR, 0, &name, 0},
|
||
|
{0, "devbuffer", GLO_ARG|GLO_DOUBLE, 0, &device_buffer, 0},
|
||
|
{0, "timelimit", GLO_ARG|GLO_LONG, 0, &timelimit_samples, 0},
|
||
|
{0, "seconds", GLO_ARG|GLO_DOUBLE, 0, &timelimit_seconds, 0},
|
||
|
{0, "source", GLO_ARG|GLO_CHAR, 0, &signal_source, 0},
|
||
|
{0, "wave-pat", GLO_ARG|GLO_CHAR, 0, &wave_patterns, 0},
|
||
|
{0, "wave-freq", GLO_ARG|GLO_CHAR, set_wave_freqs, 0, 0},
|
||
|
{0, "wave-phase", GLO_ARG|GLO_CHAR, 0, &wave_phases, 0},
|
||
|
{0, "wave-direction", GLO_ARG|GLO_CHAR, 0, &wave_direction, 0},
|
||
|
{0, "wave-limit", GLO_ARG|GLO_LONG, 0, &wave_limit, 0},
|
||
|
{0, "genbuffer", GLO_ARG|GLO_LONG, 0, &wave_limit, 0},
|
||
|
{0, "pink-rows", GLO_ARG|GLO_INT, set_pink_rows, 0, 0},
|
||
|
{0, "geiger-activity", GLO_ARG|GLO_DOUBLE, set_geiger_act, 0, 0},
|
||
|
{0, "wave-sweep", GLO_ARG|GLO_DOUBLE, set_sweep_freq, 0, 0},
|
||
|
{0, "sweep-type", GLO_ARG|GLO_CHAR, 0, &sweep_type, 0 },
|
||
|
{0, "sweep-time", GLO_ARG|GLO_DOUBLE, 0, &sweep_time, 0},
|
||
|
{0, "sweep-hard", GLO_INT, 0, &sweep_hard, TRUE},
|
||
|
{0, "sweep-count", GLO_ARG|GLO_LONG, 0, &sweep_count, 0},
|
||
|
{0, "resample", GLO_ARG|GLO_CHAR, 0, &resampler, 0},
|
||
|
{0, 0, 0, 0, 0, 0}
|
||
|
};
|
||
|
|
||
|
/* An strtok() that also returns empty tokens on multiple separators. */
|
||
|
|
||
|
static size_t mytok_count(const char *choppy)
|
||
|
{
|
||
|
size_t count = 0;
|
||
|
if(choppy)
|
||
|
{
|
||
|
count = 1;
|
||
|
do {
|
||
|
if(*choppy == ',')
|
||
|
++count;
|
||
|
} while(*(++choppy));
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static char *mytok(char **choppy)
|
||
|
{
|
||
|
char *tok;
|
||
|
if(!*choppy)
|
||
|
return NULL;
|
||
|
tok = *choppy;
|
||
|
while(**choppy && **choppy != ',')
|
||
|
++(*choppy);
|
||
|
/* Another token follows if we found a separator. */
|
||
|
if(**choppy == ',')
|
||
|
{
|
||
|
*(*choppy)++ = 0;
|
||
|
while(isspace(**choppy))
|
||
|
*(*choppy)++ = 0;
|
||
|
}
|
||
|
else
|
||
|
*choppy = NULL; /* Nothing left. */
|
||
|
return tok;
|
||
|
}
|
||
|
|
||
|
static void setup_wavegen(void)
|
||
|
{
|
||
|
size_t count = 0;
|
||
|
size_t i;
|
||
|
double *freq = NULL;
|
||
|
double *freq_real = NULL;
|
||
|
double *phase = NULL;
|
||
|
int *backwards = NULL;
|
||
|
int *id = NULL;
|
||
|
int synerr = 0;
|
||
|
size_t common = 0;
|
||
|
|
||
|
if(!generate)
|
||
|
wave_limit = 0;
|
||
|
waver = syn123_new(inputrate, inputch, inputenc, wave_limit, &synerr);
|
||
|
check_fatal_syn("waver init", synerr);
|
||
|
check_fatal_syn("dither", syn123_dither(waver, dither, NULL));
|
||
|
if(!waver)
|
||
|
safe_exit(132);
|
||
|
if(do_resample)
|
||
|
{
|
||
|
int dirty = -1;
|
||
|
if(!strcasecmp(resampler, "fine"))
|
||
|
dirty = 0;
|
||
|
else if(!strcasecmp(resampler, "dirty"))
|
||
|
dirty = 1;
|
||
|
else
|
||
|
{
|
||
|
if(!quiet)
|
||
|
error1("Bad value for resampler type given: %s\n", resampler);
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
check_fatal_syn( "resampling setup", syn123_setup_resample( waver
|
||
|
, inputrate, outputrate, channels, dirty, 0 ) );
|
||
|
}
|
||
|
if(do_filter)
|
||
|
{
|
||
|
size_t err_count = 0;
|
||
|
struct filterlist *fl = parse_filterspec(filter_spec);
|
||
|
if(!fl)
|
||
|
{
|
||
|
error1("Got no valid filter specification out of: %s", filter_spec);
|
||
|
safe_exit(135);
|
||
|
}
|
||
|
for(size_t fi=0; fi<fl->count; ++fi)
|
||
|
{
|
||
|
int err = syn123_setup_filter( waver, 1
|
||
|
, fl->f[fi].order, fl->f[fi].b, fl->f[fi].a
|
||
|
, mixenc, channels, 1 );
|
||
|
if(err)
|
||
|
{
|
||
|
merror("Cannot init filter %zu: %s", fi, syn123_strerror(err));
|
||
|
++err_count;
|
||
|
}
|
||
|
}
|
||
|
free_filterlist(fl);
|
||
|
if(err_count)
|
||
|
{
|
||
|
merror("%zu errors during filter setup", err_count);
|
||
|
safe_exit(135);
|
||
|
}
|
||
|
}
|
||
|
// At least have waver handy for conversions.
|
||
|
if(!generate)
|
||
|
return;
|
||
|
|
||
|
if(!strcmp(signal_source, "pink"))
|
||
|
{
|
||
|
synerr = syn123_setup_pink(waver, pink_rows, 123456, &common);
|
||
|
if(synerr)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("setting up pink noise generator: %s\n", syn123_strerror(synerr));
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
if(verbose)
|
||
|
{
|
||
|
fprintf( stderr
|
||
|
, ME ": pink noise with %i generator rows (0=internal default)\n"
|
||
|
, pink_rows );
|
||
|
}
|
||
|
goto setup_waver_end;
|
||
|
}
|
||
|
if(!strcmp(signal_source, "white"))
|
||
|
{
|
||
|
synerr = syn123_setup_white(waver, 123456, &common);
|
||
|
if(synerr)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("setting up white noise generator: %s\n", syn123_strerror(synerr));
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
if(verbose)
|
||
|
{
|
||
|
fprintf( stderr, ME ": white noise\n" );
|
||
|
}
|
||
|
goto setup_waver_end;
|
||
|
}
|
||
|
else if(!strcmp(signal_source, "geiger"))
|
||
|
{
|
||
|
synerr = syn123_setup_geiger(waver, geiger_activity, 123456, &common);
|
||
|
if(synerr)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("setting up geiger generator: %s\n", syn123_strerror(synerr));
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
if(verbose)
|
||
|
{
|
||
|
fprintf(stderr, ME ": geiger with actvity %g\n", geiger_activity);
|
||
|
}
|
||
|
goto setup_waver_end;
|
||
|
}
|
||
|
else if(!strcmp(signal_source, "sweep"))
|
||
|
{
|
||
|
double f1 = 0.;
|
||
|
double f2 = sweep_freq;
|
||
|
int wid = SYN123_WAVE_SINE;
|
||
|
int backwards = FALSE;
|
||
|
int sid = SYN123_SWEEP_QUAD;
|
||
|
// Yes, could overflow. You get a short time, then.
|
||
|
size_t duration = timelimit > -1 ? (size_t)timelimit : inputrate;
|
||
|
size_t period = 0;
|
||
|
double sweep_phase = 0.;
|
||
|
double endphase = -1;
|
||
|
if(sweep_type)
|
||
|
{
|
||
|
if(!strncmp("lin", sweep_type, 3))
|
||
|
sid = SYN123_SWEEP_LIN;
|
||
|
else if(!strncmp("qua", sweep_type, 3))
|
||
|
sid = SYN123_SWEEP_QUAD;
|
||
|
else if(!strncmp("exp", sweep_type, 3))
|
||
|
sid = SYN123_SWEEP_EXP;
|
||
|
else
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("bad sweep choice: %s", sweep_type);
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
}
|
||
|
if(wave_freqs)
|
||
|
{
|
||
|
char *next = wave_freqs;
|
||
|
f1 = atof(mytok(&next));
|
||
|
}
|
||
|
if(wave_patterns)
|
||
|
{
|
||
|
char *next = wave_patterns;
|
||
|
wid = syn123_wave_id(mytok(&next));
|
||
|
if(wid < 0 && !quiet)
|
||
|
fprintf(stderr, ME ": Warning: bad wave pattern: %s\n", wave_patterns);
|
||
|
}
|
||
|
if(wave_phases)
|
||
|
{
|
||
|
char *next = wave_phases;
|
||
|
sweep_phase = atof(mytok(&next));
|
||
|
if(sweep_phase < 0)
|
||
|
{
|
||
|
backwards = TRUE;
|
||
|
sweep_phase = -sweep_phase;
|
||
|
}
|
||
|
}
|
||
|
if(wave_direction)
|
||
|
{
|
||
|
char *next = wave_phases;
|
||
|
backwards = atof(mytok(&next)) < 0 ? TRUE : FALSE;
|
||
|
}
|
||
|
if(sweep_time > 0)
|
||
|
duration = (size_t)(sweep_time*inputrate);
|
||
|
synerr = syn123_setup_sweep( waver, wid, sweep_phase, backwards
|
||
|
, sid, &f1, &f2, !sweep_hard, duration, &endphase, &period, &common);
|
||
|
if(synerr)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("setting up sweep generator: %s\n", syn123_strerror(synerr));
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
if(sweep_count > -1)
|
||
|
timelimit = sweep_count * period;
|
||
|
if(verbose)
|
||
|
{
|
||
|
fprintf( stderr, ME ": %s sweep of %zu samples (%s)\n"
|
||
|
, sweep_type
|
||
|
, (timelimit > -1 && timelimit < period) ? (size_t)timelimit : (size_t)period
|
||
|
, (timelimit > -1 && timelimit < period)
|
||
|
? ((timelimit == duration) ? "exactly" : "cut off" )
|
||
|
: (sweep_hard ? "periodic with phase jumps" : "smoothly periodic") );
|
||
|
fprintf( stderr, ME ": from: %s @ %g Hz p %g\n"
|
||
|
, syn123_wave_name(wid), f1, sweep_phase );
|
||
|
fprintf( stderr, ME ": to: %s @ %g Hz p %g\n"
|
||
|
, syn123_wave_name(wid), f2
|
||
|
, (timelimit < 0 || timelimit >= period) ? sweep_phase : endphase );
|
||
|
}
|
||
|
goto setup_waver_end;
|
||
|
}
|
||
|
else if(strcmp(signal_source, "wave"))
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("unknown signal source: %s", signal_source);
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
|
||
|
// The big default code block is for wave setup.
|
||
|
// Exceptions jump over it.
|
||
|
if(wave_freqs)
|
||
|
{
|
||
|
char *tok;
|
||
|
char *next;
|
||
|
count = mytok_count(wave_freqs);
|
||
|
freq = malloc(sizeof(double)*count);
|
||
|
freq_real = malloc(sizeof(double)*count);
|
||
|
if(!freq || !freq_real){ error("OOM!"); safe_exit(1); }
|
||
|
next = wave_freqs;
|
||
|
for(i=0; i<count; ++i)
|
||
|
{
|
||
|
tok = mytok(&next);
|
||
|
if(tok && *tok)
|
||
|
freq[i] = atof(tok);
|
||
|
else if(i)
|
||
|
freq[i] = freq[i-1];
|
||
|
else
|
||
|
freq[i] = 0;
|
||
|
}
|
||
|
memcpy(freq_real, freq, sizeof(double)*count);
|
||
|
}
|
||
|
else if(wave_patterns || wave_phases || wave_direction)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
fprintf( stderr
|
||
|
, ME ": Warning: wave setup ignored without specified frequency\n" );
|
||
|
}
|
||
|
|
||
|
|
||
|
if(count && wave_patterns)
|
||
|
{
|
||
|
char *tok;
|
||
|
char *next = wave_patterns;
|
||
|
id = malloc(sizeof(int)*count);
|
||
|
if(!id){ error("OOM!"); safe_exit(1); }
|
||
|
for(i=0; i<count; ++i)
|
||
|
{
|
||
|
tok = mytok(&next);
|
||
|
if((tok && *tok) || i==0)
|
||
|
{
|
||
|
id[i] = syn123_wave_id(tok);
|
||
|
if(id[i] < 0 && !quiet)
|
||
|
fprintf(stderr, ME ": Warning: bad wave pattern: %s\n", tok);
|
||
|
}
|
||
|
else
|
||
|
id[i] = id[i-1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(count && wave_phases)
|
||
|
{
|
||
|
char *tok;
|
||
|
char *next = wave_phases;
|
||
|
phase = malloc(sizeof(double)*count);
|
||
|
backwards = malloc(sizeof(int)*count);
|
||
|
if(!phase || !backwards){ error("OOM!"); safe_exit(1); }
|
||
|
for(i=0; i<count; ++i)
|
||
|
{
|
||
|
tok = mytok(&next);
|
||
|
if(tok && *tok)
|
||
|
phase[i] = atof(tok);
|
||
|
else if(i)
|
||
|
phase[i] = phase[i-1];
|
||
|
else
|
||
|
phase[i] = 0;
|
||
|
if(phase[i] < 0)
|
||
|
{
|
||
|
phase[i] *= -1;
|
||
|
backwards[i] = TRUE;
|
||
|
}
|
||
|
else
|
||
|
backwards[i] = FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(count && wave_direction)
|
||
|
{
|
||
|
char *tok;
|
||
|
char *next = wave_direction;
|
||
|
if(!backwards)
|
||
|
backwards = malloc(sizeof(int)*count);
|
||
|
if(!backwards){ error("OOM!"); safe_exit(1); }
|
||
|
for(i=0; i<count; ++i)
|
||
|
{
|
||
|
tok = mytok(&next);
|
||
|
if(tok && *tok)
|
||
|
backwards[i] = atof(tok) < 0 ? TRUE : FALSE;
|
||
|
else if(i)
|
||
|
backwards[i] = backwards[i-1];
|
||
|
else
|
||
|
backwards[i] = FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
synerr = syn123_setup_waves( waver, count
|
||
|
, id, freq_real, phase, backwards, &common );
|
||
|
if(synerr)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
merror("setting up wave generator: %s\n", syn123_strerror(synerr));
|
||
|
safe_exit(132);
|
||
|
}
|
||
|
if(verbose)
|
||
|
{
|
||
|
if(count) for(i=0; i<count; ++i)
|
||
|
fprintf( stderr, ME ": wave %zu: %s @ %g Hz (%g Hz) p %g\n"
|
||
|
, i
|
||
|
, syn123_wave_name(id ? id[i] : SYN123_WAVE_SINE)
|
||
|
, freq[i], freq_real[i]
|
||
|
, phase ? phase[i] : 0 );
|
||
|
else
|
||
|
fprintf(stderr, ME ": default sine wave\n");
|
||
|
}
|
||
|
|
||
|
setup_waver_end:
|
||
|
if(verbose)
|
||
|
{
|
||
|
if(common)
|
||
|
fprintf(stderr, ME ": periodic signal table of %zu samples\n", common);
|
||
|
else
|
||
|
fprintf(stderr, ME ": live signal generation\n");
|
||
|
}
|
||
|
if(phase)
|
||
|
free(phase);
|
||
|
if(id)
|
||
|
free(id);
|
||
|
if(freq)
|
||
|
free(freq);
|
||
|
if(freq_real)
|
||
|
free(freq_real);
|
||
|
}
|
||
|
|
||
|
|
||
|
void push_output(unsigned char *buf, size_t samples)
|
||
|
{
|
||
|
errno = 0;
|
||
|
size_t bytes = samples*pcmframe;
|
||
|
|
||
|
if(byte_out_flags & byte_big)
|
||
|
syn123_host2be( buf, pcmframe/channels, samples*channels);
|
||
|
if(byte_out_flags & byte_little)
|
||
|
syn123_host2le( buf, pcmframe/channels, samples*channels);
|
||
|
if(byte_out_flags & byte_swap)
|
||
|
syn123_swap_bytes(buf, pcmframe/channels, samples*channels);
|
||
|
|
||
|
mdebug("playing %zu bytes", bytes);
|
||
|
check_fatal_output(out123_play(ao, buf, bytes) < (int)bytes);
|
||
|
if(also_stdout && INT123_unintr_fwrite(buf, pcmframe, samples, stdout) < samples)
|
||
|
{
|
||
|
if(!quiet)
|
||
|
error1( "failed to copy stream to stdout: %s", INT123_strerror(errno));
|
||
|
safe_exit(133);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Clipping helper, called with mixbuf, resampler buffer, playback buffer ...
|
||
|
// This implies the output channel count.
|
||
|
void clip(void *buf, int enc, size_t samples)
|
||
|
{
|
||
|
size_t clipped = soft_clip
|
||
|
? syn123_soft_clip( buf, enc, samples*channels
|
||
|
, clip_limit, clip_width, waver )
|
||
|
: syn123_clip(buf, enc, samples*channels);
|
||
|
if(verbose > 1 && clipped)
|
||
|
fprintf(stderr, ME ": explicitly clipped %zu samples\n", clipped);
|
||
|
}
|
||
|
|
||
|
static int had_something = 0;
|
||
|
static int just_stdin = 0;
|
||
|
FILE* open_next_file(int argc, char** argv, int firstrun)
|
||
|
{
|
||
|
FILE *in = NULL;
|
||
|
if(firstrun && loptind >= argc)
|
||
|
{
|
||
|
just_stdin = 1;
|
||
|
had_something = 1;
|
||
|
in = stdin;
|
||
|
} else
|
||
|
while(!in && loptind < argc)
|
||
|
{
|
||
|
char *filename = argv[loptind++];
|
||
|
errno = 0;
|
||
|
in = strcmp(filename, "-") ? INT123_compat_fopen(filename, "rb") : stdin;
|
||
|
if(!in)
|
||
|
merror( "Failed to open input file '%s' (%s), ignoring."
|
||
|
, filename, INT123_strerror(errno) );
|
||
|
else
|
||
|
had_something = 1;
|
||
|
}
|
||
|
return in;
|
||
|
}
|
||
|
|
||
|
/* return 1 on success, 0 on failure */
|
||
|
int play_frame(void)
|
||
|
{
|
||
|
size_t got_samples = 0;
|
||
|
size_t get_samples = pcmblock;
|
||
|
debug("play_frame");
|
||
|
if(timelimit >= 0)
|
||
|
{
|
||
|
if(offset >= timelimit)
|
||
|
return 0;
|
||
|
else if(timelimit < offset+get_samples)
|
||
|
get_samples = (off_t)timelimit-offset;
|
||
|
}
|
||
|
if(generate)
|
||
|
got_samples = syn123_read(waver, inaudio, get_samples*pcminframe)
|
||
|
/ pcminframe;
|
||
|
else
|
||
|
got_samples = input ? fread(inaudio, pcminframe, get_samples, input) : 0;
|
||
|
/* Play what is there to play (starting with second decode_frame call!) */
|
||
|
if(!got_samples)
|
||
|
return 0;
|
||
|
|
||
|
if(byte_in_flags & byte_big)
|
||
|
syn123_be2host(inaudio, pcminframe/channels, got_samples*inputch);
|
||
|
if(byte_in_flags & byte_little)
|
||
|
syn123_le2host(inaudio, pcminframe/channels, got_samples*inputch);
|
||
|
|
||
|
if(mixaudio)
|
||
|
{
|
||
|
// All complicated cases trigger mixing/conversion into intermediate
|
||
|
// buffer.
|
||
|
// First either mix or convert into the mixing buffer.
|
||
|
if(mixmat)
|
||
|
check_fatal_syn("buffer mix", syn123_mix( mixaudio, mixenc, channels
|
||
|
, inaudio, inputenc, inputch, mixmat, got_samples, TRUE, NULL, waver ));
|
||
|
else
|
||
|
check_fatal_syn("buffer conv", syn123_conv( mixaudio, mixenc, got_samples*mixframe
|
||
|
, inaudio, inputenc, got_samples*pcminframe, NULL, NULL, waver ));
|
||
|
// Apply filters.
|
||
|
if(do_filter)
|
||
|
check_fatal_syn("buffer filter", syn123_filter( waver, mixaudio, mixenc, got_samples ));
|
||
|
// Do pre-amplification in-place.
|
||
|
if(do_preamp)
|
||
|
check_fatal_syn("buffer amp", syn123_amp( mixaudio, mixenc, got_samples*channels
|
||
|
, preamp_factor, preamp_offset, NULL, NULL ));
|
||
|
// Resampling needs another buffer.
|
||
|
if(do_resample)
|
||
|
{
|
||
|
// Resampling, clipping, and conversion to end buffer in a loop
|
||
|
// of small blocks since resampling can mean a lot of output from tiny
|
||
|
// input.
|
||
|
// Mixaudio has to carry 32 bit float!
|
||
|
float * fmixaudio = (float*)mixaudio;
|
||
|
size_t insamples = got_samples;
|
||
|
size_t inoff = 0;
|
||
|
while(insamples)
|
||
|
{
|
||
|
size_t inblock = insamples < resample_block
|
||
|
? insamples
|
||
|
: resample_block;
|
||
|
debug("resample");
|
||
|
size_t outsamples = syn123_resample( waver
|
||
|
, resaudio, fmixaudio+inoff*channels, inblock );
|
||
|
debug("clip?");
|
||
|
if(do_clip)
|
||
|
clip(resaudio, MPG123_ENC_FLOAT_32, outsamples);
|
||
|
// Damn, another loop inside the loop for a smaller output buffer?!
|
||
|
// No. I will ensure pcmblock being as large as the largest to be
|
||
|
// expected resampling block output!
|
||
|
size_t clipped = 0;
|
||
|
debug("conv");
|
||
|
check_fatal_syn("buffer resample conv", syn123_conv( audio, encoding, outsamples*pcmframe
|
||
|
, resaudio, MPG123_ENC_FLOAT_32, outsamples*sizeof(float)*channels
|
||
|
, NULL, &clipped, waver ));
|
||
|
if(verbose > 1 && clipped)
|
||
|
fprintf(stderr, ME ": clipped %zu samples\n", clipped);
|
||
|
// Finally, some output!
|
||
|
push_output(audio, outsamples);
|
||
|
// Advance.
|
||
|
insamples -= inblock;
|
||
|
inoff += inblock;
|
||
|
if(intflag)
|
||
|
return 1; // 1 or 0? Does it matter?
|
||
|
}
|
||
|
} else
|
||
|
{
|
||
|
// Just handle explicit clipping and do final conversion.
|
||
|
if(do_clip)
|
||
|
clip(mixaudio, mixenc, got_samples);
|
||
|
// Finally, convert to output.
|
||
|
size_t clipped = 0;
|
||
|
check_fatal_syn("buffer conv", syn123_conv( audio, encoding, got_samples*pcmframe
|
||
|
, mixaudio, mixenc, got_samples*mixframe, NULL, &clipped, waver ));
|
||
|
if(verbose > 1 && clipped)
|
||
|
fprintf(stderr, ME ": clipped %zu samples\n", clipped);
|
||
|
}
|
||
|
} else
|
||
|
{
|
||
|
size_t clipped = 0;
|
||
|
// No separate mixing buffer, but possibly some mixing or conversion
|
||
|
// directly into the output buffer. This path should only be taken if
|
||
|
// there is either only one operation happening, or if the output
|
||
|
// is in floating point encoding which makes intermediate conversions
|
||
|
// unnecessary. The path should still be correct for other cases, just
|
||
|
// slow and with suboptimal quality.
|
||
|
if(inaudio != audio)
|
||
|
{
|
||
|
if(mixmat)
|
||
|
{
|
||
|
check_fatal_syn("direct mix", syn123_mix( audio, encoding, channels
|
||
|
, inaudio, inputenc, inputch, mixmat, got_samples, TRUE, &clipped
|
||
|
, waver ));
|
||
|
} else
|
||
|
{
|
||
|
check_fatal_syn("direct conv", syn123_conv( audio, encoding, got_samples*pcmframe
|
||
|
, inaudio, inputenc, got_samples*pcminframe, NULL, &clipped
|
||
|
, waver ));
|
||
|
}
|
||
|
}
|
||
|
if(do_preamp)
|
||
|
{
|
||
|
check_fatal_syn("preamp", syn123_amp (audio, encoding, got_samples*channels
|
||
|
, preamp_factor, preamp_offset, &clipped, waver ));
|
||
|
if(verbose > 1 && clipped)
|
||
|
fprintf(stderr, ME ": clipped %zu samples\n", clipped);
|
||
|
}
|
||
|
if(do_clip)
|
||
|
clip(audio, encoding, got_samples);
|
||
|
}
|
||
|
|
||
|
// To recap: Without any conversion, the simplest code path has
|
||
|
// optional preamp and possibly clipping, but only one of those.
|
||
|
// The next stage is a combination, mixing or conversion before
|
||
|
// those. If resampling is desired, the complex
|
||
|
// code path with mixing buffer and resampling buffer is chosen, with
|
||
|
// all the other bits. Soft clipping is probably a default with resampling,
|
||
|
// for integer output. Also, if soft clip and preamp is desired for
|
||
|
// integer output, this also triggers the complex code path. I rather
|
||
|
// create separate copies than to convert from/to integers multiple
|
||
|
// times.
|
||
|
|
||
|
// The resampling loop triggers its own output. All other
|
||
|
// paths want output of the prepared data here.
|
||
|
if(!do_resample)
|
||
|
push_output(audio, got_samples);
|
||
|
offset += got_samples;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#if !defined(WIN32) && !defined(GENERIC)
|
||
|
static void catch_interrupt(int sig)
|
||
|
{
|
||
|
intflag = TRUE;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void *fatal_malloc(size_t bytes)
|
||
|
{
|
||
|
void *buf;
|
||
|
if(!(buf = malloc(bytes)))
|
||
|
{
|
||
|
if(!quiet)
|
||
|
error("OOM");
|
||
|
safe_exit(1);
|
||
|
}
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
// Prepare all the buffers and some of the flags for the processing chain.
|
||
|
// The goal is to avoid unnecessary copies and format conversions.
|
||
|
// If we do multiple operations on the intermediate data, it gets a separate
|
||
|
// buffer in floating point encoding. If there is not much to do, this
|
||
|
// intermediate copy is skipped.
|
||
|
static void setup_processing(void)
|
||
|
{
|
||
|
pcminframe = out123_encsize(inputenc)*inputch;
|
||
|
pcmframe = out123_encsize(encoding)*channels;
|
||
|
size_t resample_out = 0;
|
||
|
|
||
|
unsigned int op_count = 0;
|
||
|
|
||
|
// Full mixing is initiated if channel counts differ or a non-empty
|
||
|
// mixing matrix has been specified.
|
||
|
if(inputch != channels || (mixmat_string && mixmat_string[0]))
|
||
|
{
|
||
|
mixmat = fatal_malloc(sizeof(double)*inputch*channels);
|
||
|
size_t mmcount = (mixmat_string && mixmat_string[0])
|
||
|
? mytok_count(mixmat_string)
|
||
|
: 0;
|
||
|
// Special cases of trivial down/upmixing need no user input.
|
||
|
if(mmcount == 0 && inputch == 1)
|
||
|
{
|
||
|
for(int oc=0; oc<channels; ++oc)
|
||
|
mixmat[oc] = 1.;
|
||
|
}
|
||
|
else if(mmcount == 0 && channels == 1)
|
||
|
{
|
||
|
for(int ic=0; ic<inputch; ++ic)
|
||
|
mixmat[ic] = 1./inputch;
|
||
|
}
|
||
|
else if(mmcount != inputch*channels)
|
||
|
{
|
||
|
merror( "Need %i mixing matrix entries, got %zu."
|
||
|
, inputch*channels, mmcount );
|
||
|
safe_exit(1);
|
||
|
} else
|
||
|
{
|
||
|
char *next = mixmat_string;
|
||
|
for(int i=0; i<inputch*channels; ++i)
|
||
|
{
|
||
|
char *tok = mytok(&next);
|
||
|
mixmat[i] = tok ? atof(tok) : 0.;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(inputrate != outputrate)
|
||
|
{
|
||
|
do_resample = TRUE;
|
||
|
++op_count;
|
||
|
// Buffer computations only make sense if resampling possibly can
|
||
|
// be set up at all.
|
||
|
if( inputrate > syn123_resample_maxrate()
|
||
|
|| outputrate > syn123_resample_maxrate() )
|
||
|
{
|
||
|
error("Sampling rates out of range for the resampler.");
|
||
|
safe_exit(134);
|
||
|
}
|
||
|
// Settle resampling block size, which determines the resampling
|
||
|
// buffer size.
|
||
|
// First attempt: Try if pcmblock input yields a reasonable
|
||
|
// number of samples/frames for the resampling output.
|
||
|
// if it is too large, reduce until we are below a threshold.
|
||
|
// How big should the threshold be? When I say 10 times pcmblock ...
|
||
|
// I still to the channel count as variable ... but let's say that.
|
||
|
// Then the baddest resampling ratio that could barely work is about
|
||
|
// 1:10000. That's quite generous. Even fixing things at pcmblock
|
||
|
// max in the output would still be ok for factors close to 1000.
|
||
|
// Well, let's try this to stay reasonable: limit to 10*pcmblock
|
||
|
// at first. If that is not enough, reduce resample_block down to
|
||
|
// 100 samples or so, to avoid too degenerate situations. If
|
||
|
// that lower limit of resample_block is reached and still produces
|
||
|
// more than 10*pcmblock output, it is attempted to just allocate
|
||
|
// that bigger buffer. If you have the memory, you can choose the
|
||
|
// rates. Reasonable resampling tasks should be well within the
|
||
|
// factor 10 allowed for by default.
|
||
|
// Another question: Should I try to increase pcmblock for the case of
|
||
|
// extreme downsampling? This is just a matter of efficiency, having
|
||
|
// lots of code only producing a single sample now and then, the
|
||
|
// block treatment in libsyn123 not getting active.
|
||
|
resample_block = pcmblock;
|
||
|
while( (resample_out = syn123_resample_count( inputrate, outputrate
|
||
|
, resample_block )) > 10*pcmblock)
|
||
|
resample_block /= 2;
|
||
|
if(resample_block < 128)
|
||
|
{
|
||
|
resample_block = 128;
|
||
|
resample_out = syn123_resample_count(inputrate, outputrate, resample_block);
|
||
|
}
|
||
|
if(verbose)
|
||
|
fprintf( stderr, ME": resampling %zu samples @ %ld Hz"
|
||
|
" to up to %zu samples @ %ld Hz\n", resample_block, inputrate
|
||
|
, resample_out, outputrate );
|
||
|
if(!resample_out)
|
||
|
{
|
||
|
error("Cannot compute resampler output count.");
|
||
|
safe_exit(134);
|
||
|
}
|
||
|
// Now we got some number of resampler output samples.
|
||
|
// That buffer will always be big enough when handing in resample_block.
|
||
|
resaudio = fatal_malloc(resample_out*sizeof(float)*channels);
|
||
|
}
|
||
|
|
||
|
audio = fatal_malloc((resample_out>pcmblock ? resample_out : pcmblock)*pcmframe);
|
||
|
|
||
|
// If converting or mixing, use separate input buffer.
|
||
|
if(inputenc != encoding || mixmat)
|
||
|
{
|
||
|
inaudio = fatal_malloc(pcmblock*pcminframe);
|
||
|
++op_count; // conversion or mixing
|
||
|
}
|
||
|
else
|
||
|
inaudio = audio;
|
||
|
|
||
|
if(preamp != 0. || preamp_offset != 0.)
|
||
|
{
|
||
|
preamp_factor = syn123_db2lin(preamp);
|
||
|
// Store limited value for proper reporting.
|
||
|
preamp = syn123_lin2db(preamp_factor);
|
||
|
if(preamp_offset == 0. && mixmat)
|
||
|
{
|
||
|
// If we are mixing already, just include preamp in this.
|
||
|
for(int i=0; i<inputch*channels; ++i)
|
||
|
mixmat[i] *= preamp_factor;
|
||
|
preamp_factor = 1.;
|
||
|
}
|
||
|
do_preamp = TRUE;
|
||
|
++op_count;
|
||
|
}
|
||
|
|
||
|
do_clip = FALSE;
|
||
|
if(!strcasecmp(clip_mode, "soft"))
|
||
|
{
|
||
|
do_clip = TRUE;
|
||
|
soft_clip = TRUE;
|
||
|
}
|
||
|
else if(!strcasecmp(clip_mode, "hard"))
|
||
|
{
|
||
|
if(encoding & MPG123_ENC_FLOAT)
|
||
|
do_clip = TRUE;
|
||
|
soft_clip = FALSE;
|
||
|
}
|
||
|
else if(strcasecmp(clip_mode, "implicit"))
|
||
|
{
|
||
|
if(!quiet)
|
||
|
error1("Bad value for clipping mode given: %s\n", clip_mode);
|
||
|
safe_exit(135);
|
||
|
}
|
||
|
if(do_clip)
|
||
|
++op_count;
|
||
|
|
||
|
if(filter_spec && *filter_spec)
|
||
|
{
|
||
|
do_filter = TRUE;
|
||
|
++op_count;
|
||
|
}
|
||
|
|
||
|
if(do_filter || do_resample || op_count > 1)
|
||
|
{
|
||
|
// Create a separate mixing buffer for the complicated cases.
|
||
|
// Resampling limits quality to 32 bits float anyway, so avoid
|
||
|
// trying to mix in double.
|
||
|
debug("employing separate mixing buffer");
|
||
|
mixenc = do_resample
|
||
|
? MPG123_ENC_FLOAT_32
|
||
|
: syn123_mixenc(inputenc, encoding);
|
||
|
mixframe = out123_encsize(mixenc)*channels;
|
||
|
mixaudio = fatal_malloc(mixframe*pcmblock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
int main(int sys_argc, char ** sys_argv)
|
||
|
{
|
||
|
int result;
|
||
|
INT123_compat_binmode(STDIN_FILENO, TRUE);
|
||
|
check_locale();
|
||
|
|
||
|
#if defined (WANT_WIN32_UNICODE)
|
||
|
if(win32_cmdline_utf8(&argc, &argv) != 0)
|
||
|
{
|
||
|
error("Cannot convert command line to UTF8!");
|
||
|
safe_exit(76);
|
||
|
}
|
||
|
#else
|
||
|
argv = sys_argv;
|
||
|
argc = sys_argc;
|
||
|
#endif
|
||
|
|
||
|
if(!(fullprogname = INT123_compat_strdup(argv[0])))
|
||
|
{
|
||
|
error("OOM"); /* Out Of Memory. Don't waste bytes on that error. */
|
||
|
safe_exit(1);
|
||
|
}
|
||
|
/* Extract binary and path, take stuff before/after last / or \ . */
|
||
|
if( (cmd_name = strrchr(fullprogname, '/'))
|
||
|
|| (cmd_name = strrchr(fullprogname, '\\')))
|
||
|
{
|
||
|
/* There is some explicit path. */
|
||
|
cmd_name[0] = 0; /* End byte for path. */
|
||
|
cmd_name++;
|
||
|
binpath = fullprogname;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cmd_name = fullprogname; /* No path separators there. */
|
||
|
binpath = NULL; /* No path at all. */
|
||
|
}
|
||
|
|
||
|
/* Get default flags. */
|
||
|
{
|
||
|
out123_handle *paro = out123_new();
|
||
|
out123_getparam_int(paro, OUT123_FLAGS, &outflags);
|
||
|
out123_del(paro);
|
||
|
}
|
||
|
|
||
|
#ifdef __OS2__
|
||
|
_wildcard(&argc,&argv);
|
||
|
#endif
|
||
|
|
||
|
while ((result = getlopt(argc, argv, opts)))
|
||
|
switch (result) {
|
||
|
case GLO_UNKNOWN:
|
||
|
fprintf (stderr, ME": invalid argument: %s\n", loptarg);
|
||
|
usage(1);
|
||
|
case GLO_NOARG:
|
||
|
fprintf (stderr, ME": missing argument for parameter: %s\n", loptarg);
|
||
|
usage(1);
|
||
|
case GLO_BADARG:
|
||
|
fprintf(stderr, ME": bad option argument: %s\n", loptarg);
|
||
|
usage(1);
|
||
|
}
|
||
|
finish_endian();
|
||
|
mdebug("input byte flags: %ld, output byte flags: %ld", byte_in_flags, byte_out_flags);
|
||
|
|
||
|
if(quiet)
|
||
|
verbose = 0;
|
||
|
|
||
|
switch(runmode)
|
||
|
{
|
||
|
case RUN_MAIN:
|
||
|
break;
|
||
|
case LIST_MODULES:
|
||
|
list_output_modules();
|
||
|
case LIST_DEVICES:
|
||
|
list_output_devices();
|
||
|
default:
|
||
|
merror("Invalid run mode %d", runmode);
|
||
|
safe_exit(79);
|
||
|
}
|
||
|
|
||
|
if(inputrate < 1)
|
||
|
inputrate = rate;
|
||
|
outputrate = -1;
|
||
|
if(speed > 0)
|
||
|
{ // Compute rate from speed and safely convert to long.
|
||
|
double outputrater = (1./speed) * rate;
|
||
|
double limit = (double)syn123_resample_maxrate();
|
||
|
double diff = 1;
|
||
|
// LONG_MAX will likely be rounded in the conversion to float.
|
||
|
// Ensure that the resulting limit is _smaller_ by subtracting
|
||
|
// until we see a rounding step down.
|
||
|
while((limit-diff) == limit)
|
||
|
diff *= 2;
|
||
|
limit -= diff;
|
||
|
if(outputrater+0.5 > limit)
|
||
|
outputrate = syn123_resample_maxrate();
|
||
|
else if(outputrater > 0)
|
||
|
outputrate = (long)(outputrater+0.5);
|
||
|
}
|
||
|
if(outputrate < 1)
|
||
|
{
|
||
|
fprintf(stderr, ME": no valid output rate with given speed and input rate\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
/* Ensure cleanup before we cause too much mess. */
|
||
|
#if !defined(WIN32) && !defined(GENERIC)
|
||
|
INT123_catchsignal(SIGINT, catch_interrupt);
|
||
|
INT123_catchsignal(SIGTERM, catch_interrupt);
|
||
|
#endif
|
||
|
ao = out123_new();
|
||
|
if(!ao){ error("Failed to allocate output."); exit(1); }
|
||
|
|
||
|
if
|
||
|
( 0
|
||
|
|| out123_param_int(ao, OUT123_FLAGS, outflags)
|
||
|
|| out123_param_float(ao, OUT123_PRELOAD, preload)
|
||
|
|| out123_param_int(ao, OUT123_VERBOSE, verbose)
|
||
|
|| out123_param_string(ao, OUT123_NAME, name)
|
||
|
|| out123_param_string(ao, OUT123_BINDIR, binpath)
|
||
|
|| out123_param_float(ao, OUT123_DEVICEBUFFER, device_buffer)
|
||
|
)
|
||
|
{
|
||
|
error("Error setting output parameters. Do you need a usage reminder?");
|
||
|
usage(1);
|
||
|
}
|
||
|
|
||
|
#ifdef HAVE_SETPRIORITY
|
||
|
if(aggressive) { /* tst */
|
||
|
int mypid = getpid();
|
||
|
if(!quiet) fprintf(stderr, ME": Aggressively trying to increase priority.\n");
|
||
|
if(setpriority(PRIO_PROCESS,mypid,-20))
|
||
|
error("Failed to aggressively increase priority.\n");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if defined (HAVE_SCHED_SETSCHEDULER) && !defined (__CYGWIN__) && !defined (HAVE_WINDOWS_H)
|
||
|
/* Cygwin --realtime seems to fail when accessing network, using win32 set priority instead */
|
||
|
/* MinGW may have pthread installed, we prefer win32API */
|
||
|
if(realtime)
|
||
|
{ /* Get real-time priority */
|
||
|
struct sched_param sp;
|
||
|
if(!quiet) fprintf(stderr, ME": Getting real-time priority\n");
|
||
|
memset(&sp, 0, sizeof(struct sched_param));
|
||
|
sp.sched_priority = sched_get_priority_min(SCHED_FIFO);
|
||
|
if (sched_setscheduler(0, SCHED_RR, &sp) == -1)
|
||
|
error("Can't get realtime priority\n");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* make sure not Cygwin, it doesn't need it */
|
||
|
#if defined(WIN32) && defined(HAVE_WINDOWS_H)
|
||
|
/* argument "3" is equivalent to realtime priority class */
|
||
|
win32_set_priority(realtime ? 3 : w32_priority);
|
||
|
#endif
|
||
|
|
||
|
if(encoding_name)
|
||
|
{
|
||
|
encoding = out123_enc_byname(encoding_name);
|
||
|
if(encoding < 0)
|
||
|
{
|
||
|
error1("Unknown encoding '%s' given!\n", encoding_name);
|
||
|
safe_exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(strcmp(signal_source, "file"))
|
||
|
generate = TRUE;
|
||
|
else
|
||
|
input = open_next_file(argc, argv, 1);
|
||
|
|
||
|
// Genererally generate signal in floating point for later conversion
|
||
|
// after possible additional processing.
|
||
|
inputenc = generate && encoding != MPG123_ENC_FLOAT_64
|
||
|
? MPG123_ENC_FLOAT_32
|
||
|
: encoding;
|
||
|
// User can override.
|
||
|
if(inputenc_name)
|
||
|
{
|
||
|
inputenc = out123_enc_byname(inputenc_name);
|
||
|
if(inputenc < 0)
|
||
|
{
|
||
|
error1("Unknown input encoding '%s' given!\n", inputenc_name);
|
||
|
safe_exit(1);
|
||
|
}
|
||
|
}
|
||
|
if(!inputch)
|
||
|
inputch = channels;
|
||
|
|
||
|
// Settle buffers and the mixing/conversion code path.
|
||
|
setup_processing();
|
||
|
check_fatal_output(out123_set_buffer(ao, buffer_kb*1024));
|
||
|
check_fatal_output(out123_open(ao, driver, device));
|
||
|
|
||
|
if(timelimit_seconds >= 0.)
|
||
|
timelimit = timelimit_seconds*inputrate;
|
||
|
if(timelimit_samples >= 0)
|
||
|
timelimit = timelimit_samples;
|
||
|
|
||
|
if(verbose)
|
||
|
{
|
||
|
long props = 0;
|
||
|
const char *encname;
|
||
|
char *realname = NULL;
|
||
|
if(inaudio != audio)
|
||
|
{
|
||
|
encname = out123_enc_name(inputenc);
|
||
|
fprintf( stderr, ME": input format: %li Hz, %i channels, %s\n"
|
||
|
, inputrate, inputch, encname ? encname : "???" );
|
||
|
encname = out123_enc_name(syn123_mixenc(inputenc, encoding));
|
||
|
if(mixmat)
|
||
|
{
|
||
|
fprintf( stderr, ME": mixing in %s\n", encname ? encname : "???" );
|
||
|
for(int oc=0; oc<channels; ++oc)
|
||
|
{
|
||
|
fprintf(stderr, ME": out ch %i mix:", oc);
|
||
|
for(int ic=0; ic<inputch; ++ic)
|
||
|
fprintf(stderr, " %6.2f", mixmat[SYN123_IOFF(oc,ic,inputch)]);
|
||
|
fprintf(stderr, "\n");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
fprintf( stderr, ME": converting via %s\n", encname ? encname : "???" );
|
||
|
}
|
||
|
if(outputrate != rate)
|
||
|
fprintf( stderr, ME": virtual output rate %li for actual speed %g\n"
|
||
|
, outputrate, (double)rate/outputrate );
|
||
|
encname = out123_enc_name(encoding);
|
||
|
fprintf(stderr, ME": format: %li Hz, %i channels, %s\n"
|
||
|
, rate, channels, encname ? encname : "???" );
|
||
|
if(preamp != 0.)
|
||
|
fprintf( stderr, ME": preamp: %.1f dB%s\n", preamp
|
||
|
, preamp_factor != 1. ? "" : " (during mixing)" );
|
||
|
if(preamp_offset != 0.)
|
||
|
fprintf(stderr, ME": applying scaled offset: %f\n", preamp_offset);
|
||
|
out123_getparam_string(ao, OUT123_NAME, &realname);
|
||
|
if(realname)
|
||
|
fprintf(stderr, ME": output real name: %s\n", realname);
|
||
|
out123_getparam_int(ao, OUT123_PROPFLAGS, &props);
|
||
|
if(props & OUT123_PROP_LIVE)
|
||
|
fprintf(stderr, ME": This is a live sink.\n");
|
||
|
}
|
||
|
check_fatal_output(out123_start(ao, rate, channels, encoding));
|
||
|
|
||
|
setup_wavegen(); // Used also for conversion/mixing.
|
||
|
|
||
|
do
|
||
|
{
|
||
|
while(play_frame() && !intflag)
|
||
|
{
|
||
|
/* be happy */
|
||
|
}
|
||
|
if(!intflag && !generate && !just_stdin)
|
||
|
{
|
||
|
if(input && input != stdin)
|
||
|
INT123_compat_fclose(input);
|
||
|
input = open_next_file(argc, argv, 0);
|
||
|
}
|
||
|
} while(!intflag && !generate && input && !just_stdin);
|
||
|
if(intflag) /* Make it quick! */
|
||
|
{
|
||
|
if(!quiet)
|
||
|
fprintf(stderr, ME": Interrupted. Dropping the ball.\n");
|
||
|
out123_drop(ao);
|
||
|
}
|
||
|
|
||
|
safe_exit(had_something ? 0 : 2); /* That closes output and restores terminal, too. */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static char* output_enclist(void)
|
||
|
{
|
||
|
int i;
|
||
|
char *list = NULL;
|
||
|
char *pos;
|
||
|
size_t len = 0;
|
||
|
int enc_count = 0;
|
||
|
int *enc_codes = NULL;
|
||
|
|
||
|
enc_count = out123_enc_list(&enc_codes);
|
||
|
if(enc_count < 0 || !enc_codes)
|
||
|
return NULL;
|
||
|
for(i=0;i<enc_count;++i)
|
||
|
len += strlen(out123_enc_name(enc_codes[i]));
|
||
|
len += enc_count;
|
||
|
|
||
|
if((pos = list = malloc(len))) for(i=0;i<enc_count;++i)
|
||
|
{
|
||
|
const char *name = out123_enc_name(enc_codes[i]);
|
||
|
if(i>0)
|
||
|
*(pos++) = ' ';
|
||
|
strcpy(pos, name);
|
||
|
pos+=strlen(name);
|
||
|
}
|
||
|
free(enc_codes);
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
static void print_title(FILE *o)
|
||
|
{
|
||
|
fprintf(o, "Simple audio output with raw PCM input\n");
|
||
|
fprintf(o, "\tversion %s; derived from mpg123 by Michael Hipp and others\n", MPG123_VERSION);
|
||
|
fprintf(o, "\tfree software (LGPL) without any warranty but with best wishes\n");
|
||
|
}
|
||
|
|
||
|
static void usage(int err) /* print syntax & exit */
|
||
|
{
|
||
|
FILE* o = stdout;
|
||
|
if(err)
|
||
|
{
|
||
|
o = stderr;
|
||
|
fprintf(o, ME": You made some mistake in program usage... let me briefly remind you:\n\n");
|
||
|
}
|
||
|
print_title(o);
|
||
|
fprintf(o,"\nusage: %s [option(s)] [file(s) | URL(s) | -]\n", cmd_name);
|
||
|
fprintf(o,"supported options [defaults in brackets]:\n");
|
||
|
fprintf(o," -v increase verbosity level -q quiet (only print errors)\n");
|
||
|
fprintf(o," -t testmode (no output) -s write to stdout\n");
|
||
|
fprintf(o," -w f write output as WAV file\n");
|
||
|
fprintf(o," -b n output buffer: n Kbytes [0] \n");
|
||
|
fprintf(o," -r n set samplerate [44100]\n");
|
||
|
fprintf(o," -o m select output module -a d set audio device\n");
|
||
|
fprintf(o," -m single-channel (mono) instead of stereo\n");
|
||
|
#ifdef HAVE_SCHED_SETSCHEDULER
|
||
|
fprintf(o," -T get realtime priority\n");
|
||
|
#endif
|
||
|
fprintf(o," -? this help --version print name + version\n");
|
||
|
fprintf(o,"See the manpage out123(1) or call %s with --longhelp for more parameters and information.\n", cmd_name);
|
||
|
safe_exit(err);
|
||
|
}
|
||
|
|
||
|
static void want_usage(char* arg, topt *opts)
|
||
|
{
|
||
|
usage(0);
|
||
|
}
|
||
|
|
||
|
static void long_usage(int err)
|
||
|
{
|
||
|
char *enclist;
|
||
|
FILE* o = stdout;
|
||
|
if(err)
|
||
|
{
|
||
|
o = stderr;
|
||
|
fprintf(o, "You made some mistake in program usage... let me remind you:\n\n");
|
||
|
}
|
||
|
enclist = output_enclist();
|
||
|
print_title(o);
|
||
|
fprintf(o,"\nusage: %s [option(s)] [file(s) | -]\n", cmd_name);
|
||
|
|
||
|
fprintf(o," --name <n> set instance name (p.ex. JACK client)\n");
|
||
|
fprintf(o," -o <o> --output <o> select audio output module\n");
|
||
|
fprintf(o," --list-modules list the available modules\n");
|
||
|
fprintf(o," --list-devices list the available output devices for given output module\n");
|
||
|
fprintf(o," -a <d> --audiodevice <d> select audio device (for files, empty or - is stdout)\n");
|
||
|
fprintf(o," -s --stdout write raw audio to stdout (-o raw -a -)\n");
|
||
|
fprintf(o," -S --STDOUT play AND output stream to stdout\n");
|
||
|
fprintf(o," -O <f> --outfile <f> raw output to given file (-o raw -a <f>)\n");
|
||
|
fprintf(o," -w <f> --wav <f> write samples as WAV file in <f> (-o wav -a <f>)\n");
|
||
|
fprintf(o," --au <f> write samples as Sun AU file in <f> (-o au -a <f>)\n");
|
||
|
fprintf(o," --cdr <f> write samples as raw CD audio file in <f> (-o cdr -a <f>)\n");
|
||
|
fprintf(o," -r <r> --rate <r> set the audio output rate in Hz (default 44100)\n");
|
||
|
fprintf(o," -R <r> --inputrate <r> set intput rate in Hz for conversion (if > 0)\n"
|
||
|
" (always last operation before output)\n");
|
||
|
fprintf(o," --speed <f> play at given speed factor by resampling\n");
|
||
|
fprintf(o," --resample <s> set resampler method (fine (default) or dirty)\n");
|
||
|
fprintf(o," -c <n> --channels <n> set channel count to <n>\n");
|
||
|
fprintf(o," -m --mono set output channel count to 1\n");
|
||
|
fprintf(o," --stereo set output channel count to 2 (default)\n");
|
||
|
fprintf(o," -C <n> --inputch <n> set input channel count for conversion\n");
|
||
|
fprintf(o," -e <c> --encoding <c> set output encoding (%s)\n"
|
||
|
, enclist != NULL ? enclist : "OOM!");
|
||
|
fprintf(o," --endian <s> set output endianess: big, little, native (default)\n");
|
||
|
fprintf(o," -E <c> --inputenc <c> set input encoding for conversion\n");
|
||
|
fprintf(o," --inputend <s> set input endianess: big, little, or native\n");
|
||
|
fprintf(o," (default being same as output endianess)\n");
|
||
|
fprintf(o," --byteswap swap byte order at end (possibly again)\n");
|
||
|
fprintf(o," --list-encodings list of encoding short and long names\n");
|
||
|
fprintf(o," --mix <m> mixing matrix <m> between input and output channels\n");
|
||
|
fprintf(o," as linear factors, comma separated list for output\n");
|
||
|
fprintf(o," channel 1, then 2, ... default unity if channel counts\n");
|
||
|
fprintf(o," match, 0.5,0.5 for stereo to mono, 1,1 for the other way\n");
|
||
|
fprintf(o," --filter <coeff> apply filter(s) before preamp stage, with coeff as\n");
|
||
|
fprintf(o," b_0,...,b_N,a_0,...,a_N (a_0=1 is mandatory) and\n");
|
||
|
fprintf(o," multiple filters separated by ':'.\n");
|
||
|
fprintf(o," -P <p> --preamp <p> amplify signal with <p> dB before output\n");
|
||
|
fprintf(o," --offset <o> apply PCM offset (floating point scaled in [-1:1]");
|
||
|
fprintf(o," --clip <s> select clipping mode: soft or hard for forced\n"
|
||
|
" clipping also for floating point output, implicit\n"
|
||
|
" (default) for implied clipping during conversion\n" );
|
||
|
fprintf(o," --dither enable dithering for conversions to integer\n");
|
||
|
fprintf(o," --test-format return 0 if audio format set by preceeding options is supported\n");
|
||
|
fprintf(o," --test-encodings print out possible encodings with given channels/rate\n");
|
||
|
fprintf(o," --query-format print out default format for given device, if any\n");
|
||
|
fprintf(o," -o h --headphones (aix/hp/sun) output on headphones\n");
|
||
|
fprintf(o," -o s --speaker (aix/hp/sun) output on speaker\n");
|
||
|
fprintf(o," -o l --lineout (aix/hp/sun) output to lineout\n");
|
||
|
#ifndef NOXFERMEM
|
||
|
fprintf(o," -b <n> --buffer <n> set play buffer (\"output cache\")\n");
|
||
|
fprintf(o," --preload <value> fraction of buffer to fill before playback\n");
|
||
|
#endif
|
||
|
fprintf(o," --devbuffer <s> set device buffer in seconds; <= 0 means default\n");
|
||
|
fprintf(o," --timelimit <s> set time limit in PCM samples if >= 0\n");
|
||
|
fprintf(o," --seconds <s> set time limit in seconds if >= 0\n");
|
||
|
fprintf(o," --source <s> choose signal source: file (default),\n");
|
||
|
fprintf(o," wave, sweep, pink, geiger (implied by\n");
|
||
|
fprintf(o," --wave-freq, --wave-sweep,\n");
|
||
|
fprintf(o," --pink-rows, --geiger-activity), or white for\n");
|
||
|
fprintf(o," white noise\n");
|
||
|
fprintf(o," --wave-freq <f> set wave generator frequency or list of those\n");
|
||
|
fprintf(o," with comma separation for enabling a generated\n");
|
||
|
fprintf(o," test signal instead of standard input,\n");
|
||
|
fprintf(o," empty value repeating the previous\n");
|
||
|
fprintf(o," --wave-pat <p> set wave pattern(s) (out of those:\n");
|
||
|
{
|
||
|
int i=0;
|
||
|
const char* wn;
|
||
|
while((wn=syn123_wave_name(i++)) && wn[0] != '?')
|
||
|
fprintf(o, " %s\n", wn);
|
||
|
}
|
||
|
fprintf(o," ),\n");
|
||
|
fprintf(o," empty value repeating the previous\n");
|
||
|
fprintf(o," --wave-phase <p> set wave phase shift(s), negative values\n");
|
||
|
fprintf(o," inverting the pattern in time and\n");
|
||
|
fprintf(o," empty value repeating the previous,\n");
|
||
|
fprintf(o," --wave-direction overriding the negative bit\n");
|
||
|
fprintf(o," --wave-direction <d> set direction explicitly (the sign counts)\n");
|
||
|
fprintf(o," --wave-sweep <f> sweep a generated wave to frequency f, from\n");
|
||
|
fprintf(o," first one specified for --wave-freq, using the\n");
|
||
|
fprintf(o," first wave pattern and direction, too\n");
|
||
|
fprintf(o," --sweep-time <s> set frequency sweep duration to s seconds if > 0\n");
|
||
|
fprintf(o," (defaulting to timelimit if set, otherwise one second)\n");
|
||
|
fprintf(o," --sweep-count <c> set timelimit to exactly produce that many\n");
|
||
|
fprintf(o," (smooth) sweeps\n");
|
||
|
fprintf(o," --sweep-type <t> set sweep type: lin(ear), qua(d) (default),\n");
|
||
|
fprintf(o," exp(onential)\n");
|
||
|
fprintf(o," --sweep-hard disable post-sweep smoothing for periodicity\n");
|
||
|
fprintf(o," --genbuffer <b> buffer size (limit) for signal generators,\n");
|
||
|
fprintf(o," if > 0 (default), this enforces a periodic\n");
|
||
|
fprintf(o," buffer also for non-periodic signals, benefit:\n");
|
||
|
fprintf(o," less runtime CPU overhead\n");
|
||
|
fprintf(o," --wave-limit <l> alias for --genbuffer\n");
|
||
|
fprintf(o," --pink-rows <r> activate pink noise source and choose rows for\n");
|
||
|
fprintf(o," ` the algorithm (<1 chooses default)\n");
|
||
|
fprintf(o," --geiger-activity <a> a Geiger-Mueller counter as source, with\n");
|
||
|
fprintf(o," <a> average events per second\n");
|
||
|
fprintf(o," -t --test no output, just read and discard data (-o test)\n");
|
||
|
fprintf(o," -v[*] --verbose increase verboselevel\n");
|
||
|
#ifdef HAVE_SETPRIORITY
|
||
|
fprintf(o," --aggressive tries to get higher priority (nice)\n");
|
||
|
#endif
|
||
|
#if defined (HAVE_SCHED_SETSCHEDULER) || defined (HAVE_WINDOWS_H)
|
||
|
fprintf(o," -T --realtime tries to get realtime priority\n");
|
||
|
#endif
|
||
|
#ifdef HAVE_WINDOWS_H
|
||
|
fprintf(o," --priority <n> use specified process priority\n");
|
||
|
fprintf(o," accepts -2 to 3 as integer arguments\n");
|
||
|
fprintf(o," -2 as idle, 0 as normal and 3 as realtime.\n");
|
||
|
#endif
|
||
|
fprintf(o," -? --help give compact help\n");
|
||
|
fprintf(o," --longhelp give this long help listing\n");
|
||
|
fprintf(o," --version give name / version string\n");
|
||
|
|
||
|
fprintf(o,"\nSee the manpage out123(1) for more information. Also, note that\n");
|
||
|
fprintf(o,"any numeric arguments are parsed in C locale (pi is 3.14, not 3,14).\n");
|
||
|
free(enclist);
|
||
|
safe_exit(err);
|
||
|
}
|
||
|
|
||
|
static void want_long_usage(char* arg, topt *opts)
|
||
|
{
|
||
|
long_usage(0);
|
||
|
}
|
||
|
|
||
|
static void give_version(char* arg, topt *opts)
|
||
|
{
|
||
|
fprintf(stdout, "out123 "MPG123_VERSION"\n");
|
||
|
safe_exit(0);
|
||
|
}
|
||
|
|
||
|
static void give_libversion(char* arg, topt *opts)
|
||
|
{
|
||
|
unsigned int pl = 0;
|
||
|
unsigned int al = out123_libversion(&pl);
|
||
|
printf("libout123 from mpg123 %s, API version %u, patchlevel %u\n", out123_distversion(NULL, NULL, NULL), al, pl);
|
||
|
al = syn123_libversion(&pl);
|
||
|
printf("libsyn123 from mpg123 %s, API version %u, patchlevel %u\n", syn123_distversion(NULL, NULL, NULL), al, pl);
|
||
|
safe_exit(0);
|
||
|
}
|
||
|
|