Files
UnrealEngine/Engine/Source/Runtime/Online/Voice/Private/Linux/VoiceModuleLinux.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

350 lines
9.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#if !VOICE_MODULE_WITH_CAPTURE
#include "Interfaces/VoiceCapture.h"
#include "Interfaces/VoiceCodec.h"
bool InitVoiceCapture()
{
return false;
}
void ShutdownVoiceCapture()
{
}
IVoiceCapture* CreateVoiceCaptureObject(const FString& DeviceName, int32 SampleRate, int32 NumChannels)
{
return nullptr;
}
#else // VOICE_MODULE_WITH_CAPTURE
#include "VoiceCodecOpus.h"
#include "Voice.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_audio.h>
class FVoiceCaptureSDL : public IVoiceCapture
{
public:
FVoiceCaptureSDL();
~FVoiceCaptureSDL();
static void AudioCallback(void* UserData, Uint8* Stream, int Length);
void ReadData(Uint8* Stream, int Length);
// IVoiceCapture
virtual bool Init(const FString& DeviceName, int32 SampleRate, int32 NumChannels) override;
virtual void Shutdown() override;
virtual bool Start() override;
virtual void Stop() override;
virtual bool ChangeDevice(const FString& DeviceName, int32 SampleRate, int32 NumChannels) override;
virtual bool IsCapturing() override;
virtual EVoiceCaptureState::Type GetCaptureState(uint32& OutAvailableVoiceData) const override;
virtual EVoiceCaptureState::Type GetVoiceData(uint8* OutVoiceBuffer, uint32 InVoiceBufferSize, uint32& OutAvailableVoiceData) override;
virtual int32 GetBufferSize() const override;
virtual void DumpState() const override;
private:
SDL_AudioStream* AudioStream;
EVoiceCaptureState::Type VoiceCaptureState;
/** Array to be used as a ring buffer */
TArray<Uint8> AudioBuffer;
int32 AudioBufferWritePos;
int32 AudioBufferAvailableData;
};
FVoiceCaptureSDL::FVoiceCaptureSDL()
: AudioStream(nullptr)
, VoiceCaptureState(EVoiceCaptureState::UnInitialized)
, AudioBufferWritePos(0)
, AudioBufferAvailableData(0)
{
}
FVoiceCaptureSDL::~FVoiceCaptureSDL()
{
Shutdown();
}
void FVoiceCaptureSDL::AudioCallback(void* UserData, Uint8* Stream, int Length)
{
// SDL locks/unlocks the audio device around this callback
static_cast<FVoiceCaptureSDL*>(UserData)->ReadData(Stream, Length);
}
void FVoiceCaptureSDL::ReadData(Uint8* Stream, int Length)
{
if (AudioBuffer.Num() - AudioBufferWritePos < Length)
{
const int32 FirstCopySize = AudioBuffer.Num() - AudioBufferWritePos;
const int32 SecondCopySize = Length - FirstCopySize;
FMemory::Memcpy(&AudioBuffer[AudioBufferWritePos], Stream, FirstCopySize);
FMemory::Memcpy(&AudioBuffer[0], Stream + FirstCopySize, SecondCopySize);
AudioBufferWritePos = SecondCopySize;
}
else
{
FMemory::Memcpy(&AudioBuffer[AudioBufferWritePos], Stream, Length);
AudioBufferWritePos += Length;
if (AudioBufferWritePos == AudioBuffer.Num())
{
AudioBufferWritePos = 0;
}
}
#if UE_BUILD_DEBUG
if (AudioBufferAvailableData + Length > AudioBuffer.Num())
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Discarding %i voice bytes\n"), AudioBufferAvailableData + Length - AudioBuffer.Num());
}
#endif
AudioBufferAvailableData = FMath::Min(AudioBufferAvailableData + Length, AudioBuffer.Num());
}
static void SDLCALL VoiceCallbackWrapper(void *UserData, SDL_AudioStream *AudioStream, int AdditionalAmount, int TotalAmount)
{
if (AdditionalAmount > 0)
{
Uint8 *Data = SDL_stack_alloc(Uint8, AdditionalAmount);
if(Data)
{
FVoiceCaptureSDL::AudioCallback(UserData, Data, AdditionalAmount);
SDL_PutAudioStreamData(AudioStream, Data, AdditionalAmount);
SDL_stack_free(Data);
}
}
}
bool FVoiceCaptureSDL::Init(const FString& DeviceName, int32 SampleRate, int32 NumChannels)
{
check(VoiceCaptureState == EVoiceCaptureState::UnInitialized);
if (SampleRate < 8000 || SampleRate > 48000)
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Voice capture doesn't support %d hz"), SampleRate);
return false;
}
if (NumChannels < 0 || NumChannels > 2)
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Voice capture only supports 1 or 2 channels"));
return false;
}
const SDL_AudioSpec DesiredAudioSpec = { SDL_AUDIO_S16LE, NumChannels, SampleRate };
AudioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &DesiredAudioSpec, &VoiceCallbackWrapper, this);
if (AudioStream == nullptr)
{
UE_LOG(LogVoiceCapture, Error, TEXT("Unable to open Audio Stream: %s"), ANSI_TO_TCHAR(SDL_GetError()));
return false;
}
int SampleSizeInBytes;
switch(DesiredAudioSpec.format)
{
case SDL_AUDIO_U8: // intentional fallthrough
case SDL_AUDIO_S8:
SampleSizeInBytes = 1;
break;
case SDL_AUDIO_S16LE: // intentional fallthrough
case SDL_AUDIO_S16BE:
SampleSizeInBytes = 2;
break;
case SDL_AUDIO_S32LE: // intentional fallthrough
case SDL_AUDIO_S32BE:
case SDL_AUDIO_F32LE:
case SDL_AUDIO_F32BE:
SampleSizeInBytes = 4;
break;
default: SampleSizeInBytes = -1;
break;
}
AudioBuffer.SetNum(DesiredAudioSpec.channels * DesiredAudioSpec.freq * SampleSizeInBytes);
return true;
}
void FVoiceCaptureSDL::Shutdown()
{
switch (VoiceCaptureState)
{
case EVoiceCaptureState::Ok:
case EVoiceCaptureState::NoData:
case EVoiceCaptureState::Stopping:
case EVoiceCaptureState::BufferTooSmall:
case EVoiceCaptureState::Error:
Stop();
// intentional fall-through
case EVoiceCaptureState::NotCapturing:
if (AudioStream != nullptr)
{
SDL_DestroyAudioStream(AudioStream);
AudioStream = nullptr;
}
VoiceCaptureState = EVoiceCaptureState::UnInitialized;
break;
case EVoiceCaptureState::UnInitialized:
break;
default:
check(false);
break;
}
}
bool FVoiceCaptureSDL::Start()
{
AudioBufferWritePos = 0;
AudioBufferAvailableData = 0;
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(AudioStream));
if (SDL_AudioDevicePaused(SDL_GetAudioStreamDevice(AudioStream)) == false)
{
VoiceCaptureState = EVoiceCaptureState::Ok;
return true;
}
else
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Failed to start capture"));
return false;
}
}
void FVoiceCaptureSDL::Stop()
{
check(IsCapturing());
SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(AudioStream));
VoiceCaptureState = EVoiceCaptureState::NotCapturing;
AudioBufferWritePos = 0;
AudioBufferAvailableData = 0;
}
bool FVoiceCaptureSDL::ChangeDevice(const FString& DeviceName, int32 SampleRate, int32 NumChannels)
{
/** stubbed */
return false;
}
bool FVoiceCaptureSDL::IsCapturing()
{
return AudioStream != nullptr && VoiceCaptureState > EVoiceCaptureState::NotCapturing;
}
EVoiceCaptureState::Type FVoiceCaptureSDL::GetCaptureState(uint32& OutAvailableVoiceData) const
{
if (VoiceCaptureState == EVoiceCaptureState::Ok)
{
// may be better to use atomics to avoid locking here
SDL_LockAudioStream(AudioStream);
OutAvailableVoiceData = AudioBufferAvailableData;
SDL_UnlockAudioStream(AudioStream);
}
else
{
OutAvailableVoiceData = 0;
}
return VoiceCaptureState;
}
EVoiceCaptureState::Type FVoiceCaptureSDL::GetVoiceData(uint8* OutVoiceBuffer, uint32 InVoiceBufferSize, uint32& OutAvailableVoiceData)
{
EVoiceCaptureState::Type ReturnState = VoiceCaptureState;
OutAvailableVoiceData = 0;
if (VoiceCaptureState == EVoiceCaptureState::Ok)
{
SDL_LockAudioStream(AudioStream);
uint32 AvailableVoiceData = AudioBufferAvailableData;
OutAvailableVoiceData = FMath::Min(AvailableVoiceData, InVoiceBufferSize);
if (OutAvailableVoiceData > 0)
{
if (AudioBufferAvailableData > AudioBufferWritePos)
{
const int32 FirstCopyAvailableData = AudioBufferAvailableData - AudioBufferWritePos;
const int32 FirstCopyIndex = AudioBuffer.Num() - FirstCopyAvailableData;
if (FirstCopyAvailableData >= OutAvailableVoiceData)
{
FMemory::Memcpy(OutVoiceBuffer, &AudioBuffer[FirstCopyIndex], OutAvailableVoiceData);
}
else
{
const int32 SecondCopySize = OutAvailableVoiceData - FirstCopyAvailableData;
FMemory::Memcpy(OutVoiceBuffer, &AudioBuffer[FirstCopyIndex], FirstCopyAvailableData);
FMemory::Memcpy(OutVoiceBuffer + FirstCopyAvailableData, &AudioBuffer[0], SecondCopySize);
}
}
else
{
const int32 ReadIndex = AudioBufferWritePos - AudioBufferAvailableData;
FMemory::Memcpy(OutVoiceBuffer, &AudioBuffer[ReadIndex], OutAvailableVoiceData);
}
AudioBufferAvailableData -= OutAvailableVoiceData;
}
SDL_UnlockAudioStream(AudioStream);
}
return ReturnState;
}
int32 FVoiceCaptureSDL::GetBufferSize() const
{
return AudioBuffer.Num();
}
void FVoiceCaptureSDL::DumpState() const
{
/** stubbed */
UE_LOG(LogVoiceCapture, Display, TEXT("FVoiceCaptureSDL::DumpState() is not implemented"));
}
bool GVoiceCaptureNeedsToShutdownSDLAudio = false;
bool InitVoiceCapture()
{
// AudioMixerSDL may interfere with this
if (!SDL_WasInit(SDL_INIT_AUDIO))
{
GVoiceCaptureNeedsToShutdownSDLAudio = SDL_InitSubSystem(SDL_INIT_AUDIO) == 0;
return GVoiceCaptureNeedsToShutdownSDLAudio;
}
GVoiceCaptureNeedsToShutdownSDLAudio = false;
return true;
}
void ShutdownVoiceCapture()
{
// assume reverse order of shutdown
if (GVoiceCaptureNeedsToShutdownSDLAudio)
{
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
}
IVoiceCapture* CreateVoiceCaptureObject(const FString& DeviceName, int32 SampleRate, int32 NumChannels)
{
IVoiceCapture* Capture = new FVoiceCaptureSDL;
if (!Capture->Init(DeviceName, SampleRate, NumChannels))
{
delete Capture;
Capture = nullptr;
}
return Capture;
}
#endif // VOICE_MODULE_WITH_CAPTURE