aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2017-04-10 09:17:10 -0700
committerChris Robinson <[email protected]>2017-04-10 09:26:06 -0700
commit6cc69c8d94744d66e68ffffb9b71f6714d86e710 (patch)
tree1fd85a1f2eb40e5383065560bb8d1a0de8df06ed
parent81527cdbddc52338f5fb3c8b79139bf9d9186d3a (diff)
Add a sample converter
This is intended to do conversions for interleaved samples, and supports changing from one DevFmtType to another as well as resampling. It does not handle remixing channels. The mixer is more optimized to use the resampling functions directly. However, this should prove useful for recording with certain backends that won't do the conversion themselves.
-rw-r--r--Alc/converter.c332
-rw-r--r--Alc/converter.h42
-rw-r--r--Alc/mixer.c11
-rw-r--r--CMakeLists.txt1
-rw-r--r--OpenAL32/Include/alu.h10
5 files changed, 386 insertions, 10 deletions
diff --git a/Alc/converter.c b/Alc/converter.c
new file mode 100644
index 00000000..68752693
--- /dev/null
+++ b/Alc/converter.c
@@ -0,0 +1,332 @@
+
+#include "config.h"
+
+#include "converter.h"
+
+#include "mixer_defs.h"
+
+
+SampleConverter *CreateSampleConverter(enum DevFmtType srcType, enum DevFmtType dstType, ALsizei numchans, ALsizei srcRate, ALsizei dstRate)
+{
+ SampleConverter *converter;
+
+ if(numchans <= 0 || srcRate <= 0 || dstRate <= 0)
+ return NULL;
+
+ converter = al_calloc(16, offsetof(SampleConverter, Chan[numchans]));
+ converter->mSrcType = srcType;
+ converter->mDstType = dstType;
+ converter->mNumChannels = numchans;
+ converter->mSrcTypeSize = BytesFromDevFmt(srcType);
+ converter->mDstTypeSize = BytesFromDevFmt(dstType);
+
+ converter->mSrcPrepCount = 0;
+
+ converter->mFracOffset = 0;
+ converter->mIncrement = (ALsizei)clampu64((ALuint64)srcRate*FRACTIONONE/dstRate,
+ 1, MAX_PITCH*FRACTIONONE);
+ if(converter->mIncrement == FRACTIONONE)
+ converter->mResample = Resample_copy32_C;
+ else
+ {
+ /* TODO: Allow other resamplers. */
+ converter->mResample = SelectResampler(LinearResampler);
+ }
+
+ return converter;
+}
+
+void DestroySampleConverter(SampleConverter **converter)
+{
+ if(converter)
+ {
+ al_free(*converter);
+ *converter = NULL;
+ }
+}
+
+
+static inline ALfloat Sample_ALbyte(ALbyte val)
+{ return val * (1.0f/128.0f); }
+static inline ALfloat Sample_ALubyte(ALubyte val)
+{ return Sample_ALbyte((ALint)val - 128); }
+
+static inline ALfloat Sample_ALshort(ALshort val)
+{ return val * (1.0f/32768.0f); }
+static inline ALfloat Sample_ALushort(ALushort val)
+{ return Sample_ALshort((ALint)val - 32768); }
+
+static inline ALfloat Sample_ALint(ALint val)
+{ return (val>>7) * (1.0f/16777216.0f); }
+static inline ALfloat Sample_ALuint(ALuint val)
+{ return ((ALint)(val>>7) - 16777216) * (1.0f/16777216.0f); }
+
+static inline ALfloat Sample_ALfloat(ALfloat val)
+{ return val; }
+
+#define DECL_TEMPLATE(T) \
+static inline void Load_##T(ALfloat *dst, const T *src, ALint srcstep, ALsizei samples)\
+{ \
+ ALsizei i; \
+ for(i = 0;i < samples;i++) \
+ dst[i] = Sample_##T(src[i*srcstep]); \
+}
+
+DECL_TEMPLATE(ALbyte)
+DECL_TEMPLATE(ALubyte)
+DECL_TEMPLATE(ALshort)
+DECL_TEMPLATE(ALushort)
+DECL_TEMPLATE(ALint)
+DECL_TEMPLATE(ALuint)
+DECL_TEMPLATE(ALfloat)
+
+#undef DECL_TEMPLATE
+
+static void LoadSamples(ALfloat *dst, const ALvoid *src, ALint srcstep, enum DevFmtType srctype, ALsizei samples)
+{
+ switch(srctype)
+ {
+ case DevFmtByte:
+ Load_ALbyte(dst, src, srcstep, samples);
+ break;
+ case DevFmtUByte:
+ Load_ALubyte(dst, src, srcstep, samples);
+ break;
+ case DevFmtShort:
+ Load_ALshort(dst, src, srcstep, samples);
+ break;
+ case DevFmtUShort:
+ Load_ALushort(dst, src, srcstep, samples);
+ break;
+ case DevFmtInt:
+ Load_ALint(dst, src, srcstep, samples);
+ break;
+ case DevFmtUInt:
+ Load_ALuint(dst, src, srcstep, samples);
+ break;
+ case DevFmtFloat:
+ Load_ALfloat(dst, src, srcstep, samples);
+ break;
+ }
+}
+
+
+static inline ALbyte ALbyte_Sample(ALfloat val)
+{ return (ALbyte)clampf(val*128.0f, -128.0f, 127.0f); }
+static inline ALubyte ALubyte_Sample(ALfloat val)
+{ return ALbyte_Sample(val)+128; }
+
+static inline ALshort ALshort_Sample(ALfloat val)
+{ return (ALshort)clampf(val*32768.0f, -32768.0f, 32767.0f); }
+static inline ALushort ALushort_Sample(ALfloat val)
+{ return ALshort_Sample(val)+32768; }
+
+static inline ALint ALint_Sample(ALfloat val)
+{ return (ALint)clampf(val*16777216.0f, -16777216.0f, 16777215.0f) << 7; }
+static inline ALuint ALuint_Sample(ALfloat val)
+{ return ALint_Sample(val)+INT_MAX+1; }
+
+static inline ALfloat ALfloat_Sample(ALfloat val)
+{ return val; }
+
+#define DECL_TEMPLATE(T) \
+static inline void Store_##T(T *dst, const ALfloat *src, ALint dststep, ALsizei samples)\
+{ \
+ ALsizei i; \
+ for(i = 0;i < samples;i++) \
+ dst[i*dststep] = T##_Sample(src[i]); \
+}
+
+DECL_TEMPLATE(ALbyte)
+DECL_TEMPLATE(ALubyte)
+DECL_TEMPLATE(ALshort)
+DECL_TEMPLATE(ALushort)
+DECL_TEMPLATE(ALint)
+DECL_TEMPLATE(ALuint)
+DECL_TEMPLATE(ALfloat)
+
+#undef DECL_TEMPLATE
+
+static void StoreSamples(ALvoid *dst, const ALfloat *src, ALint dststep, enum DevFmtType dsttype, ALsizei samples)
+{
+ switch(dsttype)
+ {
+ case DevFmtByte:
+ Store_ALbyte(dst, src, dststep, samples);
+ break;
+ case DevFmtUByte:
+ Store_ALubyte(dst, src, dststep, samples);
+ break;
+ case DevFmtShort:
+ Store_ALshort(dst, src, dststep, samples);
+ break;
+ case DevFmtUShort:
+ Store_ALushort(dst, src, dststep, samples);
+ break;
+ case DevFmtInt:
+ Store_ALint(dst, src, dststep, samples);
+ break;
+ case DevFmtUInt:
+ Store_ALuint(dst, src, dststep, samples);
+ break;
+ case DevFmtFloat:
+ Store_ALfloat(dst, src, dststep, samples);
+ break;
+ }
+}
+
+
+ALsizei SampleConverterAvailableOut(SampleConverter *converter, ALsizei srcframes)
+{
+ ALint prepcount = converter->mSrcPrepCount;
+ ALsizei increment = converter->mIncrement;
+ ALsizei DataPosFrac = converter->mFracOffset;
+ ALuint64 DataSize64;
+
+ if(prepcount < 0)
+ {
+ /* Negative prepcount means we need to skip that many input samples. */
+ if(-prepcount >= srcframes)
+ return 0;
+ srcframes += prepcount;
+ prepcount = 0;
+ }
+
+ if(prepcount < MAX_POST_SAMPLES+MAX_PRE_SAMPLES &&
+ MAX_POST_SAMPLES+MAX_PRE_SAMPLES-prepcount >= srcframes)
+ {
+ /* Not enough input samples to generate an output sample. */
+ return 0;
+ }
+
+ DataSize64 = prepcount;
+ DataSize64 += srcframes;
+ DataSize64 -= MAX_POST_SAMPLES+MAX_PRE_SAMPLES;
+ DataSize64 <<= FRACTIONBITS;
+ DataSize64 -= DataPosFrac;
+
+ /* If we have a full prep, we can generate at least one sample. */
+ return (ALsizei)clampu64(DataSize64/increment, 1, INT_MAX);
+}
+
+
+ALsizei SampleConverterInput(SampleConverter *converter, const ALvoid *src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes)
+{
+ const ALsizei SrcFrameSize = converter->mNumChannels * converter->mSrcTypeSize;
+ const ALsizei DstFrameSize = converter->mNumChannels * converter->mDstTypeSize;
+ const ALsizei increment = converter->mIncrement;
+ ALsizei pos = 0;
+
+ while(pos < dstframes)
+ {
+ ALfloat *restrict SrcData = ASSUME_ALIGNED(converter->mSrcSamples, 16);
+ ALfloat *restrict DstData = ASSUME_ALIGNED(converter->mDstSamples, 16);
+ ALint prepcount = converter->mSrcPrepCount;
+ ALsizei DataPosFrac = converter->mFracOffset;
+ ALuint64 DataSize64;
+ ALsizei DstSize;
+ ALint toread;
+ ALsizei chan;
+
+ if(prepcount < 0)
+ {
+ /* Negative prepcount means we need to skip that many input samples. */
+ if(-prepcount >= *srcframes)
+ {
+ converter->mSrcPrepCount = prepcount + *srcframes;
+ *srcframes = 0;
+ break;
+ }
+ src = (ALbyte*)src + SrcFrameSize*-prepcount;
+ *srcframes += prepcount;
+ prepcount = 0;
+ }
+ toread = mini(*srcframes, BUFFERSIZE-(MAX_POST_SAMPLES+MAX_PRE_SAMPLES));
+
+ if(prepcount < MAX_POST_SAMPLES+MAX_PRE_SAMPLES &&
+ MAX_POST_SAMPLES+MAX_PRE_SAMPLES-prepcount >= toread)
+ {
+ /* Not enough input samples to generate an output sample. Store
+ * what we're given for later.
+ */
+ for(chan = 0;chan < converter->mNumChannels;chan++)
+ LoadSamples(&converter->Chan[chan].mPrevSamples[prepcount],
+ (const ALbyte*)src + converter->mSrcTypeSize*chan,
+ converter->mNumChannels, converter->mSrcType, toread
+ );
+
+ converter->mSrcPrepCount = prepcount + toread;
+ *srcframes = 0;
+ break;
+ }
+
+ DataSize64 = prepcount;
+ DataSize64 += toread;
+ DataSize64 -= MAX_POST_SAMPLES+MAX_PRE_SAMPLES;
+ DataSize64 <<= FRACTIONBITS;
+ DataSize64 -= DataPosFrac;
+
+ /* If we have a full prep, we can generate at least one sample. */
+ DstSize = (ALsizei)clampu64(DataSize64/increment, 1, BUFFERSIZE);
+ DstSize = mini(DstSize, dstframes-pos);
+
+ for(chan = 0;chan < converter->mNumChannels;chan++)
+ {
+ const ALbyte *SrcSamples = (const ALbyte*)src + converter->mSrcTypeSize*chan;
+ ALbyte *DstSamples = (ALbyte*)dst + converter->mSrcTypeSize*chan;
+ const ALfloat *ResampledData;
+ ALsizei SrcDataEnd;
+
+ /* Load the previous samples into the source data first, then the
+ * new samples from the input buffer.
+ */
+ memcpy(SrcData, converter->Chan[chan].mPrevSamples,
+ prepcount*sizeof(ALfloat));
+ LoadSamples(SrcData + prepcount, SrcSamples,
+ converter->mNumChannels, converter->mSrcType, toread
+ );
+
+ /* Store as many prep samples for next time as possible, given the
+ * number of output samples being generated.
+ */
+ SrcDataEnd = (DataPosFrac + increment*DstSize)>>FRACTIONBITS;
+ if(SrcDataEnd >= prepcount+toread)
+ memset(converter->Chan[chan].mPrevSamples, 0,
+ sizeof(converter->Chan[chan].mPrevSamples));
+ else
+ {
+ size_t len = mini(MAX_PRE_SAMPLES+MAX_POST_SAMPLES, prepcount+toread-SrcDataEnd);
+ memcpy(converter->Chan[chan].mPrevSamples, &SrcData[SrcDataEnd],
+ len*sizeof(ALfloat));
+ memset(converter->Chan[chan].mPrevSamples+len, 0,
+ sizeof(converter->Chan[chan].mPrevSamples) - len*sizeof(ALfloat));
+ }
+
+ /* Now resample, and store the result in the output buffer. */
+ ResampledData = converter->mResample(NULL,
+ SrcData+MAX_PRE_SAMPLES, DataPosFrac, increment,
+ DstData, DstSize
+ );
+
+ StoreSamples(DstSamples, ResampledData, converter->mNumChannels,
+ converter->mDstType, DstSize);
+ }
+
+ /* Update the number of prep samples still available, as well as the
+ * fractional offset.
+ */
+ DataPosFrac += increment*DstSize;
+ converter->mSrcPrepCount = mini(MAX_PRE_SAMPLES+MAX_POST_SAMPLES,
+ prepcount+toread-(DataPosFrac>>FRACTIONBITS));
+ converter->mFracOffset = DataPosFrac & FRACTIONMASK;
+
+ /* Update the src and dst pointers in case there's still more to do. */
+ src = (const ALbyte*)src + SrcFrameSize*(DataPosFrac>>FRACTIONBITS);
+ *srcframes -= mini(*srcframes, (DataPosFrac>>FRACTIONBITS));
+
+ dst = (ALbyte*)dst + DstFrameSize*DstSize;
+ pos += DstSize;
+ }
+
+ return pos;
+}
diff --git a/Alc/converter.h b/Alc/converter.h
new file mode 100644
index 00000000..76b2520f
--- /dev/null
+++ b/Alc/converter.h
@@ -0,0 +1,42 @@
+#ifndef CONVERTER_H
+#define CONVERTER_H
+
+#include "alMain.h"
+#include "alu.h"
+
+#ifdef __cpluspluc
+extern "C" {
+#endif
+
+typedef struct SampleConverter {
+ enum DevFmtType mSrcType;
+ enum DevFmtType mDstType;
+ ALsizei mNumChannels;
+ ALsizei mSrcTypeSize;
+ ALsizei mDstTypeSize;
+
+ ALint mSrcPrepCount;
+
+ ALsizei mFracOffset;
+ ALsizei mIncrement;
+ ResamplerFunc mResample;
+
+ alignas(16) ALfloat mSrcSamples[BUFFERSIZE+MAX_PRE_SAMPLES+MAX_POST_SAMPLES];
+ alignas(16) ALfloat mDstSamples[BUFFERSIZE];
+
+ struct {
+ alignas(16) ALfloat mPrevSamples[MAX_PRE_SAMPLES+MAX_POST_SAMPLES];
+ } Chan[];
+} SampleConverter;
+
+SampleConverter *CreateSampleConverter(enum DevFmtType srcType, enum DevFmtType dstType, ALsizei numchans, ALsizei srcRate, ALsizei dstRate);
+void DestroySampleConverter(SampleConverter **converter);
+
+ALsizei SampleConverterInput(SampleConverter *converter, const ALvoid *src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes);
+ALsizei SampleConverterAvailableOut(SampleConverter *converter, ALsizei srcframes);
+
+#ifdef __cpluspluc
+}
+#endif
+
+#endif /* CONVERTER_H */
diff --git a/Alc/mixer.c b/Alc/mixer.c
index 2a30e323..90f3e05e 100644
--- a/Alc/mixer.c
+++ b/Alc/mixer.c
@@ -44,15 +44,6 @@ static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE,
extern inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *restrict frac_arr, ALint *restrict pos_arr, ALsizei size);
-enum Resampler {
- PointResampler,
- LinearResampler,
- FIR4Resampler,
- BSincResampler,
-
- ResamplerDefault = LinearResampler
-};
-
/* BSinc requires up to 11 extra samples before the current position, and 12 after. */
static_assert(MAX_PRE_SAMPLES >= 11, "MAX_PRE_SAMPLES must be at least 11!");
static_assert(MAX_POST_SAMPLES >= 12, "MAX_POST_SAMPLES must be at least 12!");
@@ -103,7 +94,7 @@ static inline HrtfMixerFunc SelectHrtfMixer(void)
return MixHrtf_C;
}
-static inline ResamplerFunc SelectResampler(enum Resampler resampler)
+ResamplerFunc SelectResampler(enum Resampler resampler)
{
switch(resampler)
{
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2cb95402..ee1c9e57 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -689,6 +689,7 @@ SET(ALC_OBJS Alc/ALc.c
Alc/alcConfig.c
Alc/alcRing.c
Alc/bs2b.c
+ Alc/converter.c
Alc/effects/chorus.c
Alc/effects/compressor.c
Alc/effects/dedicated.c
diff --git a/OpenAL32/Include/alu.h b/OpenAL32/Include/alu.h
index 4d0ee196..51c43d85 100644
--- a/OpenAL32/Include/alu.h
+++ b/OpenAL32/Include/alu.h
@@ -302,6 +302,15 @@ inline ALfloat resample_fir4(ALfloat val0, ALfloat val1, ALfloat val2, ALfloat v
}
+enum Resampler {
+ PointResampler,
+ LinearResampler,
+ FIR4Resampler,
+ BSincResampler,
+
+ ResamplerDefault = LinearResampler
+};
+
enum HrtfRequestMode {
Hrtf_Default = 0,
Hrtf_Enable = 1,
@@ -313,6 +322,7 @@ void aluInitMixer(void);
MixerFunc SelectMixer(void);
RowMixerFunc SelectRowMixer(void);
+ResamplerFunc SelectResampler(enum Resampler resampler);
/* aluInitRenderer
*