diff options
author | Chris Robinson <[email protected]> | 2017-04-10 09:17:10 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2017-04-10 09:26:06 -0700 |
commit | 6cc69c8d94744d66e68ffffb9b71f6714d86e710 (patch) | |
tree | 1fd85a1f2eb40e5383065560bb8d1a0de8df06ed /Alc/converter.c | |
parent | 81527cdbddc52338f5fb3c8b79139bf9d9186d3a (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.
Diffstat (limited to 'Alc/converter.c')
-rw-r--r-- | Alc/converter.c | 332 |
1 files changed, 332 insertions, 0 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; +} |