Wwise Audio Inputプラグイン
Unrealインテグレーションは、Wwise Audio Inputプラグイン経由で、Wwiseにオーディオ入力の方法を提供します。Audio Input Source Plug-in from the Wwise SDK documentationを参照してください。Wwiseにオーディオ入力を提供するには、クラスを AkAudioInputComponent
から継承します。
AkAudioInputComponent
AkAudioInputComponent
は、 AkComponent から派生しています。特別な AkComponent
で、Wwiseにオーディオ入力を提供するために使えます。2つの主要な関数を、実装する必要があります。
virtual bool FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill);
virtual void GetChannelConfig(AkAudioFormat& AudioFormat);
また、 Post Associated Audio Input Event というブループリント関数も1つあり、コンポーネントのAkAudioEventを、関連するAudioSamplesコールバックやAudioFormatコールバックと共にWwiseにポストするために、ゲームオブジェクトソースとしてこのコンポーネントを使います。
Custom Audio Inputの動作
カスタマイズしたオーディオ入力の動作を実装するために、AkAudioInputComponentから派生したカスタムクラスを書くことができます。以下の、 UAkVoiceInputComponent.h と UAkVoiceInputComponent.cpp の例は、マイクロフォンの入力をWwiseサウンドエンジンに送るクラスを表しています。
これらのファイルをC++ Unrealプロジェクトで使うには、初期設定が必要です。まずAkAudioとUnreal Voiceの両方のモジュールを互いにリンクさせる必要があるので、以下の例のように、プロジェクトのBuild.csファイルの、PublicDependencyModuleNamesに、これらを追加します。
public class MyModule : ModuleRules
{
public MyModule(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AkAudio", "Voice" });
}
}
次に、Voiceモジュールの bEnabled
フラグをTrueに設定する必要があるので、以下のラインをDefaultEngine.iniファイルに追加します:
この初期設定が終われば、マイクロフォン入力をWwiseに送信する、カスタマイズされたオーディオ入力の動作を示す以下のクラスを、追加することができます。
このコードは、簡単で短い例を提供するためにここで使われています。出荷するゲームで使うものではありません!
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();
}
}
このクラスをUnrealプロジェクトに追加すると、 AkVoiceInputComponent
のあるカスタムブループリントクラスを作成することができ、 Post Associated Audio Input Event ブループリント関数(ベースクラス AkAudioInputComponent
から)をコールして、Wwiseにマイクロフォンデータの送信を開始できます。以下のイメージ図は、 Actor
に基づくカスタムBlueprintクラスのBlueprintの一部で、 AkVoiceInput
と呼ばれる AkVoiceInputComponent
があります。