Files
UnrealEngine/Engine/Source/Runtime/SignalProcessing/Private/SampleRateConverter.cpp
2025-05-18 13:04:45 +08:00

227 lines
6.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DSP/SampleRateConverter.h"
namespace Audio
{
static float GetFloatSampleValue(const float InSampleValue)
{
return InSampleValue;
}
static float GetFloatSampleValue(const int16 InSampleValue)
{
return (float)InSampleValue / 32767.0f;
}
class FSampleRateConverter : public ISampleRateConverter
{
public:
FSampleRateConverter()
: CurrentFrameIndex(0)
, NextFrameIndex(1)
, FrameAlpha(0.0f)
, NumChannels(0)
, SampleRateRatio(1.0f)
, TargetSampleRateRatio(1.0f)
, SampleRatioDelta(0.0f)
, NumSampleRatioFrameTicks(0)
, CurrentSampleRatioFrameTick(0)
, bUsePreviousChunkFrame(false)
{
}
virtual ~FSampleRateConverter() {}
virtual void Init(const float InSampleRateRatio, const int32 InNumChannels) override
{
CurrentFrameIndex = 0;
NextFrameIndex = 1;
FrameAlpha = 0.0f;
SetSampleRateRatio(InSampleRateRatio, 0);
NumChannels = InNumChannels;
bUsePreviousChunkFrame = false;
PreviousChunkFrame.Reset();
PreviousChunkFrame.AddZeroed(NumChannels);
}
virtual void SetSampleRateRatio(const float InSampleRateRatio, const int32 NumInterpolationFrames) override
{
NumSampleRatioFrameTicks = FMath::Max(NumInterpolationFrames, 0);
CurrentSampleRatioFrameTick = 0;
if (NumSampleRatioFrameTicks == 0)
{
SampleRateRatio = InSampleRateRatio;
TargetSampleRateRatio = SampleRateRatio;
SampleRatioDelta = 0.0f;
}
else
{
SampleRatioDelta = (InSampleRateRatio - SampleRateRatio) / NumSampleRatioFrameTicks;
TargetSampleRateRatio = InSampleRateRatio;
}
}
template<typename T>
int32 ProcessChunkImpl(const T* InBufferChunk, const int32 InNumSamples, const int32 RequestedFrames, TArray<float>& OutBuffer)
{
// Reset the output buffer. Can reuse the buffer and avoid reallocs.
OutBuffer.Reset();
// We don't have any data (or less than one frame of data) in the input buffer chunk
// Or we didn't initialize the sample rate converter
if (NumChannels == 0 || InNumSamples < NumChannels)
{
// TODO: Error?
return 0;
}
const int32 NumInputFrames = InNumSamples / NumChannels;
// TODO: error if requested frames is greater than input frames?
const int32 FramesToProcess = FMath::Min(NumInputFrames, RequestedFrames);
int32 NumFramesGenerated = 0;
// Process this buffer until we've processed all the frames we can
while (NextFrameIndex < NumInputFrames)
{
// Perform the linear interpolation to the next frame
for (int32 Channel = 0; Channel < NumChannels; ++Channel)
{
float CurrentSample = 0.0f;
// If we're using the frame from a previous chunk for the "current" frame, then we
// read from that chunk
if (bUsePreviousChunkFrame)
{
CurrentSample = PreviousChunkFrame[Channel];
}
else
{
const int32 CurrentSampleIndex = NumChannels * CurrentFrameIndex + Channel;
CurrentSample = GetFloatSampleValue(InBufferChunk[CurrentSampleIndex]);
}
const int32 NextSampleIndex = NumChannels * NextFrameIndex + Channel;
float NextSample = GetFloatSampleValue(InBufferChunk[NextSampleIndex]);
float OutSample = FMath::Lerp(CurrentSample, NextSample, FrameAlpha);
OutBuffer.Add(OutSample);
}
++NumFramesGenerated;
// Increment the frame alpha based on the current sample rate ratio
FrameAlpha += SampleRateRatio;
// Update the sample ratio to the new interpolated value
if (CurrentSampleRatioFrameTick < NumSampleRatioFrameTicks)
{
SampleRateRatio += SampleRatioDelta;
CurrentSampleRatioFrameTick++;
}
else
{
// Make sure we're exactly at the target sample rate ratio
SampleRateRatio = TargetSampleRateRatio;
}
// Now increment the current and next frame based on the new alpha value.
// If alpha is greater than 1.0 then we need to increment the frame counts based
int32 FrameAlphaRounded = (int32)FrameAlpha;
if (FrameAlphaRounded > 0)
{
// No longer using the previous chunk frame if this hits
bUsePreviousChunkFrame = false;
CurrentFrameIndex += FrameAlphaRounded;
NextFrameIndex = CurrentFrameIndex + 1;
// Subtract the frame alpha back to < 1.0 region
FrameAlpha -= (float)FrameAlphaRounded;
check(FrameAlpha < 1.0f && FrameAlpha >= 0.0f);
}
}
// If the current frame index is within this buffer, but the next frame index is
// in the next buffer, then we need to store the value of the current frame index
// so we can use it in the next chunk
if (CurrentFrameIndex < NumInputFrames && NextFrameIndex >= NumInputFrames)
{
bUsePreviousChunkFrame = true;
for (int32 Channel = 0; Channel < NumChannels; ++Channel)
{
const int32 CurrentSampleIndex = NumChannels * CurrentFrameIndex + Channel;
PreviousChunkFrame[Channel] = GetFloatSampleValue(InBufferChunk[CurrentFrameIndex]);
}
}
// Now wrap the current frame index if we've finished this buffer
// The current and next frame indicies will now be relative to the next input buffer
if (CurrentFrameIndex >= NumInputFrames)
{
CurrentFrameIndex -= NumInputFrames;
NextFrameIndex = CurrentFrameIndex + 1;
}
return NumFramesGenerated;
}
template <typename T>
int32 ProcessFullbufferImpl(const T* InBuffer, const int32 InNumSamples, TArray<float>& OutBuffer)
{
int32 NumFrames = InNumSamples / NumChannels;
return ProcessChunkImpl(InBuffer, InNumSamples, NumFrames, OutBuffer);
}
virtual int32 ProcessChunk(const int16* BufferChunk, const int32 NumSamples, const int32 RequestedFrames, TArray<float>& OutBuffer) override
{
return ProcessChunkImpl(BufferChunk, NumSamples, RequestedFrames, OutBuffer);
}
virtual int32 ProcessChunk(const float* BufferChunk, const int32 NumSamples, const int32 RequestedFrames, TArray<float>& OutBuffer) override
{
return ProcessChunkImpl(BufferChunk, NumSamples, RequestedFrames, OutBuffer);
}
virtual int32 ProcessFullbuffer(const int16* InBuffer, const int32 InNumSamples, TArray<float>& OutBuffer) override
{
return ProcessFullbufferImpl(InBuffer, InNumSamples, OutBuffer);
}
virtual int32 ProcessFullbuffer(const float* InBuffer, const int32 InNumSamples, TArray<float>& OutBuffer) override
{
return ProcessFullbufferImpl(InBuffer, InNumSamples, OutBuffer);
}
protected:
int CurrentFrameIndex;
int NextFrameIndex;
float FrameAlpha;
int32 NumChannels;
float SampleRateRatio;
float TargetSampleRateRatio;
float SampleRatioDelta;
int32 NumSampleRatioFrameTicks;
int32 CurrentSampleRatioFrameTick;
bool bUsePreviousChunkFrame;
TArray<float> PreviousChunkFrame;
};
ISampleRateConverter* ISampleRateConverter::CreateSampleRateConverter()
{
return new Audio::FSampleRateConverter();
}
}