Commit 31086c5d authored by David Reid's avatar David Reid

Fix bugs in ma_resampler_get_required_input_frame_count().

parent 5df3c0ce
...@@ -4285,7 +4285,7 @@ static MA_INLINE ma_int32 ma_mix_s32_fast(ma_int32 x, ma_int32 y, float a) ...@@ -4285,7 +4285,7 @@ static MA_INLINE ma_int32 ma_mix_s32_fast(ma_int32 x, ma_int32 y, float a)
return x + r1; return x + r1;
} }
static MA_INLINE ma_int32 ma_mix_s16_fast(ma_int32 x, ma_int32 y, float a) static MA_INLINE ma_int16 ma_mix_s16_fast(ma_int32 x, ma_int32 y, float a)
{ {
return (ma_int16)ma_mix_s32_fast(x, y, a); return (ma_int16)ma_mix_s32_fast(x, y, a);
} }
/* Resampling research. Public domain. */ /* Resampling research. Public domain. */
/*
TODO:
- Add documentation about Speex and how initialization will fail if it's unavailable.
*/
#ifndef ma_resampler_h #ifndef ma_resampler_h
#define ma_resampler_h #define ma_resampler_h
...@@ -7,8 +12,7 @@ ...@@ -7,8 +12,7 @@
typedef enum typedef enum
{ {
ma_resample_algorithm_linear_lpf = 0, /* Linear with a biquad low pass filter. Default. */ ma_resample_algorithm_linear, /* Fastest, lowest quality. Optional low-pass filtering. Default. */
ma_resample_algorithm_linear, /* Fastest, lowest quality. */
ma_resample_algorithm_speex ma_resample_algorithm_speex
} ma_resample_algorithm; } ma_resample_algorithm;
...@@ -21,13 +25,10 @@ typedef struct ...@@ -21,13 +25,10 @@ typedef struct
ma_resample_algorithm algorithm; ma_resample_algorithm algorithm;
struct struct
{ {
int _unused; ma_bool32 enableLPF;
ma_uint32 lpfCutoffFrequency;
} linear; } linear;
struct struct
{
ma_uint32 cutoffFrequency;
} linearLPF;
struct
{ {
int quality; /* 0 to 10. Defaults to 3. */ int quality; /* 0 to 10. Defaults to 3. */
} speex; } speex;
...@@ -110,24 +111,12 @@ number of output frames. ...@@ -110,24 +111,12 @@ number of output frames.
The returned value does not include cached input frames. It only returns the number of extra frames that would need to be The returned value does not include cached input frames. It only returns the number of extra frames that would need to be
read from the client in order to output the specified number of output frames. read from the client in order to output the specified number of output frames.
When the end of input mode is set to ma_resampler_end_of_input_mode_no_consume, the input frames sitting in the filter
window are not included in the calculation.
*/ */
ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResampler, ma_uint64 outputFrameCount); ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResampler, ma_uint64 outputFrameCount);
/* /*
Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of
input frames from the client. input frames from the client.
A detail to keep in mind is how cached input frames are handled. This function calculates the output frame count based on
inputFrameCount + ma_resampler_get_cached_input_time(). It essentially calcualtes how many output frames will be returned
if an additional inputFrameCount frames were read from the client and consumed by the resampler. You can adjust the return
value by ma_resampler_get_cached_output_frame_count() which calculates the number of output frames that can be output from
the currently cached input.
When the end of input mode is set to ma_resampler_end_of_input_mode_no_consume, the input frames sitting in the filter
window are not included in the calculation.
*/ */
ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler, ma_uint64 inputFrameCount); ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler, ma_uint64 inputFrameCount);
...@@ -185,7 +174,7 @@ static ma_result ma_resampler__init_lpf(ma_resampler* pResampler) ...@@ -185,7 +174,7 @@ static ma_result ma_resampler__init_lpf(ma_resampler* pResampler)
pResampler->state.linear.t = -1; /* This must be set to -1 for the linear backend. It's used to indicate that the first frame needs to be loaded. */ pResampler->state.linear.t = -1; /* This must be set to -1 for the linear backend. It's used to indicate that the first frame needs to be loaded. */
lpfConfig = ma_lpf_config_init(pResampler->config.format, pResampler->config.channels, pResampler->config.sampleRateOut, pResampler->config.linearLPF.cutoffFrequency); lpfConfig = ma_lpf_config_init(pResampler->config.format, pResampler->config.channels, pResampler->config.sampleRateOut, pResampler->config.linear.lpfCutoffFrequency);
if (lpfConfig.cutoffFrequency == 0) { if (lpfConfig.cutoffFrequency == 0) {
lpfConfig.cutoffFrequency = ma_min(pResampler->config.sampleRateIn, pResampler->config.sampleRateOut) / 2; lpfConfig.cutoffFrequency = ma_min(pResampler->config.sampleRateIn, pResampler->config.sampleRateOut) / 2;
} }
...@@ -212,12 +201,16 @@ ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pR ...@@ -212,12 +201,16 @@ ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pR
switch (pConfig->algorithm) switch (pConfig->algorithm)
{ {
case ma_resample_algorithm_linear: case ma_resample_algorithm_linear:
case ma_resample_algorithm_linear_lpf:
{ {
/* We need to initialize the time to -1 so that ma_resampler_process() can know that it needs to load the buffer with an initial frame. */
pResampler->state.linear.t = -1;
if (pConfig->linear.enableLPF) {
result = ma_resampler__init_lpf(pResampler); result = ma_resampler__init_lpf(pResampler);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
return result; return result;
} }
}
} break; } break;
case ma_resample_algorithm_speex: case ma_resample_algorithm_speex:
...@@ -278,12 +271,6 @@ static ma_result ma_resampler_process__seek__linear(ma_resampler* pResampler, co ...@@ -278,12 +271,6 @@ static ma_result ma_resampler_process__seek__linear(ma_resampler* pResampler, co
return MA_SUCCESS; return MA_SUCCESS;
} }
static ma_result ma_resampler_process__seek__linear_lpf(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut)
{
/* TODO: Proper linear LPF implementation. */
return ma_resampler_process__seek__linear(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut);
}
#if defined(MA_HAS_SPEEX_RESAMPLER) #if defined(MA_HAS_SPEEX_RESAMPLER)
static ma_result ma_resampler_process__seek__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) static ma_result ma_resampler_process__seek__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut)
{ {
...@@ -308,20 +295,21 @@ static ma_result ma_resampler_process__seek(ma_resampler* pResampler, const void ...@@ -308,20 +295,21 @@ static ma_result ma_resampler_process__seek(ma_resampler* pResampler, const void
return ma_resampler_process__seek__linear(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); return ma_resampler_process__seek__linear(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut);
} break; } break;
case ma_resample_algorithm_linear_lpf:
{
return ma_resampler_process__seek__linear_lpf(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut);
} break;
case ma_resample_algorithm_speex: case ma_resample_algorithm_speex:
{ {
#if defined(MA_HAS_SPEEX_RESAMPLER) #if defined(MA_HAS_SPEEX_RESAMPLER)
return ma_resampler_process__seek__speex(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); return ma_resampler_process__seek__speex(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut);
#else
break;
#endif #endif
} break; };
default: return MA_INVALID_ARGS; /* Should never hit this. */ default: break;
} }
/* Should never hit this. */
MA_ASSERT(MA_FALSE);
return MA_INVALID_ARGS;
} }
static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
...@@ -347,7 +335,7 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co ...@@ -347,7 +335,7 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co
frameCountOut = *pFrameCountOut; frameCountOut = *pFrameCountOut;
frameCountIn = *pFrameCountIn; frameCountIn = *pFrameCountIn;
if (frameCountOut == 0 || frameCountIn == 0) { if (frameCountOut == 0) {
return MA_INVALID_ARGS; /* Nothing to do. */ return MA_INVALID_ARGS; /* Nothing to do. */
} }
...@@ -384,10 +372,6 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co ...@@ -384,10 +372,6 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co
} }
for (;;) { for (;;) {
if (iFrameOut >= frameCountOut || iFrameIn >= frameCountIn) {
break;
}
/* We can't interpolate if our interpolation factor (time relative to x0) is greater than 1. */ /* We can't interpolate if our interpolation factor (time relative to x0) is greater than 1. */
if (pResampler->state.linear.t > 1) { if (pResampler->state.linear.t > 1) {
/* Need to load the next input frame. */ /* Need to load the next input frame. */
...@@ -489,32 +473,18 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co ...@@ -489,32 +473,18 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co
/* Move time forward. */ /* Move time forward. */
pResampler->state.linear.t += ratioInOut; pResampler->state.linear.t += ratioInOut;
iFrameOut += 1; iFrameOut += 1;
if (iFrameOut >= frameCountOut || iFrameIn >= frameCountIn) {
break;
}
} }
/* Here is where we set the number of frames that were consumed. */ /* Here is where we set the number of frames that were consumed. */
*pFrameCountOut = iFrameOut; *pFrameCountOut = iFrameOut;
*pFrameCountIn = iFrameIn; *pFrameCountIn = iFrameIn;
return MA_SUCCESS; /* Low-pass filter if it's enabled. */
} if (pResampler->config.linear.enableLPF && pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) {
static ma_result ma_resampler_process__read__linear_lpf(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
{
/* To do this we just read using the non-filtered linear pipeline, and then do an in-place filter on the output buffer. */
ma_result result;
MA_ASSERT(pResampler != NULL);
MA_ASSERT(pFramesOut != NULL);
MA_ASSERT(pFrameCountOut != NULL);
MA_ASSERT(pFrameCountIn != NULL);
result = ma_resampler_process__read__linear(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
if (result != MA_SUCCESS) {
return result;
}
/* Now just do an in-place low-pass filter. No need to spend time filtering if the sample rates are the same. */
if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) {
return ma_lpf_process(&pResampler->state.linear.lpf, pFramesOut, pFramesOut, *pFrameCountOut); return ma_lpf_process(&pResampler->state.linear.lpf, pFramesOut, pFramesOut, *pFrameCountOut);
} else { } else {
return MA_SUCCESS; return MA_SUCCESS;
...@@ -608,11 +578,6 @@ static ma_result ma_resampler_process__read(ma_resampler* pResampler, const void ...@@ -608,11 +578,6 @@ static ma_result ma_resampler_process__read(ma_resampler* pResampler, const void
return ma_resampler_process__read__linear(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); return ma_resampler_process__read__linear(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
} }
case ma_resample_algorithm_linear_lpf:
{
return ma_resampler_process__read__linear_lpf(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
}
case ma_resample_algorithm_speex: case ma_resample_algorithm_speex:
{ {
#if defined(MA_HAS_SPEEX_RESAMPLER) #if defined(MA_HAS_SPEEX_RESAMPLER)
...@@ -666,12 +631,10 @@ ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampleRateIn ...@@ -666,12 +631,10 @@ ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampleRateIn
{ {
case ma_resample_algorithm_linear: case ma_resample_algorithm_linear:
{ {
} break; /* If we are using low-pass filtering we need to reinitialize the filter since it depends on the sample rate. */
if (pResampler->config.linear.enableLPF) {
case ma_resample_algorithm_linear_lpf:
{
/* We need to reinitialize the low-pass filter. */
ma_resampler__init_lpf(pResampler); ma_resampler__init_lpf(pResampler);
}
} break; } break;
case ma_resample_algorithm_speex: case ma_resample_algorithm_speex:
...@@ -732,23 +695,29 @@ ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResampler, ...@@ -732,23 +695,29 @@ ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResampler,
switch (pResampler->config.algorithm) switch (pResampler->config.algorithm)
{ {
case ma_resample_algorithm_linear: case ma_resample_algorithm_linear:
case ma_resample_algorithm_linear_lpf:
{ {
/* /*
All we're doing is calculating the number of whole input frames that'll be processed after outputFrameCount frames are returned. We can The first output frame is treated a little different to the rest because it is never interpolated - the first output frame is always the
determine this by just looking at where the input time will be at the end of it. same as the first input frame. We can know if we're loading the first frame by checking if the input time is < 0.
*/
/*
The linear backend initializes t to -1 at start up to trigger the initial load of the first sample. In this case we will always load at
least two whole input frames before outputting the first output frame.
*/ */
ma_uint64 count = 0;
double t = pResampler->state.linear.t; double t = pResampler->state.linear.t;
if (t < 0) { if (t < 0) {
t = 2; count = 1;
t = 1;
}
/* If the input time is greater than 1 we consume any whole input frames. */
if (t > 1) {
count = (ma_uint64)t;
t -= count;
} }
return (ma_uint64)ceil(pResampler->state.linear.t + (outputFrameCount * ratioInOut)) - 1; /* At this point we are guaranteed to get at least one output frame from the cached input (not requiring an additional input). */
outputFrameCount -= 1;
count += (ma_uint64)ceil(t + (outputFrameCount * ratioInOut)) - 1;
return count;
} }
case ma_resample_algorithm_speex: case ma_resample_algorithm_speex:
...@@ -785,7 +754,6 @@ ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler, ...@@ -785,7 +754,6 @@ ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler,
switch (pResampler->config.algorithm) switch (pResampler->config.algorithm)
{ {
case ma_resample_algorithm_linear: case ma_resample_algorithm_linear:
case ma_resample_algorithm_linear_lpf:
{ {
/* /*
The linear backend initializes t to -1 at start up to trigger the initial load of the first sample. In this case we will always load at The linear backend initializes t to -1 at start up to trigger the initial load of the first sample. In this case we will always load at
...@@ -796,7 +764,7 @@ ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler, ...@@ -796,7 +764,7 @@ ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler,
t = 2; t = 2;
} }
return (ma_uint64)ceil((pResampler->state.linear.t + inputFrameCount) / ratioInOut) - 1; return (ma_uint64)ceil((t + inputFrameCount) / ratioInOut) - 1;
} }
case ma_resample_algorithm_speex: case ma_resample_algorithm_speex:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment