The Wwise Audio Input Plug-in
The Unreal integration provides a way of providing audio input to Wwise, via the Wwise Audio Input plug-in. See Audio Input Source Plug-in from the Wwise SDK documentation. In order to provide audio input to Wwise, classes should inherit from AkAudioInputComponent
.
AkAudioInputComponent
AkAudioInputComponent
is derived from AkComponent. It is a specialized AkComponent
that can be used to provide audio input to Wwise. There are two key functions that need to be implemented:
virtual bool FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill);
virtual void GetChannelConfig(AkAudioFormat& AudioFormat);
It also has one Blueprint function, Post Associated Audio Input Event, which posts the component's AkAudioEvent to Wwise along with associated AudioSamples callback and AudioFormat callback, using this component as the game object source.
Custom Audio Input behavior
Custom classes can be written that derive from AkAudioInputComponent in order to implement custom audio input behavior. The UAkVoiceInputComponent.h and UAkVoiceInputComponent.cpp examples below show a class that takes microphone input and pipes it to the Wwise sound engine.
In order to use these files in a C++ Unreal project, there is some initial setup to do. Firstly, both the AkAudio and the Unreal Voice modules must be linked, by adding them to the PublicDependencyModuleNames in the project's Build.cs file, for example:
public class MyModule : ModuleRules
{
public MyModule(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AkAudio", "Voice" });
}
}
Next, the bEnabled
flag in the Voice module must be set to true, by adding the following lines to the DefaultEngine.ini file:
With this initial setup complete, the following class, which demonstrates custom audio input behavior by taking microphone input and sending it to Wwise, can be added.
Note that this code is only used here to provide a quick simple example. It is NOT intended for use in shipped games!
AkVoiceInputComponent.h:
#pragma once
#include "CoreMinimal.h"
#include "AkAudioInputComponent.h"
#include "Voice.h"
#include "AkVoiceInputComponent.generated.h"
UCLASS(ClassGroup = Audiokinetic, BlueprintType, hidecategories = (Transform, Rendering, Mobility, LOD, Component, Activation), meta = (BlueprintSpawnableComponent))
class WWISEDEMOGAME_API UAkVoiceInputComponent : public UAkAudioInputComponent
{
GENERATED_BODY()
UAkVoiceInputComponent(const class FObjectInitializer& ObjectInitializer);
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
protected:
virtual void PostUnregisterGameObject() override;
virtual bool FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill) override;
virtual void GetChannelConfig(AkAudioFormat& AudioFormat) override;
TSharedPtr<IVoiceCapture> VoiceCapture;
TArray<uint8> IncomingRawVoiceData;
TArray<uint8> CollectedRawVoiceData;
FThreadSafeBool bIsReadingVoiceData = false;
};
AkVoiceInputComponent.cpp:
#include "AkVoiceInputComponent.h"
UAkVoiceInputComponent::UAkVoiceInputComponent(const class FObjectInitializer& ObjectInitializer) :
UAkAudioInputComponent(ObjectInitializer)
{
CollectedRawVoiceData.Reset();
VoiceCapture = FVoiceModule::Get().CreateVoiceCapture();
}
void UAkVoiceInputComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!VoiceCapture.IsValid())
{
return;
}
uint32 NumAvailableVoiceCaptureBytes = 0;
EVoiceCaptureState::Type CaptureState = VoiceCapture->GetCaptureState(NumAvailableVoiceCaptureBytes);
if (CaptureState == EVoiceCaptureState::Ok && NumAvailableVoiceCaptureBytes > 0)
{
uint32 NumVoiceCaptureBytesReturned = 0;
IncomingRawVoiceData.Reset((int32)NumAvailableVoiceCaptureBytes);
IncomingRawVoiceData.AddDefaulted(NumAvailableVoiceCaptureBytes);
uint64 SampleCounter = 0;
VoiceCapture->GetVoiceData(IncomingRawVoiceData.GetData(), NumAvailableVoiceCaptureBytes, NumVoiceCaptureBytesReturned, SampleCounter);
if (NumVoiceCaptureBytesReturned > 0)
{
while (bIsReadingVoiceData) {}
CollectedRawVoiceData.Append(IncomingRawVoiceData);
}
}
}
bool UAkVoiceInputComponent::FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill)
{
if (!VoiceCapture.IsValid())
{
return false;
}
const uint8 NumBytesPerSample = 2;
const uint32 NumRequiredBytesPerChannel = NumSamples * NumBytesPerSample;
const uint32 NumRequiredBytes = NumRequiredBytesPerChannel * NumChannels;
int16 VoiceSample = 0;
uint32 RawChannelIndex = 0;
uint32 RawSampleIndex = 0;
bIsReadingVoiceData = true;
const int32 NumSamplesAvailable = CollectedRawVoiceData.Num() / NumBytesPerSample;
const uint32 BufferSlack = (uint32)FMath::Max(0, (int32)(NumSamples * NumChannels) - NumSamplesAvailable);
for (uint32 c = 0; c < NumChannels; ++c)
{
RawChannelIndex = c * NumRequiredBytesPerChannel;
for (uint32 s = 0; s < NumSamples; ++s)
{
if (s >= (NumSamples - BufferSlack) / NumChannels)
{
BufferToFill[c][s] = 0.0f;
}
else
{
uint32 RawSampleDataMSBIndex = s * 2 + 1;
uint32 RawSampleDataLSBIndex = s * 2;
VoiceSample = (CollectedRawVoiceData[RawSampleDataMSBIndex] << 8) | CollectedRawVoiceData[RawSampleDataLSBIndex];
BufferToFill[c][s] = VoiceSample / (float)INT16_MAX;
}
}
}
const int32 NumBytesRead = (NumSamples - BufferSlack) * NumBytesPerSample;
CollectedRawVoiceData.RemoveAt(0, NumBytesRead);
bIsReadingVoiceData = false;
return true;
}
void UAkVoiceInputComponent::GetChannelConfig(AkAudioFormat& AudioFormat)
{
const int sampleRate = 16000;
AudioFormat.uSampleRate = sampleRate;
AudioFormat.channelConfig.SetStandard(AK_SPEAKER_SETUP_MONO);
if (VoiceCapture.IsValid())
{
if (!VoiceCapture->Init(FString(""), AudioFormat.uSampleRate, AudioFormat.channelConfig.uNumChannels))
{
UE_LOG(LogTemp, Error, TEXT("Failed to initialize device for voice input!"));
return;
}
VoiceCapture->Start();
}
}
void UAkVoiceInputComponent::PostUnregisterGameObject()
{
Super::PostUnregisterGameObject();
if (VoiceCapture.IsValid())
{
VoiceCapture->Stop();
VoiceCapture->Shutdown();
}
}
With this class added to an Unreal project, a custom Blueprint class can be created which has an AkVoiceInputComponent
, and the Post Associated Audio Input Event Blueprint function (from base class AkAudioInputComponent
) can be called in order to start sending microphone data to Wwise. The image below shows part of the Blueprint for a custom Blueprint class, based on Actor
, that has an AkVoiceInputComponent
called AkVoiceInput
.