Trying to play a Voice Data stream using a ByteArray in Unreal Engine 5

,

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!

1 Like