Thanks for taking a look at it!
I’ve combed over the example and tried to implement it. But I still get more of the same crackling/popping and now I also get looped weirdness with the end of the audio repeating. If you wouldn’t mind taking another look at my latest attempt that would be amazing?
#include "FmodVoice.h"
FMOD_RESULT F_CALLBACK PCMReadCallback(FMOD_SOUND* sound, void *data, unsigned int datalen);
const int LATENCY_MS = 50;
const int DRIFT_MS = 1;
const float DRIFT_CORRECTION_PERCENTAGE = 0.5f;
FMOD::Channel* Channel = nullptr;
static inline TArray<uint8> Buffer;
FMOD::Sound* Sound = nullptr;
int SampleRate = 16000;
uint32 DriftThreshold;
uint32 TargetLatency;
uint32 AdjustedLatency;
uint32 ActualLatency;
uint32 TotalSamplesWritten;
uint32 MinimumSamplesWritten = UINT32_MAX;
uint32 LastReadPosition;
uint32 TotalSamplesRead;
const FMOD::Studio::System* System;
FMOD::System* CoreSystem = nullptr;
FMOD_CREATESOUNDEXINFO soundExInfo = { 0 };
UVoipManagerComponent* VOIP;
UFmodVoice::UFmodVoice()
{
PrimaryComponentTick.bCanEverTick = true;
}
void UFmodVoice::BeginPlay()
{
Super::BeginPlay();
System = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime);
System->getCoreSystem(&CoreSystem);
DriftThreshold = static_cast<uint32>(SampleRate * DRIFT_MS) / 1000;
TargetLatency = static_cast<uint32>(SampleRate * LATENCY_MS) / 1000;
AdjustedLatency = TargetLatency;
ActualLatency = static_cast<int>(TargetLatency);
soundExInfo.cbsize = sizeof(soundExInfo);
soundExInfo.format = FMOD_SOUND_FORMAT_PCM16;
soundExInfo.defaultfrequency = SampleRate;
soundExInfo.numchannels = 1;
soundExInfo.length = TargetLatency * sizeof(uint8);
soundExInfo.pcmreadcallback = PCMReadCallback;
}
void UFmodVoice::BeginDestroy()
{
Super::BeginDestroy();
Channel->stop();
Sound->release();
}
void UFmodVoice::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (VOIP)
{
Buffer = VOIP->DecompressedVoiceBuffer;
if (!Channel && TotalSamplesWritten > AdjustedLatency)
{
const FMOD_RESULT result = CoreSystem->createSound("", FMOD_OPENUSER | FMOD_LOOP_NORMAL , &soundExInfo, &Sound);
CoreSystem->playSound(Sound, nullptr, false, &Channel);
}
if (Buffer.Num() > 0 && Channel)
{
uint32 readPosition;
Channel->getPosition(&readPosition, FMOD_TIMEUNIT_PCMBYTES);
UE_LOG(LogTemp, Warning, TEXT("Channel read position: %d"), readPosition);
/*
* Account for wrapping
*/
int bytesRead = readPosition - LastReadPosition;
if (readPosition < LastReadPosition)
{
bytesRead += soundExInfo.length;
}
if (bytesRead > 0 && Buffer.Num() >= bytesRead)
{
/*
* Fill previously read data with fresh samples
*/
void* ptr1;
void* ptr2;
uint32 len1, len2;
FMOD_RESULT result = Sound->lock(LastReadPosition, bytesRead, &ptr1, &ptr2, &len1, &len2);
if (result != FMOD_OK) UE_LOG(LogTemp, Warning, TEXT("VOIP Manager Size: %d"), result);
int sampleLen1 = static_cast<int>(len1 / sizeof(uint8));
int sampleLen2 = static_cast<int>(len2 / sizeof(uint8));
int samplesRead = sampleLen1 + sampleLen2;
TArray<uint8> tmpBuffer;
tmpBuffer.Init(0, samplesRead);
tmpBuffer.SetNum(Buffer.Num(), false);
// Directly copy the data from Buffer to tmpBuffer using FMemory::Memcpy
if (Buffer.Num() > 0 && samplesRead <= Buffer.Num())
{
FMemory::Memcpy(tmpBuffer.GetData(), Buffer.GetData(), samplesRead);
}
// Remove the copied range from mBuffer
Buffer.RemoveAt(0, tmpBuffer.Num());
if (len1 > 0)
{
// Directly copy from the start of tmpBuffer
FMemory::Memcpy(ptr1, tmpBuffer.GetData(), sampleLen1);
}
if (len2 > 0)
{
// Calculate the start index for the second copy by offsetting the source pointer
// Note: This assumes sampleLen1 is the byte offset to start from for the second copy
uint8* sourcePtrOffset = tmpBuffer.GetData() + sampleLen1;
FMemory::Memcpy(ptr2, sourcePtrOffset, sampleLen2);
}
result = Sound->unlock(ptr1, ptr2, len1, len2);
if (result != FMOD_OK) UE_LOG(LogTemp, Warning, TEXT("VOIP Manager Size: %d"), result);
LastReadPosition = readPosition;
TotalSamplesRead += static_cast<uint32>(samplesRead);
}
}
//Drift compensation
uint32 samplesWritten = Buffer.Num();
TotalSamplesWritten += samplesWritten;
if (samplesWritten != 0 && samplesWritten < MinimumSamplesWritten)
{
MinimumSamplesWritten = samplesWritten;
AdjustedLatency = FMath::Max(samplesWritten, TargetLatency);
}
int32 latency = TotalSamplesWritten - TotalSamplesRead;
ActualLatency = static_cast<uint32>((0.93f * ActualLatency) + (0.03f * latency));
int32 PlaybackRate = SampleRate;
if (ActualLatency < (AdjustedLatency - DriftThreshold))
{
PlaybackRate = SampleRate - static_cast<int32>(SampleRate * (DRIFT_CORRECTION_PERCENTAGE / 100.0f));
}
else if (ActualLatency > (AdjustedLatency + DriftThreshold))
{
PlaybackRate = SampleRate + static_cast<int32>(SampleRate * (DRIFT_CORRECTION_PERCENTAGE / 100.0f));
}
Channel->setFrequency(PlaybackRate);
}
}
void UFmodVoice::InitComponent(UFMODAudioComponent* fmodAudioComponent, UVoipManagerComponent* VoipManager)
{
VOIP = VoipManager;
}
FMOD_RESULT F_CALLBACK PCMReadCallback(FMOD_SOUND* sound, void *data, unsigned int datalen) {
//FMemory::Memcpy(data, &UFmodVoice::UncompressedVoiceData[0], datalen);
return FMOD_OK;
}
Thanks again!