mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-21 15:56:58 +02:00
Resampling testing.
This commit is contained in:
@@ -1,3 +1,200 @@
|
|||||||
|
/* Resampling research. Public domain. */
|
||||||
|
|
||||||
|
#ifndef ma_resampler_h
|
||||||
|
#define ma_resampler_h
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
ma_resample_algorithm_linear = 0, /* Default. Fastest. */
|
||||||
|
ma_resample_algorithm_sinc /* Slower. */
|
||||||
|
} ma_resample_algorithm;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Simple high-level API for resampling 32-bit floating point samples.
|
||||||
|
|
||||||
|
Use ma_calculate_frame_count_after_src() to determine the required output buffer size.
|
||||||
|
*/
|
||||||
|
ma_result ma_resample_f32(ma_resample_algorithm algorithm, ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn);
|
||||||
|
|
||||||
|
#endif /* ma_resampler_h */
|
||||||
|
|
||||||
|
/*
|
||||||
|
Implementation
|
||||||
|
*/
|
||||||
|
#ifdef MINIAUDIO_IMPLEMENTATION
|
||||||
|
|
||||||
|
#ifndef MA_RESAMPLER_MIN_RATIO
|
||||||
|
#define MA_RESAMPLER_MIN_RATIO 0.02083333
|
||||||
|
#endif
|
||||||
|
#ifndef MA_RESAMPLER_MAX_RATIO
|
||||||
|
#define MA_RESAMPLER_MAX_RATIO 48.0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ma_result ma_resample_f32__linear(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn)
|
||||||
|
{
|
||||||
|
double ratio = (double)sampleRateIn / (double)sampleRateOut;
|
||||||
|
double timeIn = 0;
|
||||||
|
double timeOut = 0;
|
||||||
|
|
||||||
|
/* Fast path if the sample rates are the same. */
|
||||||
|
if (sampleRateOut == sampleRateIn) {
|
||||||
|
MA_COPY_MEMORY(pSamplesOut, pSamplesIn, (size_t)ma_min(sampleCountOut, sampleCountIn) * sizeof(float));
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do nothing if there's no input. */
|
||||||
|
if (sampleCountOut == 0 || sampleCountIn == 0) {
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* The first output sample should always be the same as the input sample. */
|
||||||
|
pSamplesOut[0] = pSamplesIn[0];
|
||||||
|
timeIn += ratio;
|
||||||
|
timeOut += 1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ma_uint64 iTimeIn;
|
||||||
|
ma_uint64 iTimeOut;
|
||||||
|
|
||||||
|
iTimeIn = (ma_uint64)timeIn;
|
||||||
|
if (iTimeIn >= sampleCountIn) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
iTimeOut = (ma_uint64)timeOut;
|
||||||
|
if (iTimeOut >= sampleCountOut) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To linearly interpolate we need the previous and next input samples. */
|
||||||
|
{
|
||||||
|
ma_uint64 iTimeInPrev = iTimeIn;
|
||||||
|
ma_uint64 iTimeInNext = (ma_uint64)ceil(timeIn);
|
||||||
|
|
||||||
|
if (iTimeInNext >= sampleCountIn) {
|
||||||
|
iTimeInNext = iTimeInPrev; /* <-- We could instead terminate here which would make the output a few samples shorter. */
|
||||||
|
}
|
||||||
|
|
||||||
|
pSamplesOut[iTimeOut] = ma_mix_f32_fast(pSamplesIn[iTimeInPrev], pSamplesIn[iTimeInNext], (float)(timeIn - iTimeIn));
|
||||||
|
|
||||||
|
/* Try some kind of low-pass filter. */
|
||||||
|
#if 1
|
||||||
|
{
|
||||||
|
double cutoff = ma_min(sampleRateIn, sampleRateOut) * 0.5;
|
||||||
|
double RC = 1.0/(cutoff*MA_TAU_D);
|
||||||
|
double dt = 1.0/sampleRateOut;
|
||||||
|
float alpha = (float)(dt/(RC+dt));
|
||||||
|
pSamplesOut[iTimeOut] = pSamplesOut[iTimeOut-1] + (alpha*(pSamplesOut[iTimeOut] - pSamplesOut[iTimeOut-1]));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
timeIn += ratio;
|
||||||
|
timeOut += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double ma_sinc_hann(double n, int N)
|
||||||
|
{
|
||||||
|
double s = sin(MA_PI_D*n/N);
|
||||||
|
return s*s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_result ma_resample_f32__sinc(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn)
|
||||||
|
{
|
||||||
|
double ratio = (double)sampleRateIn / (double)sampleRateOut;
|
||||||
|
double timeIn = 0;
|
||||||
|
double timeOut = 0;
|
||||||
|
double samplingPeriodIn = 1.0/sampleRateIn;
|
||||||
|
int windowWidth = 32;
|
||||||
|
|
||||||
|
/* Fast path if the sample rates are the same. */
|
||||||
|
if (sampleRateOut == sampleRateIn) {
|
||||||
|
MA_COPY_MEMORY(pSamplesOut, pSamplesIn, (size_t)ma_min(sampleCountOut, sampleCountIn) * sizeof(float));
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do nothing if there's no input. */
|
||||||
|
if (sampleCountOut == 0 || sampleCountIn == 0) {
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* The first output sample should always be the same as the input sample. */
|
||||||
|
pSamplesOut[0] = pSamplesIn[0];
|
||||||
|
timeIn += ratio;
|
||||||
|
timeOut += 1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ma_uint64 iTimeIn;
|
||||||
|
ma_uint64 iTimeOut;
|
||||||
|
|
||||||
|
iTimeIn = (ma_uint64)timeIn;
|
||||||
|
if (iTimeIn >= sampleCountIn) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
iTimeOut = (ma_uint64)timeOut;
|
||||||
|
if (iTimeOut >= sampleCountOut) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To linearly interpolate we need the previous and next input samples. */
|
||||||
|
{
|
||||||
|
ma_uint64 iTimeInPrev = iTimeIn;
|
||||||
|
ma_uint64 iTimeInNext = (ma_uint64)ceil(timeIn);
|
||||||
|
|
||||||
|
if (iTimeInNext >= sampleCountIn) {
|
||||||
|
iTimeInNext = iTimeInPrev; /* <-- We could instead terminate here which would make the output a few samples shorter. */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
pSamplesOut[iTimeOut] = ma_mix_f32_fast(pSamplesIn[iTimeInPrev], pSamplesIn[iTimeInNext], (float)(timeIn - iTimeIn));
|
||||||
|
#else
|
||||||
|
pSamplesOut[iTimeOut] = 0;
|
||||||
|
for (int i = -windowWidth; i < windowWidth; i += 1) {
|
||||||
|
if ((i < 0 && -i < iTimeIn) || i > 0 && i < sampleCountIn) {
|
||||||
|
#if 0
|
||||||
|
double t = (iTimeIn+n);
|
||||||
|
double s = ma_sinc(sampleRateIn*t);
|
||||||
|
pSamplesOut[iTimeOut] += pSamplesIn[(ma_uint64)(iTimeIn+n)] * s;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
timeIn += ratio;
|
||||||
|
timeOut += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_result ma_resample_f32(ma_resample_algorithm algorithm, ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn)
|
||||||
|
{
|
||||||
|
if (pSamplesOut == NULL || pSamplesIn == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (algorithm)
|
||||||
|
{
|
||||||
|
case ma_resample_algorithm_linear: return ma_resample_f32__linear(sampleRateOut, sampleRateIn, sampleCountOut, pSamplesOut, sampleCountIn, pSamplesIn);
|
||||||
|
case ma_resample_algorithm_sinc: return ma_resample_f32__sinc (sampleRateOut, sampleRateIn, sampleCountOut, pSamplesOut, sampleCountIn, pSamplesIn);
|
||||||
|
default: return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MINIAUDIO_IMPLEMENTATION */
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
/*
|
/*
|
||||||
Consider this code public domain.
|
Consider this code public domain.
|
||||||
|
|
||||||
@@ -1204,3 +1401,4 @@ ma_uint64 ma_resampler_seek__sinc(ma_resampler* pResampler, ma_uint64 frameCount
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
#endif /* 0 (Old Implementation)*/
|
||||||
@@ -1,11 +1,122 @@
|
|||||||
|
#define DR_FLAC_IMPLEMENTATION
|
||||||
|
#include "../../extras/dr_flac.h" /* Enables FLAC decoding. */
|
||||||
|
#define DR_MP3_IMPLEMENTATION
|
||||||
|
#include "../../extras/dr_mp3.h" /* Enables MP3 decoding. */
|
||||||
#define DR_WAV_IMPLEMENTATION
|
#define DR_WAV_IMPLEMENTATION
|
||||||
#include "../../../../dr_libs/dr_wav.h"
|
#include "../../extras/dr_wav.h" /* Enables WAV decoding. */
|
||||||
|
|
||||||
|
|
||||||
#define MA_DEBUG_OUTPUT
|
#define MA_DEBUG_OUTPUT
|
||||||
#define MINIAUDIO_IMPLEMENTATION
|
#define MINIAUDIO_IMPLEMENTATION
|
||||||
#include "../../miniaudio.h"
|
#include "../../miniaudio.h"
|
||||||
#include "../ma_resampler.h"
|
#include "../ma_resampler.h"
|
||||||
|
|
||||||
|
#define USE_NEW_RESAMPLER 1
|
||||||
|
|
||||||
|
ma_uint64 g_outputFrameCount;
|
||||||
|
void* g_pRunningFrameData;
|
||||||
|
|
||||||
|
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||||
|
{
|
||||||
|
ma_uint32 framesToCopy;
|
||||||
|
|
||||||
|
framesToCopy = frameCount;
|
||||||
|
if (framesToCopy > (ma_uint32)g_outputFrameCount) {
|
||||||
|
framesToCopy = (ma_uint32)g_outputFrameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
MA_COPY_MEMORY(pOutput, g_pRunningFrameData, framesToCopy * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels));
|
||||||
|
|
||||||
|
g_pRunningFrameData = ma_offset_ptr(g_pRunningFrameData, framesToCopy * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels));
|
||||||
|
g_outputFrameCount -= framesToCopy;
|
||||||
|
|
||||||
|
(void)pInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ma_result result;
|
||||||
|
ma_decoder_config decoderConfig;
|
||||||
|
ma_uint64 inputFrameCount;
|
||||||
|
void* pInputFrameData;
|
||||||
|
ma_uint64 outputFrameCount = 0;
|
||||||
|
void* pOutputFrameData = NULL;
|
||||||
|
ma_device_config deviceConfig;
|
||||||
|
ma_device device;
|
||||||
|
ma_backend backend;
|
||||||
|
|
||||||
|
/* This example just resamples the input file to an exclusive device's native sample rate. */
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("No input file.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoderConfig = ma_decoder_config_init(ma_format_f32, 1, 0);
|
||||||
|
|
||||||
|
result = ma_decode_file(argv[1], &decoderConfig, &inputFrameCount, &pInputFrameData);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
return (int)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
backend = ma_backend_wasapi;
|
||||||
|
|
||||||
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||||
|
#if USE_NEW_RESAMPLER
|
||||||
|
deviceConfig.playback.shareMode = ma_share_mode_exclusive; /* <-- We need to use exclusive mode to ensure there's no resampling going on by the OS. */
|
||||||
|
deviceConfig.sampleRate = 0; /* <-- Always use the device's native sample rate. */
|
||||||
|
#else
|
||||||
|
deviceConfig.playback.shareMode = ma_share_mode_shared; /* <-- We need to use exclusive mode to ensure there's no resampling going on by the OS. */
|
||||||
|
deviceConfig.sampleRate = decoderConfig.sampleRate;
|
||||||
|
#endif
|
||||||
|
deviceConfig.playback.format = decoderConfig.format;
|
||||||
|
deviceConfig.playback.channels = decoderConfig.channels;
|
||||||
|
deviceConfig.dataCallback = data_callback;
|
||||||
|
deviceConfig.pUserData = NULL;
|
||||||
|
|
||||||
|
if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
|
||||||
|
printf("Failed to open playback device.\n");
|
||||||
|
ma_free(pInputFrameData);
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if USE_NEW_RESAMPLER
|
||||||
|
/* Resample. */
|
||||||
|
outputFrameCount = ma_calculate_frame_count_after_src(device.sampleRate, decoderConfig.sampleRate, inputFrameCount);
|
||||||
|
pOutputFrameData = ma_malloc((size_t)(outputFrameCount * ma_get_bytes_per_frame(device.playback.format, device.playback.channels)));
|
||||||
|
if (pOutputFrameData == NULL) {
|
||||||
|
printf("Out of memory.\n");
|
||||||
|
ma_free(pInputFrameData);
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_resample_f32(ma_resample_algorithm_sinc, device.playback.internalSampleRate, decoderConfig.sampleRate, outputFrameCount, pOutputFrameData, inputFrameCount, pInputFrameData);
|
||||||
|
|
||||||
|
g_pRunningFrameData = pOutputFrameData;
|
||||||
|
g_outputFrameCount = outputFrameCount;
|
||||||
|
#else
|
||||||
|
g_pRunningFrameData = pInputFrameData;
|
||||||
|
g_outputFrameCount = inputFrameCount;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ma_device_start(&device) != MA_SUCCESS) {
|
||||||
|
printf("Failed to start playback device.\n");
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
ma_free(pInputFrameData);
|
||||||
|
ma_free(pOutputFrameData);
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Press Enter to quit...");
|
||||||
|
getchar();
|
||||||
|
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
ma_free(pInputFrameData);
|
||||||
|
ma_free(pOutputFrameData);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
#define SAMPLE_RATE_IN 44100
|
#define SAMPLE_RATE_IN 44100
|
||||||
#define SAMPLE_RATE_OUT 44100
|
#define SAMPLE_RATE_OUT 44100
|
||||||
#define CHANNELS 1
|
#define CHANNELS 1
|
||||||
@@ -71,4 +182,5 @@ int main(int argc, char** argv)
|
|||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
+12
-12
@@ -319,12 +319,12 @@
|
|||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\examples\simple_playback.c">
|
<ClCompile Include="..\examples\simple_playback.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\examples\simple_playback_emscripten.c">
|
<ClCompile Include="..\examples\simple_playback_emscripten.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
@@ -335,12 +335,12 @@
|
|||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\research\tests\ma_resampler_test_0.c">
|
<ClCompile Include="..\research\tests\ma_resampler_test_0.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">false</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="ma_dithering.c">
|
<ClCompile Include="ma_dithering.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
|
|||||||
Reference in New Issue
Block a user