Skip to content

Commit

Permalink
update TFAudioCapture
Browse files Browse the repository at this point in the history
- adds support for native c++ delegates
- should capture the whole audio better when it finishes
- returns average volume (as a fraction of int16) with each data chunk
  • Loading branch information
getnamo committed Jul 24, 2018
1 parent 51b36be commit 7745768
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 41 deletions.
8 changes: 4 additions & 4 deletions Source/TFAudioCapture/Private/AudioCaptureData.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include "AudioCapturePrivatePCH.h"
#include "AudioCaptureData.h"

FAudioCaptureOptions::FAudioCaptureOptions()
{
SampleRate = 22050;
//this is deep-speech default
SampleRate = 16000;
BitsPerSample = 16;
Channels = 1;
}
Expand All @@ -17,8 +17,8 @@ FWavHeader::FWavHeader()
memcpy((char*)Subchunk2ID, "data", 4);
Subchunk1Size = 16;

//Default to 44khz 16 bit PCM Mono
SetHeaderSampleData(44100, 16);
//Default to 16khz 16 bit PCM Mono
SetHeaderSampleData(16000, 16);
AudioFormat = 1; //PCM
}

Expand Down
4 changes: 0 additions & 4 deletions Source/TFAudioCapture/Private/AudioCapturePrivatePCH.h

This file was deleted.

45 changes: 28 additions & 17 deletions Source/TFAudioCapture/Private/FTFAudioCapture.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include "AudioCapturePrivatePCH.h"
#include "ITFAudioCapture.h"
#include "TFAudioCaptureComponent.h"
#include "LambdaRunnable.h"
Expand All @@ -8,20 +7,22 @@ class FTFAudioCapture : public ITFAudioCapture
{
public:

virtual void StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData = nullptr, TFunction<void(const TArray<uint8>&)> OnCaptureFinished = nullptr) override;
virtual void StartCapture(TFunction<void(const TArray<uint8>&, float)> OnAudioData = nullptr, TFunction<void(const TArray<uint8>&, float)> OnCaptureFinished = nullptr) override;
virtual void StopCapture() override;
virtual void SetOptions(const FAudioCaptureOptions& Options) override;

virtual void AddAudioComponent(const UTFAudioCaptureComponent* Component) override;
virtual void RemoveAudioComponent(const UTFAudioCaptureComponent* Component) override;
virtual void AddAudioDelegate(IAudioDataInterface* Delegate) override;
virtual void RemoveAudioDelegate(IAudioDataInterface* Delegate) override;

/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;

private:
TSharedPtr<FWindowsAudioCapture> WindowsCapture;
TArray<UTFAudioCaptureComponent*> Components;
TArray<IAudioDataInterface*> Delegates;
};

void FTFAudioCapture::StartupModule()
Expand All @@ -37,42 +38,41 @@ void FTFAudioCapture::ShutdownModule()

}

void FTFAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData, TFunction<void(const TArray<uint8>&)> OnCaptureFinished)
void FTFAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&, float)> OnAudioData, TFunction<void(const TArray<uint8>&, float)> OnCaptureFinished)
{
TFunction<void(const TArray<uint8>&)> OnDataDelegate = [this, OnAudioData] (const TArray<uint8>& AudioData)
TFunction<void(const TArray<uint8>&, float)> OnDataDelegate = [this, OnAudioData] (const TArray<uint8>& AudioData, float AudioMaxLevel)
{
//Call each added component function inside game thread
FLambdaRunnable::RunShortLambdaOnGameThread([this, AudioData, OnAudioData]
FLambdaRunnable::RunShortLambdaOnGameThread([this, AudioData, OnAudioData, AudioMaxLevel]
{
for (auto Component : Components)
for (auto Delegate : Delegates)
{
Component->OnAudioData.Broadcast(AudioData);
Delegate->OnAudioDataEvent(AudioData, AudioMaxLevel);
}

//Also if valid pass it to the new delegate
if (OnAudioData != nullptr)
{
OnAudioData(AudioData);
OnAudioData(AudioData, AudioMaxLevel);
}
});
};

TFunction<void(const TArray<uint8>&)> OnFinishedDelegate = [this, OnCaptureFinished](const TArray<uint8>& AudioData)
TFunction<void(const TArray<uint8>&, float)> OnFinishedDelegate = [this, OnCaptureFinished](const TArray<uint8>& AudioData, float AudioMaxLevel)
{
//Call each added component function inside game thread
FLambdaRunnable::RunShortLambdaOnGameThread([this, AudioData, OnCaptureFinished]
FLambdaRunnable::RunShortLambdaOnGameThread([this, AudioData, OnCaptureFinished, AudioMaxLevel]
{
for (auto Component : Components)
for (auto Delegate : Delegates)
{
Component->OnCaptureFinished.Broadcast(AudioData);
Delegate->OnCaptureFinishedEvent(AudioData, AudioMaxLevel);
}

//Also if valid pass it to the new delegate
if (OnCaptureFinished != nullptr)
{
OnCaptureFinished(AudioData);
OnCaptureFinished(AudioData, AudioMaxLevel);
}

});
};

Expand All @@ -94,14 +94,25 @@ void FTFAudioCapture::SetOptions(const FAudioCaptureOptions& Options)

void FTFAudioCapture::AddAudioComponent(const UTFAudioCaptureComponent* Component)
{
Components.Add((UTFAudioCaptureComponent*)Component);
AddAudioDelegate((IAudioDataInterface*)Component);
}

void FTFAudioCapture::RemoveAudioComponent(const UTFAudioCaptureComponent* Component)
{
Components.Remove((UTFAudioCaptureComponent*)Component);
RemoveAudioDelegate((IAudioDataInterface*)Component);
}

void FTFAudioCapture::AddAudioDelegate(IAudioDataInterface* Delegate)
{
Delegates.Add(Delegate);
}

void FTFAudioCapture::RemoveAudioDelegate(IAudioDataInterface* Delegate)
{
Delegates.Remove(Delegate);
}


IMPLEMENT_MODULE(FTFAudioCapture, TFAudioCapture)


65 changes: 56 additions & 9 deletions Source/TFAudioCapture/Private/FWindowsAudioCapture.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "AudioCapturePrivatePCH.h"
#include "LambdaRunnable.h"
#include "FWindowsAudioCapture.h"
#include "LambdaRunnable.h"
#include "CoreMinimal.h"

#include "AllowWindowsPlatformTypes.h"
#include <iostream>
Expand All @@ -15,7 +15,7 @@ FWindowsAudioCapture::FWindowsAudioCapture()
bRunLoopActive = false;
}

void FWindowsAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData /*= nullptr*/, TFunction<void(const TArray<uint8>&)> OnCaptureFinished /*= nullptr*/)
void FWindowsAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&, float)> OnAudioData /*= nullptr*/, TFunction<void(const TArray<uint8>&, float)> OnCaptureFinished /*= nullptr*/)
{
//Only attempt to start capture once. If it's active return.
if (bRunLoopActive)
Expand Down Expand Up @@ -58,21 +58,24 @@ void FWindowsAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&)> On

// Insert a wave input buffer
result = waveInAddBuffer(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));

result = waveInStart(hWaveIn);

float MaxLevel = 0.f;

//The headers will now get filled and we should check them periodically for new data
while (*bShouldRunPtr)
{
if (hWaveInHdr.dwFlags & WHDR_DONE)
{
TArray<uint8> OutData;
OutData.SetNum(AudioBuffer.Num());
FMemory::Memcpy(OutData.GetData(), AudioBuffer.GetData(), AudioBuffer.Num());
OutData.SetNum(hWaveInHdr.dwBytesRecorded);
FMemory::Memcpy(OutData.GetData(), AudioBuffer.GetData(), hWaveInHdr.dwBytesRecorded);

MaxLevel = CalculateMaxAudioLevel(OutData, pFormat.wBitsPerSample);

if (OnAudioData != nullptr)
{
OnAudioData(OutData);
OnAudioData(OutData, MaxLevel);
}

//Clear flags
Expand All @@ -85,14 +88,34 @@ void FWindowsAudioCapture::StartCapture(TFunction<void(const TArray<uint8>&)> On
}
}

//Stop
waveInStop(hWaveIn);

//Clear flags
hWaveInHdr.dwFlags = 0;
hWaveInHdr.dwBytesRecorded = 0;

//Flush last data
waveInPrepareHeader(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));

