mirror of
http://mpg123.de/trunk/.git
synced 2025-10-25 04:37:34 +03:00
out123+libsyn123: getting crazy with mixing and conversion
git-svn-id: svn://scm.orgis.org/mpg123/trunk@4423 35dc7657-300d-0410-a2e5-dc2837fedb53
This commit is contained in:
6
NEWS
6
NEWS
@@ -1,5 +1,10 @@
|
||||
1.26.0
|
||||
------
|
||||
TODO: dither flag in syn123_handle ... some place for the noise ...
|
||||
TPDF is enough, isn't it? The highpass is specific to a sampling rate.
|
||||
Not sure if buffering TPDF is worth it. It is cheap, very cheap.
|
||||
TODO: drop special optimization flags, default to just -On ... but
|
||||
consider defaulting to O3 for auto-vectorization
|
||||
TODO: avoid MPG123_NEED_MORE from non-feed reader reading frame
|
||||
bodies or see it as a feature
|
||||
- Starting to intentionaly use C99 in the codebase. API headers are still
|
||||
@@ -24,6 +29,7 @@ bodies or see it as a feature
|
||||
output.
|
||||
-- It also hosts sample format conversions as a necessity to be able to
|
||||
directly produce the format output devices need.
|
||||
-- Well, also channel mixing while we're at it.
|
||||
TODO: Make libout123 and/or mpg123 use that to convert on the fly. Optionally?
|
||||
A new incompatible version of libmpg123 would drop duplicate code for
|
||||
conversions …
|
||||
|
||||
17
configure.ac
17
configure.ac
@@ -571,6 +571,20 @@ if test "x$ieee" = xenabled; then
|
||||
AC_DEFINE(IEEE_FLOAT, 1, [ Define to indicate that float storage follows IEEE754. ])
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE(cases,
|
||||
[ --enable-cases=[yes/no] include special cases for likely parameter values (channel count, encoding sizes in libsyn123 routines) in the hope of better optimization at the expense of some code bloat (default enabled) ],
|
||||
[
|
||||
if test "x$enableval" = xyes; then
|
||||
specialcases=enabled
|
||||
else
|
||||
specialcases=disabled
|
||||
fi
|
||||
], [ specialcases=enabled ])
|
||||
|
||||
if test "x$specialcases" = xdisabled; then
|
||||
AC_DEFINE(SYN123_NO_CASES, 1, [ Define to not duplicate some code for likely cases in libsyn123. ])
|
||||
fi
|
||||
|
||||
sys_cppflags=
|
||||
newoldwritesample=disabled
|
||||
case $host in
|
||||
@@ -2498,6 +2512,9 @@ Note: Disabling core features is not commonly done and some combinations might n
|
||||
# just an empty line
|
||||
echo
|
||||
|
||||
echo " libsyn123 special cases . $specialcases"
|
||||
echo
|
||||
|
||||
echo " Modules ................. $modules"
|
||||
echo " Checked audio modules ... $check_modules
|
||||
Detected audio support ..$output_modules
|
||||
|
||||
@@ -39,16 +39,6 @@ static size_t round2size(double a)
|
||||
return a < 0 ? 0 : (size_t)(a+0.5);
|
||||
}
|
||||
|
||||
static size_t smin(size_t a, size_t b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
static size_t smax(size_t a, size_t b)
|
||||
{
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
/* fractional part, relating to frequencies (so long matches) */
|
||||
static double myfrac(double a)
|
||||
{
|
||||
@@ -597,7 +587,7 @@ syn123_read( syn123_handle *sh, void *dest, size_t dest_bytes )
|
||||
int err = syn123_conv(
|
||||
sh->workbuf[0], sh->fmt.encoding, sizeof(sh->workbuf[0])
|
||||
, sh->workbuf[1], MPG123_ENC_FLOAT_64, sizeof(double)*block
|
||||
, NULL );
|
||||
, NULL, NULL );
|
||||
if(err)
|
||||
{
|
||||
debug1("conv error: %i", err);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#define NO_GROW_BUF
|
||||
#define NO_SMAX
|
||||
#include "syn123_int.h"
|
||||
#include "sample.h"
|
||||
#include "debug.h"
|
||||
@@ -117,8 +118,8 @@ syn123_clip(void *buf, int encoding, size_t samples)
|
||||
// double or float.
|
||||
|
||||
#define FROM_FLT(type) \
|
||||
{ type *tsrc = src; type *tend = tsrc+samples; char *tdest = dest; \
|
||||
switch(dest_enc) \
|
||||
{ type *tsrc = src; type *tend = tsrc+samples; char *tdest = dst; \
|
||||
switch(dst_enc) \
|
||||
{ \
|
||||
case MPG123_ENC_SIGNED_16: \
|
||||
for(; tsrc!=tend; ++tsrc, tdest+=2) \
|
||||
@@ -181,7 +182,7 @@ switch(dest_enc) \
|
||||
}}
|
||||
|
||||
#define TO_FLT(type) \
|
||||
{ type* tdest = dest; type* tend = tdest + samples; char * tsrc = src; \
|
||||
{ type* tdest = dst; type* tend = tdest + samples; char * tsrc = src; \
|
||||
switch(src_enc) \
|
||||
{ \
|
||||
case MPG123_ENC_SIGNED_16: \
|
||||
@@ -245,27 +246,39 @@ switch(src_enc) \
|
||||
}}
|
||||
|
||||
int attribute_align_arg
|
||||
syn123_conv( void * MPG123_RESTRICT dest, int dest_enc, size_t dest_size
|
||||
, void * MPG123_RESTRICT src, int src_enc, size_t src_bytes
|
||||
, size_t *dest_bytes)
|
||||
syn123_mixenc(int encoding)
|
||||
{
|
||||
size_t inblock = MPG123_SAMPLESIZE(src_enc);
|
||||
size_t outblock = MPG123_SAMPLESIZE(dest_enc);
|
||||
size_t samples = src_bytes/inblock;
|
||||
int esize = MPG123_SAMPLESIZE(encoding);
|
||||
if(!esize)
|
||||
return 0;
|
||||
else
|
||||
return (encoding != MPG123_ENC_FLOAT_32 && esize > 3)
|
||||
? MPG123_ENC_FLOAT_64
|
||||
: MPG123_ENC_FLOAT_32;
|
||||
}
|
||||
|
||||
int attribute_align_arg
|
||||
syn123_conv( void * MPG123_RESTRICT dst, int dst_enc, size_t dst_size
|
||||
, void * MPG123_RESTRICT src, int src_enc, size_t src_bytes
|
||||
, size_t *dst_bytes, syn123_handle *sh )
|
||||
{
|
||||
size_t srcframe = MPG123_SAMPLESIZE(src_enc);
|
||||
size_t dstframe = MPG123_SAMPLESIZE(dst_enc);
|
||||
if(!srcframe || !dstframe)
|
||||
return SYN123_BAD_ENC;
|
||||
size_t samples = src_bytes/srcframe;
|
||||
debug6( "conv from %i (%i) to %i (%i), %zu into %zu bytes"
|
||||
, src_enc, MPG123_SAMPLESIZE(src_enc)
|
||||
, dest_enc, MPG123_SAMPLESIZE(dest_enc)
|
||||
, src_bytes, dest_size );
|
||||
if(!inblock || !outblock)
|
||||
return SYN123_BAD_ENC;
|
||||
if(!dest || !src)
|
||||
, dst_enc, MPG123_SAMPLESIZE(dst_enc)
|
||||
, src_bytes, dst_size );
|
||||
if(!dst || !src)
|
||||
return SYN123_BAD_BUF;
|
||||
if(samples*inblock != src_bytes)
|
||||
if(samples*srcframe != src_bytes)
|
||||
return SYN123_BAD_CHOP;
|
||||
if(samples*outblock > dest_size)
|
||||
if(samples*dstframe > dst_size)
|
||||
return SYN123_BAD_SIZE;
|
||||
if(src_enc == dest_enc)
|
||||
memcpy(dest, src, samples*outblock);
|
||||
if(src_enc == dst_enc)
|
||||
memcpy(dst, src, samples*dstframe);
|
||||
else if(src_enc & MPG123_ENC_FLOAT)
|
||||
{
|
||||
if(src_enc == MPG123_ENC_FLOAT_64)
|
||||
@@ -275,19 +288,53 @@ syn123_conv( void * MPG123_RESTRICT dest, int dest_enc, size_t dest_size
|
||||
else
|
||||
return SYN123_BAD_CONV;
|
||||
}
|
||||
else if(dest_enc & MPG123_ENC_FLOAT)
|
||||
else if(dst_enc & MPG123_ENC_FLOAT)
|
||||
{
|
||||
if(dest_enc == MPG123_ENC_FLOAT_64)
|
||||
if(dst_enc == MPG123_ENC_FLOAT_64)
|
||||
TO_FLT(double)
|
||||
else if(dest_enc == MPG123_ENC_FLOAT_32)
|
||||
else if(dst_enc == MPG123_ENC_FLOAT_32)
|
||||
TO_FLT(float)
|
||||
else
|
||||
return SYN123_BAD_CONV;
|
||||
}
|
||||
else
|
||||
else if(sh)
|
||||
{
|
||||
char *cdst = dst;
|
||||
char *csrc = src;
|
||||
int mixenc = syn123_mixenc(dst_enc);
|
||||
int mixframe = MPG123_SAMPLESIZE(mixenc);
|
||||
if(!mixenc || !mixframe)
|
||||
return SYN123_BAD_CONV;
|
||||
if(dest_bytes)
|
||||
*dest_bytes = outblock*samples;
|
||||
// Use the whole workbuf, both halves.
|
||||
int mbufblock = 2*bufblock*sizeof(double)/mixframe;
|
||||
mdebug("mbufblock=%i (enc %i)", mbufblock, mixenc);
|
||||
// Abuse the handle workbuf for intermediate storage.
|
||||
size_t samples_left = samples;
|
||||
while(samples_left)
|
||||
{
|
||||
int block = (int)smin(samples_left, mbufblock);
|
||||
int err = syn123_conv(
|
||||
sh->workbuf, mixenc, sizeof(sh->workbuf)
|
||||
, csrc, src_enc, srcframe*block
|
||||
, NULL, NULL );
|
||||
if(!err)
|
||||
err = syn123_conv(
|
||||
cdst, dst_enc, dstframe*block
|
||||
, sh->workbuf, mixenc, mixframe*block
|
||||
, NULL, NULL );
|
||||
if(err)
|
||||
{
|
||||
mdebug("conv error: %i", err);
|
||||
return SYN123_BAD_CONV;
|
||||
}
|
||||
cdst += dstframe*block;
|
||||
csrc += srcframe*block;
|
||||
samples_left -= block;
|
||||
}
|
||||
} else
|
||||
return SYN123_BAD_CONV;
|
||||
if(dst_bytes)
|
||||
*dst_bytes = dstframe*samples;
|
||||
return SYN123_OK;
|
||||
}
|
||||
|
||||
@@ -331,6 +378,7 @@ void attribute_align_arg
|
||||
syn123_mono2many( void * MPG123_RESTRICT dest, void * MPG123_RESTRICT src
|
||||
, int channels, size_t samplesize, size_t samplecount )
|
||||
{
|
||||
#ifndef SYN123_NO_CASES
|
||||
switch(channels)
|
||||
{
|
||||
case 1:
|
||||
@@ -377,12 +425,16 @@ syn123_mono2many( void * MPG123_RESTRICT dest, void * MPG123_RESTRICT src
|
||||
}
|
||||
break;
|
||||
}
|
||||
#else
|
||||
BYTEMULTIPLY(dest, src, samplesize, samplecount, channels)
|
||||
#endif
|
||||
}
|
||||
|
||||
void attribute_align_arg
|
||||
syn123_interleave(void * MPG123_RESTRICT dest, void ** MPG123_RESTRICT src
|
||||
, int channels, size_t samplesize, size_t samplecount)
|
||||
{
|
||||
#ifndef SYN123_NO_CASEs
|
||||
switch(channels)
|
||||
{
|
||||
case 1:
|
||||
@@ -429,12 +481,16 @@ syn123_interleave(void * MPG123_RESTRICT dest, void ** MPG123_RESTRICT src
|
||||
}
|
||||
break;
|
||||
}
|
||||
#else
|
||||
BYTEINTERLEAVE(dest, src, samplesize, samplecount, channels)
|
||||
#endif
|
||||
}
|
||||
|
||||
void attribute_align_arg
|
||||
syn123_deinterleave(void ** MPG123_RESTRICT dest, void * MPG123_RESTRICT src
|
||||
, int channels, size_t samplesize, size_t samplecount)
|
||||
{
|
||||
#ifndef SYN123_NO_CASES
|
||||
switch(channels)
|
||||
{
|
||||
case 1:
|
||||
@@ -481,6 +537,161 @@ syn123_deinterleave(void ** MPG123_RESTRICT dest, void * MPG123_RESTRICT src
|
||||
}
|
||||
break;
|
||||
}
|
||||
#else
|
||||
BYTEDEINTERLEAVE(dest, src, samplesize, samplecount, channels)
|
||||
#endif
|
||||
}
|
||||
|
||||
#define MIX_CODE(type,scc,dcc) \
|
||||
for(size_t i=0; i<samples; ++i) \
|
||||
{ \
|
||||
for(int dc=0; dc<dcc; ++dc) \
|
||||
{ \
|
||||
dst[SYN123_IOFF(i, dc, dcc)] = \
|
||||
(type)mixmatrix[SYN123_IOFF(dc,0,scc)] * src[SYN123_IOFF(i,0,scc)]; \
|
||||
for(int sc=1; sc<scc; ++sc) \
|
||||
dst[SYN123_IOFF(i,dc,dcc)] += \
|
||||
(type)mixmatrix[SYN123_IOFF(dc,sc,scc)] * src[SYN123_IOFF(i,sc,scc)]; \
|
||||
} \
|
||||
}
|
||||
|
||||
// I decided against optimizing for mixing factors of 1, unity matrix ...
|
||||
// A floating point multiplication is not that expensive and there might
|
||||
// be value in the runtime of the function not depending on the matrix
|
||||
// values, only on channel setup and sample count.
|
||||
// I might trust the compiler here to hardcode the case value for
|
||||
// src/dst_channels. But why take the risk?
|
||||
#ifndef SYN123_NO_CASES
|
||||
#define SYN123_MIX_FUNC(type) \
|
||||
switch(src_channels) \
|
||||
{ \
|
||||
case 1: \
|
||||
switch(dst_channels) \
|
||||
{ \
|
||||
case 1: \
|
||||
MIX_CODE(type,1,1) \
|
||||
break; \
|
||||
case 2: \
|
||||
MIX_CODE(type,1,2) \
|
||||
break; \
|
||||
default: \
|
||||
MIX_CODE(type,1,dst_channels) \
|
||||
} \
|
||||
break; \
|
||||
case 2: \
|
||||
switch(dst_channels) \
|
||||
{ \
|
||||
case 1: \
|
||||
MIX_CODE(type,2,1) \
|
||||
break; \
|
||||
case 2: \
|
||||
MIX_CODE(type,2,2) \
|
||||
break; \
|
||||
default: \
|
||||
MIX_CODE(type,2,dst_channels) \
|
||||
} \
|
||||
break; \
|
||||
default: \
|
||||
MIX_CODE(type,src_channels, dst_channels) \
|
||||
}
|
||||
#else
|
||||
#define SYN123_MIX_FUNC(type) \
|
||||
MIX_CODE(type, src_channels, dst_channels)
|
||||
#endif
|
||||
|
||||
static void syn123_mix_f32( float * MPG123_RESTRICT dst, int dst_channels
|
||||
, float * MPG123_RESTRICT src, int src_channels
|
||||
, const double * MPG123_RESTRICT mixmatrix
|
||||
, size_t samples )
|
||||
{
|
||||
debug("syn123_mix_f32");
|
||||
SYN123_MIX_FUNC(float)
|
||||
}
|
||||
|
||||
static void syn123_mix_f64( double * MPG123_RESTRICT dst, int dst_channels
|
||||
, double * MPG123_RESTRICT src, int src_channels
|
||||
, const double * MPG123_RESTRICT mixmatrix
|
||||
, size_t samples )
|
||||
{
|
||||
debug("syn123_mix_f64");
|
||||
SYN123_MIX_FUNC(double)
|
||||
}
|
||||
|
||||
int attribute_align_arg
|
||||
syn123_mix( void * MPG123_RESTRICT dst, int dst_enc, int dst_channels
|
||||
, void * MPG123_RESTRICT src, int src_enc, int src_channels
|
||||
, const double * mixmatrix
|
||||
, size_t samples, syn123_handle *sh )
|
||||
{
|
||||
if(src_channels < 1 || dst_channels < 1)
|
||||
return SYN123_BAD_FMT;
|
||||
if(!dst || !src || !mixmatrix)
|
||||
return SYN123_BAD_BUF;
|
||||
if(dst_enc == src_enc) switch(dst_enc)
|
||||
{
|
||||
case MPG123_ENC_FLOAT_32:
|
||||
syn123_mix_f32( dst, dst_channels, src, src_channels
|
||||
, mixmatrix, samples );
|
||||
return SYN123_OK;
|
||||
break;
|
||||
case MPG123_ENC_FLOAT_64:
|
||||
syn123_mix_f64( dst, dst_channels, src, src_channels
|
||||
, mixmatrix, samples );
|
||||
return SYN123_OK;
|
||||
break;
|
||||
}
|
||||
// If still here: Some conversion needed.
|
||||
if(!sh)
|
||||
return SYN123_BAD_ENC;
|
||||
else
|
||||
{
|
||||
debug("mix with conversion");
|
||||
char *cdst = dst;
|
||||
char *csrc = src;
|
||||
int srcframe = MPG123_SAMPLESIZE(src_enc)*src_channels;
|
||||
int dstframe = MPG123_SAMPLESIZE(dst_enc)*dst_channels;
|
||||
if(!srcframe || !dstframe)
|
||||
return SYN123_BAD_ENC;
|
||||
int mixenc = syn123_mixenc(dst_enc);
|
||||
int mixinframe = MPG123_SAMPLESIZE(mixenc)*src_channels;
|
||||
int mixoutframe = MPG123_SAMPLESIZE(mixenc)*dst_channels;
|
||||
int mixframe = mixinframe > mixoutframe ? mixinframe : mixoutframe;
|
||||
if(!mixenc || !mixinframe || !mixoutframe)
|
||||
return SYN123_BAD_CONV;
|
||||
// Mix from buffblock[0] to buffblock[1].
|
||||
int mbufblock = bufblock*sizeof(double)/mixframe;
|
||||
mdebug("mbufblock=%i (enc %i)", mbufblock, mixenc);
|
||||
// Need at least one sample per round to avoid endless loop.
|
||||
// Of course, we would prefer more, but it's your fault for
|
||||
// giving an excessive amount of channels without handling conversion
|
||||
// beforehand.
|
||||
if(mbufblock < 1)
|
||||
return SYN123_BAD_CONV;
|
||||
while(samples)
|
||||
{
|
||||
int block = (int)smin(samples, mbufblock);
|
||||
int err = syn123_conv(
|
||||
sh->workbuf[0], mixenc, sizeof(sh->workbuf[0])
|
||||
, csrc, src_enc, srcframe*block
|
||||
, NULL, NULL );
|
||||
if(err)
|
||||
return err;
|
||||
err = syn123_mix( sh->workbuf[1], mixenc, dst_channels
|
||||
, sh->workbuf[0], mixenc, src_channels, mixmatrix, block, NULL );
|
||||
if(err)
|
||||
return err;
|
||||
err = syn123_conv(
|
||||
cdst, dst_enc, dstframe*block
|
||||
, sh->workbuf[1], mixenc, mixoutframe*block
|
||||
, NULL, NULL );
|
||||
if(err)
|
||||
return err;
|
||||
cdst += dstframe*block;
|
||||
csrc += srcframe*block;
|
||||
samples -= block;
|
||||
}
|
||||
return SYN123_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* All the byte-swappery for those little big endian boxes. */
|
||||
|
||||
@@ -73,6 +73,12 @@ extern "C" {
|
||||
* 1. Create handle with desired output format.
|
||||
* 2. Set up synthesis mode with parameters.
|
||||
* 3. Repeatedly extract buffers with PCM samples.
|
||||
*
|
||||
* The other functions for encoding conversion, (de-)interleaving,
|
||||
* and interleaved mixing work without a handle and only use the
|
||||
* buffers you hand in. Only the functions that are able to return
|
||||
* a success code do check arguments for obvious trouble like
|
||||
* NULL pointers. You are supposed to act responsibly when calling.
|
||||
@{
|
||||
*/
|
||||
|
||||
@@ -271,12 +277,16 @@ int syn123_setup_silence(syn123_handle *sh);
|
||||
* \param src_enc source encoding
|
||||
* \param src_bytes source buffer size in bytes
|
||||
* \param dest_bytes optional address to store the written byte count to
|
||||
* \param sh an optional syn123_handle which enables arbitrary encoding
|
||||
* conversions by utilizing the contained buffer as intermediate storage,
|
||||
* can be NULL, disabling any conversion not involving floating point
|
||||
* input or output
|
||||
* \return success code
|
||||
*/
|
||||
MPG123_EXPORT
|
||||
int syn123_conv( void * MPG123_RESTRICT dest, int dest_enc, size_t dest_size
|
||||
, void * MPG123_RESTRICT src, int src_enc, size_t src_bytes
|
||||
, size_t *dest_bytes );
|
||||
, size_t *dest_bytes, syn123_handle * sh );
|
||||
|
||||
/** Clip samples in buffer to default range.
|
||||
* This only does anything with floating point encoding, but you can always
|
||||
@@ -336,6 +346,58 @@ MPG123_EXPORT
|
||||
void syn123_mono2many( void * MPG123_RESTRICT dest, void * MPG123_RESTRICT src
|
||||
, int channels, size_t samplesize, size_t samplecount );
|
||||
|
||||
/** A little helper/reminder on how interleaved format works:
|
||||
* Produce the offset of the given sample for the given channel.
|
||||
*/
|
||||
#define SYN123_IOFF(sample, channel, channels) ((sample)*(channels)+(channel))
|
||||
|
||||
/** Specify floating point encoding to use for preserving precision in
|
||||
* intermediate computations for given output encoding.
|
||||
* This should return either MPG123_ENC_FLOAT_32 or MPG123_ENC_FLOAT_64,
|
||||
* unless an uncertain future adds things like 16 bit fp ...
|
||||
* This is what syn123_conv() and syn123_mix() will use internally if
|
||||
* intermediate conversion is necessary.
|
||||
* \param inputenc input encoding
|
||||
* \param outputenc output encoding
|
||||
* \return encoding value, zero if none can be chosen (invalid parameters)
|
||||
*/
|
||||
MPG123_EXPORT
|
||||
int syn123_mixenc(int encoding);
|
||||
|
||||
/** Mix n input channels into m output channels.
|
||||
* This takes an interleaved input stream and mixes its channels
|
||||
* into the output stream given a channel matrix (m,n) where
|
||||
* each of the m rows contains the n volume factors (weights)
|
||||
* to apply when summing the samples from the n input channels.
|
||||
* This works either on 32 bit or 64 bit floating point encodings. It
|
||||
* may have some optimization to work faster with mono or stereo on
|
||||
* either side and slower generic code for arbitrary channel counts.
|
||||
* You can use syn123_conv() to convert from/to input/output encodings.
|
||||
* There are no optimizations for special cases of mixing factors, so
|
||||
* you should always be able to predict the number of floating point
|
||||
* operations being executed.
|
||||
* For fun, you could give the same problem to a BLAS implementation
|
||||
* of your choice and compare the performance;-)
|
||||
* \param dst destination buffer
|
||||
* \param dst_channels destination channel count (m)
|
||||
* \param src source buffer
|
||||
* \param src_channels source channel count (n)
|
||||
* \param mixmatrix mixing factors ((m,n) matrix), same encoding as
|
||||
* the audio data
|
||||
* \param encoding sample encoding, must be MPG123_ENC_FLOAT_32 or
|
||||
* MPG123_ENC_FLOAT_64 unless a syn123_handle is provided
|
||||
* \param sh an optional syn123_handle which enables work on non-float
|
||||
* encodings by utilizing the contained buffer as intermediate storage,
|
||||
* converting to/from float transparently; Note that this may limit
|
||||
* the amount of channels depending on the available buffer space.
|
||||
* \return success code (e.g. bad encoding, channel counts ...)
|
||||
*/
|
||||
MPG123_EXPORT
|
||||
int syn123_mix( void * MPG123_RESTRICT dst, int dst_enc, int dst_channels
|
||||
, void * MPG123_RESTRICT src, int src_enc, int src_channels
|
||||
, const double * mixmatrix
|
||||
, size_t samples, syn123_handle *sh );
|
||||
|
||||
/** Swap byte order between little/big endian.
|
||||
* \param buf buffer to work on
|
||||
* \param samplesize size of one sample (see MPG123_SAMPLESIZE)
|
||||
|
||||
@@ -60,6 +60,20 @@ struct syn123_struct
|
||||
size_t offset; // offset in buffer for extraction helper
|
||||
};
|
||||
|
||||
#ifndef NO_SMIN
|
||||
static size_t smin(size_t a, size_t b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_SMAX
|
||||
static size_t smax(size_t a, size_t b)
|
||||
{
|
||||
return a > b ? a : b;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_GROW_BUF
|
||||
// Grow period buffer to at least given size.
|
||||
// Content is not preserved.
|
||||
|
||||
239
src/out123.c
239
src/out123.c
@@ -7,8 +7,14 @@
|
||||
|
||||
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.
|
||||
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 -.
|
||||
@@ -73,10 +79,8 @@ static char *encoding_name = NULL;
|
||||
static int encoding = MPG123_ENC_SIGNED_16;
|
||||
static char *inputenc_name = NULL;
|
||||
static int inputenc = 0;
|
||||
static int mixenc = 0;
|
||||
static int channels = 2;
|
||||
static int inputch = 0;
|
||||
static float *chmatrix = NULL;
|
||||
static long rate = 44100;
|
||||
static char *driver = NULL;
|
||||
static char *device = NULL;
|
||||
@@ -110,13 +114,15 @@ size_t pcmblock = 1152; /* samples (pcm frames) we treat en bloc */
|
||||
/* To be set after settling format. */
|
||||
size_t pcmframe = 0;
|
||||
size_t pcminframe = 0;
|
||||
size_t pcmmixframe = 0;
|
||||
unsigned char *audio = NULL;
|
||||
unsigned char *inaudio = NULL;
|
||||
unsigned char *mixaudio = NULL;
|
||||
char *mixmat_string = NULL;
|
||||
double *mixmat = NULL;
|
||||
|
||||
/* Option to play some oscillatory test signals. */
|
||||
// Option to play some oscillatory test signals.
|
||||
// Also used for conversions.
|
||||
syn123_handle *waver = NULL;
|
||||
int generate = FALSE; // Wheter to use the syn123 generator.
|
||||
|
||||
out123_handle *ao = NULL;
|
||||
char *cmd_name = NULL;
|
||||
@@ -170,10 +176,10 @@ static void safe_exit(int code)
|
||||
/* 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(mixaudio) free(mixaudio);
|
||||
if(mixmat) free(mixmat);
|
||||
if(inaudio && inaudio != audio) free(inaudio);
|
||||
if(audio) free(audio);
|
||||
syn123_del(waver);
|
||||
if(waver) syn123_del(waver);
|
||||
exit(code);
|
||||
}
|
||||
|
||||
@@ -184,7 +190,17 @@ static void check_fatal_output(int code)
|
||||
if(!quiet)
|
||||
error2( "out123 error %i: %s"
|
||||
, out123_errcode(ao), out123_strerror(ao) );
|
||||
safe_exit(code);
|
||||
safe_exit(133);
|
||||
}
|
||||
}
|
||||
|
||||
static void check_fatal_syn(int code)
|
||||
{
|
||||
if(code)
|
||||
{
|
||||
if(!quiet)
|
||||
merror("syn123 error %i: %s", code, syn123_strerror(code));
|
||||
safe_exit(132);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,6 +488,7 @@ topt opts[] = {
|
||||
{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},
|
||||
{'r', "rate", GLO_ARG | GLO_LONG, 0, &rate, 0},
|
||||
{0, "clip", GLO_INT, 0, &do_clip, TRUE},
|
||||
{0, "headphones", 0, set_output_h, 0,0},
|
||||
@@ -571,52 +588,57 @@ static void setup_wavegen(void)
|
||||
int synerr = 0;
|
||||
size_t common = 0;
|
||||
|
||||
if(!generate)
|
||||
wave_limit = 0;
|
||||
waver = syn123_new(rate, inputch, inputenc, wave_limit, &synerr);
|
||||
check_fatal_syn(synerr);
|
||||
if(!waver)
|
||||
safe_exit(132);
|
||||
// At least have waver handy for conversions.
|
||||
if(!generate)
|
||||
return;
|
||||
|
||||
if(!strcmp(signal_source, "pink"))
|
||||
{
|
||||
waver = syn123_new(rate, channels, inputenc, wave_limit, &synerr);
|
||||
if(waver)
|
||||
synerr = syn123_setup_pink(waver, pink_rows, &common);
|
||||
if(!waver || synerr)
|
||||
if(synerr)
|
||||
{
|
||||
error1("setting up pink noise generator: %s\n", syn123_strerror(synerr));
|
||||
if(!quiet)
|
||||
merror("setting up pink noise generator: %s\n", syn123_strerror(synerr));
|
||||
safe_exit(132);
|
||||
}
|
||||
if(verbose)
|
||||
{
|
||||
if(common)
|
||||
fprintf(stderr, "out123: periodic signal table of %" SIZE_P " samples\n", common);
|
||||
else
|
||||
fprintf(stderr, "out123: live signal generation\n");
|
||||
fprintf(stderr, "out123; pink noise with %i generator rows (0=internal default)\n"
|
||||
fprintf( stderr
|
||||
, ME ": pink noise with %i generator rows (0=internal default)\n"
|
||||
, pink_rows );
|
||||
}
|
||||
return;
|
||||
goto setup_waver_end;
|
||||
}
|
||||
|
||||
if(!strcmp(signal_source, "geiger"))
|
||||
else if(!strcmp(signal_source, "geiger"))
|
||||
{
|
||||
waver = syn123_new(rate, channels, inputenc, wave_limit, &synerr);
|
||||
if(waver)
|
||||
synerr = syn123_setup_geiger(waver, geiger_activity, &common);
|
||||
if(!waver || synerr)
|
||||
if(synerr)
|
||||
{
|
||||
error1("setting up geiger generator: %s\n", syn123_strerror(synerr));
|
||||
if(!quiet)
|
||||
merror("setting up geiger generator: %s\n", syn123_strerror(synerr));
|
||||
safe_exit(132);
|
||||
}
|
||||
if(verbose)
|
||||
{
|
||||
if(common)
|
||||
fprintf(stderr, "out123: periodic signal table of %" SIZE_P " samples\n", common);
|
||||
else
|
||||
fprintf(stderr, "out123: live signal generation\n");
|
||||
fprintf(stderr, "out123; geiger with actvity %g\n", geiger_activity);
|
||||
fprintf(stderr, ME ": geiger with actvity %g\n", geiger_activity);
|
||||
}
|
||||
return;
|
||||
goto setup_waver_end;
|
||||
}
|
||||
else if(strcmp(signal_source, "wave"))
|
||||
{
|
||||
if(!quiet)
|
||||
merror("unknown signal source: %s", signal_source);
|
||||
safe_exit(132);
|
||||
}
|
||||
|
||||
if(strcmp(signal_source, "wave"))
|
||||
return;
|
||||
|
||||
// The big default code block is for wave setup.
|
||||
// Exceptions jump over it.
|
||||
if(wave_freqs)
|
||||
{
|
||||
char *tok;
|
||||
@@ -704,31 +726,34 @@ static void setup_wavegen(void)
|
||||
}
|
||||
}
|
||||
|
||||
waver = syn123_new(rate, channels, inputenc, wave_limit, &synerr);
|
||||
if(waver)
|
||||
synerr = syn123_setup_waves( waver, count
|
||||
, id, freq_real, phase, backwards, &common );
|
||||
if(!waver || synerr)
|
||||
if(synerr)
|
||||
{
|
||||
error1("setting up wave generator: %s\n", syn123_strerror(synerr));
|
||||
if(!quiet)
|
||||
merror("setting up wave generator: %s\n", syn123_strerror(synerr));
|
||||
safe_exit(132);
|
||||
}
|
||||
if(verbose)
|
||||
{
|
||||
if(common)
|
||||
fprintf(stderr, "out123: periodic signal table of %" SIZE_P " samples\n", common);
|
||||
else
|
||||
fprintf(stderr, "out123: live signal generation\n");
|
||||
if(count) for(i=0; i<count; ++i)
|
||||
fprintf( stderr, "out123: wave %" SIZE_P ": %s @ %g Hz (%g Hz) p %g\n"
|
||||
fprintf( stderr, ME ": wave %" SIZE_P ": %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, "out123: default sine wave\n");
|
||||
fprintf(stderr, ME ": default sine wave\n");
|
||||
}
|
||||
|
||||
setup_waver_end:
|
||||
if(verbose)
|
||||
{
|
||||
if(common)
|
||||
fprintf(stderr, ME ": periodic signal table of %" SIZE_P " samples\n", common);
|
||||
else
|
||||
fprintf(stderr, ME ": live signal generation\n");
|
||||
}
|
||||
if(phase)
|
||||
free(phase);
|
||||
if(id)
|
||||
@@ -742,6 +767,7 @@ int play_frame(void)
|
||||
{
|
||||
size_t got_samples;
|
||||
size_t get_samples = pcmblock;
|
||||
debug("play_frame");
|
||||
if(timelimit >= 0)
|
||||
{
|
||||
if(offset >= timelimit)
|
||||
@@ -749,7 +775,7 @@ int play_frame(void)
|
||||
else if(timelimit < offset+get_samples)
|
||||
get_samples = (off_t)timelimit-offset;
|
||||
}
|
||||
if(waver)
|
||||
if(generate)
|
||||
got_samples = syn123_read(waver, inaudio, get_samples*pcminframe)/pcminframe;
|
||||
else
|
||||
got_samples = fread(inaudio, pcminframe, get_samples, input);
|
||||
@@ -760,31 +786,15 @@ int play_frame(void)
|
||||
size_t got_bytes = 0;
|
||||
if(inaudio != audio)
|
||||
{
|
||||
// Convert audio.
|
||||
if(inputch == channels) // Just encoding
|
||||
if(mixmat)
|
||||
{
|
||||
int err;
|
||||
if(mixaudio)
|
||||
check_fatal_syn(syn123_mix( audio, encoding, channels
|
||||
, inaudio, inputenc, inputch, mixmat, got_samples, waver ));
|
||||
got_bytes = pcmframe * got_samples;
|
||||
} else
|
||||
{
|
||||
err = syn123_conv( mixaudio, mixenc, pcmblock*pcmmixframe
|
||||
, inaudio, inputenc, got_samples*pcminframe, &got_bytes );
|
||||
if(!err)
|
||||
err = syn123_conv( audio, encoding, pcmblock*pcmframe
|
||||
, mixaudio, mixenc, got_bytes, &got_bytes );
|
||||
}
|
||||
else
|
||||
err = syn123_conv( audio, encoding, got_samples*pcmframe
|
||||
, inaudio, inputenc, got_samples*pcminframe, &got_bytes );
|
||||
if(err)
|
||||
{
|
||||
error1("conversion failure: %s", syn123_strerror(err));
|
||||
safe_exit(135);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error("mixing not implemented yet");
|
||||
safe_exit(120);
|
||||
check_fatal_syn(syn123_conv( audio, encoding, got_samples*pcmframe
|
||||
, inaudio, inputenc, got_samples*pcminframe, &got_bytes, waver ));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -793,17 +803,10 @@ int play_frame(void)
|
||||
{
|
||||
size_t clipped = syn123_clip(audio, encoding, got_samples*channels);
|
||||
if(verbose > 1 && clipped)
|
||||
fprintf(stderr, "out123: clipped %"SIZE_P" samples\n", clipped);
|
||||
}
|
||||
if(out123_play(ao, audio, got_bytes) < (int)got_bytes)
|
||||
{
|
||||
if(!quiet)
|
||||
{
|
||||
error2( "out123 error %i: %s"
|
||||
, out123_errcode(ao), out123_strerror(ao) );
|
||||
}
|
||||
safe_exit(133);
|
||||
fprintf(stderr, ME ": clipped %"SIZE_P" samples\n", clipped);
|
||||
}
|
||||
mdebug("playing %zu bytes", got_bytes);
|
||||
check_fatal_output(out123_play(ao, audio, got_bytes) < (int)got_bytes);
|
||||
if(also_stdout && fwrite(audio, pcmframe, got_samples, stdout) < got_samples)
|
||||
{
|
||||
if(!quiet && errno != EINTR)
|
||||
@@ -828,6 +831,7 @@ static void *fatal_malloc(size_t bytes)
|
||||
void *buf;
|
||||
if(!(buf = malloc(bytes)))
|
||||
{
|
||||
if(!quiet)
|
||||
error("OOM");
|
||||
safe_exit(1);
|
||||
}
|
||||
@@ -969,25 +973,46 @@ int main(int sys_argc, char ** sys_argv)
|
||||
}
|
||||
if(!inputch)
|
||||
inputch = channels;
|
||||
// Up to 24 bit integer is mixed using float, anything above using double.
|
||||
// Except 32 bit float, of course.
|
||||
mixenc = ((encoding != MPG123_ENC_FLOAT_32 && out123_encsize(encoding) > 3) ||
|
||||
(inputenc != MPG123_ENC_FLOAT_32 && out123_encsize(inputenc) > 3))
|
||||
? MPG123_ENC_FLOAT_64
|
||||
: MPG123_ENC_FLOAT_32;
|
||||
pcmmixframe = out123_encsize(mixenc)*channels;
|
||||
pcminframe = out123_encsize(inputenc)*inputch;
|
||||
pcmframe = out123_encsize(encoding)*channels;
|
||||
audio = fatal_malloc(pcmblock*pcmframe);
|
||||
if(inputenc != encoding)
|
||||
// Full mixing is initiated if channel counts differ or a non-empty
|
||||
// mixing matrix has been specified.
|
||||
if(1 || inputch != channels || (mixmat_string && mixmat_string[0]))
|
||||
{
|
||||
inaudio = fatal_malloc(pcmblock*pcminframe);
|
||||
if(!(inputenc & MPG123_ENC_FLOAT || encoding & MPG123_ENC_FLOAT))
|
||||
mixaudio = fatal_malloc(pcmblock*pcmmixframe);
|
||||
mixmat = fatal_malloc(sizeof(double)*inputch*channels);
|
||||
size_t mmcount = mytok_count(mixmat_string);
|
||||
// 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 converting or mixing, use separate input buffer.
|
||||
if(inputenc != encoding || mixmat)
|
||||
inaudio = fatal_malloc(pcmblock*pcminframe);
|
||||
else
|
||||
inaudio = audio;
|
||||
|
||||
check_fatal_output(out123_set_buffer(ao, buffer_kb*1024));
|
||||
check_fatal_output(out123_open(ao, driver, device));
|
||||
|
||||
@@ -999,13 +1024,22 @@ int main(int sys_argc, char ** sys_argv)
|
||||
if(inaudio != audio)
|
||||
{
|
||||
encname = out123_enc_name(inputenc);
|
||||
fprintf( stderr, ME": input format: %li Hz, %i, channels, %s\n"
|
||||
, rate, channels, encname ? encname : "???" );
|
||||
}
|
||||
if(mixaudio)
|
||||
fprintf( stderr, ME": input format: %li Hz, %i channels, %s\n"
|
||||
, rate, inputch, encname ? encname : "???" );
|
||||
encname = out123_enc_name(syn123_mixenc(encoding));
|
||||
if(mixmat)
|
||||
{
|
||||
encname = out123_enc_name(mixenc);
|
||||
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, " %4.2f", mixmat[SYN123_IOFF(oc,ic,inputch)]);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
fprintf( stderr, ME": converting via %s\n", encname ? encname : "???" );
|
||||
}
|
||||
encname = out123_enc_name(encoding);
|
||||
fprintf(stderr, ME": format: %li Hz, %i channels, %s\n"
|
||||
@@ -1021,14 +1055,8 @@ int main(int sys_argc, char ** sys_argv)
|
||||
|
||||
input = stdin;
|
||||
if(strcmp(signal_source, "file"))
|
||||
{
|
||||
setup_wavegen();
|
||||
if(!waver)
|
||||
{
|
||||
error("unknown signal source");
|
||||
safe_exit(133);
|
||||
}
|
||||
}
|
||||
generate = TRUE;
|
||||
setup_wavegen(); // Used also for conversion/mixing.
|
||||
|
||||
while(play_frame() && !intflag)
|
||||
{
|
||||
@@ -1208,7 +1236,8 @@ static void long_usage(int err)
|
||||
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.\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).");
|
||||
free(enclist);
|
||||
safe_exit(err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user