众所周知,要让某个Actor发出声音,只需要给它添加一个AudioComponent,然后实现相应方法。
但如果反过来,要获取UE关卡中已有的声音,然后将其写成文件或者进行网络广播,该怎么做?
UE给用户提供了 订阅者模式 的框架,大致思路如下:
1)写一个类继承自 ISubmixBufferListener(AudioDevice.h)
2)注册/反注册 Listener
3)实现其唯一的接口 OnNewSubmixBuffer
一、写一个类继承自 ISubmixBufferListener
只需要包含头文件 “AudioDevice.h”
其路径位于 “Runtime/Engine/Public/AudioDevice.h”
class PXSubmixCapturer : public ISubmixBufferListener
{
public:
PXSubmixCapturer() {};
virtual ~PXSubmixCapturer() = default;
public:
bool Initialize();
bool Uninitialize();
// ~ ISubmixBufferListener Interface
void OnNewSubmixBuffer(const USoundSubmix* OwningSubmix, float* AudioData, int32 NumSamples, int32 NumChannels, const int32 SampleRate, double AudioClock) override;
private:
TArray<int16_t> RecordingBuffer;
FCriticalSection CriticalSection;
};
二、注册/反注册 Listener
- 注册Listener
在Initialize函数中将自身注册到AudioDevice的Listener中,具体实现如下:
bool PXSubmixCapturer::Initialize()
{
FScopeLock Lock(&CriticalSection);
if (bInitialized)
{
return true;
}
if (GEngine == nullptr)
{
return false;
}
FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice();
if (!AudioDevice)
{
bInitialized = false;
return false;
}
AudioDevice->RegisterSubmixBufferListener(this);
// 这里修改Engine配置参数,避免Game失去焦点时拿到的音频Buffer是空的
if (GConfig)
{
GConfig->SetFloat(TEXT("Audio"), TEXT("UnfocusedVolumeMultiplier"), 1.0f, GEngineIni);
}
bInitialized = true;
return true;
}
- 注销
在Uninitialize函数中注销注册,调用AudioDevice的相应接口:
bool PXSubmixCapturer::Uninitialize()
{
FScopeLock Lock(&CriticalSection);
if (!bInitialized)
{
return true;
}
if (GEngine != nullptr)
{
FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice();
if (AudioDevice)
{
AudioDevice->UnregisterSubmixBufferListener(this);
}
}
RecordingBuffer.Empty();
bInitialized = false;
return true;
}
三、实现接口
最关键的一步,在你的类中重写 ISubmixBufferListener 的纯虚函数,最后UE运行真正执行回调时,会在这个函数中把音频Buffer及其相关信息吐给你。参考实现如下:
void PXSubmixCapturer::OnNewSubmixBuffer(const USoundSubmix* OwningSubmix, float* AudioData, int32 NumSamples, int32 NumChannels, const int32 SampleRate, double AudioClock)
{
FScopeLock Lock(&CriticalSection);
if (!bInitialized)
{
return;
}
// TSampleBuffer takes in AudioData as float* and internally converts to int16
Audio::TSampleBuffer<int16> Buffer(AudioData, NumSamples, NumChannels, SampleRate);
RecordingBuffer.Append(Buffer.GetData(), Buffer.GetNumSamples());
const int32 ChunkSamples = GetSamplesPerDurationSecs();
while (RecordingBuffer.Num() > ChunkSamples)
{
TArray<int16_t> SubmitBuffer(RecordingBuffer.GetData(), ChunkSamples);
// Do your things with this audio buffer here.
// ...
// Remove samples from the recording buffer because it is sent.
RecordingBuffer.RemoveAt(0, ChunkSamples, false);
}
}
四、应用
在客户代码中维护一个你刚刚写好的 SubmixCapturer
在合适的地方调用 Initialize / Uninitialize
然后坐享其成, UE的音频框架会自动调用到你重写的 OnNewSubmixBuffer (*仅限-game模式)