TArray<uint8> OutData;
OutData.SetNum(hWaveInHdr.dwBytesRecorded);
FMemory::Memcpy(OutData.GetData(), AudioBuffer.GetData(), hWaveInHdr.dwBytesRecorded);
MaxLevel = CalculateMaxAudioLevel(OutData, pFormat.wBitsPerSample);

waveInUnprepareHeader(hWaveIn, &hWaveInHdr, sizeof(WAVEHDR));
waveInClose(hWaveIn);

if (OnAudioData != nullptr)
{
OnAudioData(OutData, MaxLevel);
}

//flush whatever is left of the buffer, emit both in data and on finished
if (OnCaptureFinished != nullptr)
{
//flush whatever is left of the buffer
OnCaptureFinished(AudioBuffer);
OnCaptureFinished(OutData, MaxLevel);
}
});
}
Expand All @@ -107,4 +130,28 @@ void FWindowsAudioCapture::SetOptions(const FAudioCaptureOptions& InOptions)
Options = InOptions;
}

float FWindowsAudioCapture::CalculateMaxAudioLevel(TArray<uint8>& Buffer, int32 BitsPerSample)
{
//Can't handle non-16 sample
if(BitsPerSample != 16)
{
return 0.5f;
}

int32 Num = (Buffer.Num() / BitsPerSample) * 8;
int16* Buffer16 = (int16*)Buffer.GetData();
int16 MaxValue = 0;
for (int i = 0; i < Num; i++)
{
int16 Value = FMath::Abs(Buffer16[i]);
if (Value > MaxValue)
{
MaxValue = Value;
}
}
//UE_LOG(LogTemp, Log, TEXT("max value: %d"), MaxValue);

return (float)MaxValue / (float)INT16_MAX;
}

#include "HideWindowsPlatformTypes.h"
4 changes: 3 additions & 1 deletion Source/TFAudioCapture/Private/FWindowsAudioCapture.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ class FWindowsAudioCapture : public IAudioCaptureInterface
public:
FWindowsAudioCapture();

virtual void StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData = nullptr, TFunction<void(const TArray<uint8>&)> OnCaptureFinished = nullptr) override;
virtual void StartCapture(TFunction<void(const TArray<uint8>&, float)> OnAudioData = nullptr, TFunction<void(const TArray<uint8>&, float)> OnCaptureFinished = nullptr) override;
virtual void StopCapture() override;
virtual void SetOptions(const FAudioCaptureOptions& InOptions) override;

FAudioCaptureOptions Options;
private:

float CalculateMaxAudioLevel(TArray<uint8>& Buffer, int32 BitsPerSample);
FThreadSafeBool bRunLoopActive;
};
2 changes: 1 addition & 1 deletion Source/TFAudioCapture/Private/IAudioCaptureInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class IAudioCaptureInterface
/** Required API */

/** Start capturing audio, whenever there is audio data, call passed-in lambda */
virtual void StartCapture(TFunction<void(const TArray<uint8>&)> OnAudioData = nullptr, TFunction<void(const TArray<uint8>&)> OnCaptureFinished = nullptr) {};
virtual void StartCapture(TFunction<void(const TArray<uint8>&, float)> OnAudioData = nullptr, TFunction<void(const TArray<uint8>&, float)> OnCaptureFinished = nullptr) {};

/** Stop the audio capture and cleanup */
virtual void StopCapture() {};
Expand Down
14 changes: 14 additions & 0 deletions Source/TFAudioCapture/Private/IAudioDataInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

