Wwise Audio Input 플러그인
Unreal 통합은 Wwise Audio Input 플러그인을 통해 Wwise에 오디오 입력을 제공하는 방법을 제공합니다. Wwise SDK 설명서에서 오디오 입력 음원 플러그인을 참고하세요. Wwise에 오디오 입력을 제공하려면 클래스가 AkAudioInputComponent
에서 상속해야 합니다.
AkAudioInputComponent
AkAudioInputComponent
는 AkComponent 로부터 파생됩니다. 이 컴포넌트는 Wwise에 오디오 입력을 제공하는 데에 사용되는 특수한 AkComponent
입니다. 다음은 구현되어야 하는 두 가지 주요 함수입니다. Wwise
virtual bool FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill);
virtual void GetChannelConfig(AkAudioFormat& AudioFormat);
이 컴포넌트에는 Post Associated Audio Input Event라는 Blueprint 함수도 있습니다. 이 함수는 컴포넌트를 게임 오브젝트의 음원으로 사용하여 해당 컴포넌트의 AkAudioEvent를 연관된 AudioSamples 콜백과 AudioFormat 콜백과 함께 Wwise에 게시합니다.
커스텀 오디오 입력 작동 방식
AkAudioInputComponent에서 파생되는 커스텀 클래스를 제작하여 커스텀 오디오 입력 작동 방식을 구현할 수 있습니다. 아래의 UAkVoiceInputComponent.h와 UAkVoiceInputComponent.cpp 예시에서는 마이크 입력을 가져와서 Wwise 사운드 엔진에 보내주는 함수를 보여줍니다.
이러한 파일을 C++ Unreal 프로젝트에서 사용하기 위해서는 몇 가지 초기 설정 작업이 필요합니다. 먼저 아래 예시와 같이 AkAudio와 Unreal Voice 모듈을 프로젝트의 Build.cs 안에 있는 PublicDependencyModuleNames에 모두 추가하여 연결해야 합니다. Wwise
public class MyModule : ModuleRules
{
public MyModule(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AkAudio", "Voice" });
}
}
그리고 DefaultEngine.ini 파일에 다음 줄을 추가하여 Voice 모듈에 있는 bEnabled
플래그를 반드시 true로 설정해야 합니다.
Wwise
초기 설정 작업이 끝난 후에 마이크 입력을 Wwise에 전송하는 맞춤 오디오 입력 작동 방식을 보여주는 다음 클래스를 추가할 수 있습니다.
이 코드는 빠르고 간단한 예시를 위해 여기에서만 사용됩니다. 출시 게임에서 사용하도록 제작되지 않았습니다!
AkVoiceInputComponent.h:
Wwise
#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:
Wwise
#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();
}
}
이 클래스를 Unreal 프로젝트에 추가하면 AkVoiceInputComponent
가 있는 맞춤 Blueprint 클래스를 만들 수 있으며 Post Associated Audio Input Event Blueprint 함수 (기반 클래스 AkAudioInputComponent
로부터)를 호출하여 마이크 데이터를 Wwise에 전송하기 시작할 수 있습니다. 아래 이미지는 커스텀 Blueprint 클래스를 위한 Blueprint 클래스의 일부입니다. 이 클래스는 Actor를
기반으로 하며 AkVoiceInput
이라는 AkVoiceInputComponent
를 가지고 있습니다.