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)
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);
}
/* Resampling research. Public domain. */
/*
TODO:
- Add documentation about Speex and how initialization will fail if it's unavailable.
*/
#ifndef ma_resampler_h
#define ma_resampler_h
......@@ -7,8 +12,7 @@
typedef enum
{
ma_resample_algorithm_linear_lpf = 0, /* Linear with a biquad low pass filter. Default. */
ma_resample_algorithm_linear, /* Fastest, lowest quality. */
ma_resample_algorithm_linear, /* Fastest, lowest quality. Optional low-pass filtering. Default. */
ma_resample_algorithm_speex
} ma_resample_algorithm;
......@@ -21,13 +25,10 @@ typedef struct
ma_resample_algorithm algorithm;
struct
{
int _unused;
ma_bool32 enableLPF;
ma_uint32 lpfCutoffFrequency;
} linear;
struct
{
ma_uint32 cutoffFrequency;
} linearLPF;
struct
{
int quality; /* 0 to 10. Defaults to 3. */
} speex;
......@@ -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
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);
/*
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.
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);
......@@ -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. */
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) {
lpfConfig.cutoffFrequency = ma_min(pResampler->config.sampleRateIn, pResampler->config.sampleRateOut) / 2;
}
......@@ -212,11 +201,15 @@ ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pR
switch (pConfig->algorithm)
{
case ma_resample_algorithm_linear:
case ma_resample_algorithm_linear_lpf:
{
result = ma_resampler__init_lpf(pResampler);
if (result != MA_SUCCESS) {
return result;
/* 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);
if (result != MA_SUCCESS) {
return result;
}
}
} break;
......@@ -278,12 +271,6 @@ static ma_result ma_resampler_process__seek__linear(ma_resampler* pResampler, co
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)
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
return ma_resampler_process__seek__linear(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut);
} break;
case ma_resample_algorithm_linear_lpf:
{
return ma_resampler_process__seek__linear_lpf(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut);
} break;
case ma_resample_algorithm_speex:
{
#if defined(MA_HAS_SPEEX_RESAMPLER)
return ma_resampler_process__seek__speex(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut);
#else
break;
#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)
......@@ -347,7 +335,7 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co
frameCountOut = *pFrameCountOut;
frameCountIn = *pFrameCountIn;
if (frameCountOut == 0 || frameCountIn == 0) {
if (frameCountOut == 0) {
return MA_INVALID_ARGS; /* Nothing to do. */
}
......@@ -384,10 +372,6 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co
}
for (;;) {
if (iFrameOut >= frameCountOut || iFrameIn >= frameCountIn) {
break;
}
/* We can't interpolate if our interpolation factor (time relative to x0) is greater than 1. */
if (pResampler->state.linear.t > 1) {
/* Need to load the next input frame. */
......@@ -489,32 +473,18 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, co
/* Move time forward. */
pResampler->state.linear.t += ratioInOut;
iFrameOut += 1;
if (iFrameOut >= frameCountOut || iFrameIn >= frameCountIn) {
break;
}
}
/* Here is where we set the number of frames that were consumed. */
*pFrameCountOut = iFrameOut;
*pFrameCountIn = iFrameIn;
return MA_SUCCESS;
}
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) {
/* Low-pass filter if it's enabled. */
if (pResampler->config.linear.enableLPF && pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) {
return ma_lpf_process(&pResampler->state.linear.lpf, pFramesOut, pFramesOut, *pFrameCountOut);
} else {
return MA_SUCCESS;
......@@ -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);
}
case ma_resample_algorithm_linear_lpf:
{
return ma_resampler_process__read__linear_lpf(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
}
case ma_resample_algorithm_speex:
{
#if defined(MA_HAS_SPEEX_RESAMPLER)
......@@ -666,12 +631,10 @@ ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampleRateIn
{
case ma_resample_algorithm_linear:
{
} break;
case ma_resample_algorithm_linear_lpf:
{
/* We need to reinitialize the low-pass filter. */
ma_resampler__init_lpf(pResampler);
/* 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) {
ma_resampler__init_lpf(pResampler);
}
} break;
case ma_resample_algorithm_speex:
......@@ -732,23 +695,29 @@ ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResampler,
switch (pResampler->config.algorithm)
{
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
determine this by just looking at where the input time will be at the end of it.
*/
/*
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.
The first output frame is treated a little different to the rest because it is never interpolated - the first output frame is always the
same as the first input frame. We can know if we're loading the first frame by checking if the input time is < 0.
*/
ma_uint64 count = 0;
double t = pResampler->state.linear.t;
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:
......@@ -785,7 +754,6 @@ ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler,
switch (pResampler->config.algorithm)
{
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
......@@ -796,7 +764,7 @@ ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler,
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:
......
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