//Native interface to receive callbacks from audio capture
class IAudioDataInterface
{
public:
/** Required API */

/** When data gets streamed */
virtual void OnAudioDataEvent(const TArray<uint8>& Bytes, float MaxLevel) {};

/** When capture is complete */
virtual void OnCaptureFinishedEvent(const TArray<uint8>& Bytes, float MaxLevel) {};
};
10 changes: 9 additions & 1 deletion Source/TFAudioCapture/Private/TFAudioCaptureComponent.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include "AudioCapturePrivatePCH.h"
#include "TFAudioCaptureComponent.h"
#include "ITFAudioCapture.h"

Expand Down Expand Up @@ -67,3 +66,12 @@ void UTFAudioCaptureComponent::UninitializeComponent()

Super::UninitializeComponent();
}

void UTFAudioCaptureComponent::OnAudioDataEvent(const TArray<uint8>& Bytes, float MaxLevel)
{
OnAudioData.Broadcast(Bytes, MaxLevel);
}
void UTFAudioCaptureComponent::OnCaptureFinishedEvent(const TArray<uint8>& Bytes, float MaxLevel)
{
OnCaptureFinished.Broadcast(Bytes, MaxLevel);
}
7 changes: 6 additions & 1 deletion Source/TFAudioCapture/Public/ITFAudioCapture.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "IAudioCaptureInterface.h"
#include "IAudioDataInterface.h"
#include "ModuleManager.h"

class UTFAudioCaptureComponent;
Expand Down Expand Up @@ -30,7 +31,11 @@ class TFAUDIOCAPTURE_API ITFAudioCapture : public IModuleInterface, public IAudi
return FModuleManager::Get().IsModuleLoaded("TFAudioCapture");
}

//For components
virtual void AddAudioComponent(const UTFAudioCaptureComponent* Component) {};

virtual void RemoveAudioComponent(const UTFAudioCaptureComponent* Component) {};

//for native delegates
virtual void AddAudioDelegate(IAudioDataInterface* Delegate) {};
virtual void RemoveAudioDelegate(IAudioDataInterface* Delegate) {};
};
11 changes: 9 additions & 2 deletions Source/TFAudioCapture/Public/TFAudioCaptureComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

#include "Components/ActorComponent.h"
#include "AudioCaptureData.h"
#include "IAudioDataInterface.h"
#include "TFAudioCaptureComponent.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAudioDataSignature, const TArray<uint8>&, Bytes);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAudioDataSignature, const TArray<uint8>&, Bytes, float, MaxLevel);

/**
* Component used to capture microphone audio and emit bytes as the data streams in
*/
UCLASS(ClassGroup = "Sound", meta = (BlueprintSpawnableComponent))
class TFAUDIOCAPTURE_API UTFAudioCaptureComponent : public UActorComponent
class TFAUDIOCAPTURE_API UTFAudioCaptureComponent : public UActorComponent, public IAudioDataInterface
{
GENERATED_UCLASS_BODY()

Expand Down Expand Up @@ -44,6 +45,12 @@ class TFAUDIOCAPTURE_API UTFAudioCaptureComponent : public UActorComponent
UFUNCTION(BlueprintCallable, Category = "Utilities|Audio Capture")
void ConvertWavToRaw(const TArray<uint8>& InBytes, TArray<uint8>& OutBytes, FAudioCaptureOptions& OutOptions);

//Start IAudioDataInterface
virtual void OnAudioDataEvent(const TArray<uint8>& Bytes, float MaxLevel) override;
virtual void OnCaptureFinishedEvent(const TArray<uint8>& Bytes, float MaxLevel) override;
//End IAudioDataInterface


virtual void InitializeComponent() override;
virtual void UninitializeComponent() override;

Expand Down
3 changes: 2 additions & 1 deletion Source/TFAudioCapture/TFAudioCapture.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ public class TFAudioCapture : ModuleRules
{
public TFAudioCapture(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

PublicIncludePaths.AddRange(
PublicIncludePaths.AddRange(
new string[] {
"TFAudioCapture/Public"
}
Expand Down

0 comments on commit 7745768

Please sign in to comment.