mirror of
https://github.com/storytold/RuntimeAudioImporter.git
synced 2026-10-18 07:40:28 +00:00
add plugin data
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "RuntimeAudioImporter",
|
||||
"Description": "Runtime Audio Importer plugin for Unreal Engine 4. Supports mp3, wav and flac formats.",
|
||||
"Category": "Audio",
|
||||
"CreatedBy": "Respirant",
|
||||
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
|
||||
"DocsURL": "https://github.com/Respirant/RuntimeAudioImporter",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "https://telegram.me/Respirant",
|
||||
"CanContainContent": false,
|
||||
"IsBetaVersion": false,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": true,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "RuntimeAudioImporter",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "RuntimeAudioImporter.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterModule"
|
||||
|
||||
void FRuntimeAudioImporterModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
|
||||
}
|
||||
|
||||
void FRuntimeAudioImporterModule::ShutdownModule()
|
||||
{
|
||||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||
// we call this function before unloading the module.
|
||||
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FRuntimeAudioImporterModule, RuntimeAudioImporter)
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "RuntimeAudioImporterBPLibrary.h"
|
||||
#include "RuntimeAudioImporter.h"
|
||||
|
||||
URuntimeAudioImporterBPLibrary::URuntimeAudioImporterBPLibrary(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Transcode audio file to USoundWave static object */
|
||||
class USoundWave* URuntimeAudioImporterBPLibrary::GetSoundWaveFromAudioFile(const FString& filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus >& status, bool DefineFormatAutomatically)
|
||||
{
|
||||
return GetUSoundWaveFromAudioFile_Internal(filePath, Format, status, DefineFormatAutomatically);
|
||||
}
|
||||
|
||||
/* Get USoundWave static object reference */
|
||||
class USoundWave* URuntimeAudioImporterBPLibrary::GetSoundWaveObject(const uint8* WaveData, int32 WaveDataSize, TEnumAsByte < TranscodingStatus >& status) {
|
||||
USoundWave* sw = NewObject<USoundWave>(USoundWave::StaticClass());
|
||||
if (!sw) {
|
||||
status = TranscodingStatus::USoundWaveDeclarationError;
|
||||
return nullptr;
|
||||
}
|
||||
FWaveModInfo WaveInfo;
|
||||
FString ErrorReason;
|
||||
if (WaveInfo.ReadWaveInfo(WaveData, WaveDataSize, &ErrorReason))
|
||||
{
|
||||
if (*WaveInfo.pBitsPerSample != 16) {
|
||||
status = TranscodingStatus::UnsupportedBitDepth;
|
||||
return nullptr;
|
||||
}
|
||||
sw->InvalidateCompressedData();
|
||||
sw->RawData.Lock(LOCK_READ_WRITE);
|
||||
void* LockedData = sw->RawData.Realloc(WaveDataSize);
|
||||
FMemory::Memcpy(LockedData, WaveData, WaveDataSize);
|
||||
sw->RawData.Unlock();
|
||||
|
||||
int32 DurationDiv = *WaveInfo.pChannels * *WaveInfo.pBitsPerSample * *WaveInfo.pSamplesPerSec;
|
||||
if (DurationDiv)
|
||||
{
|
||||
sw->Duration = *WaveInfo.pWaveDataSize * 8.0f / DurationDiv;
|
||||
}
|
||||
else
|
||||
{
|
||||
sw->Duration = 0.0f;
|
||||
status = TranscodingStatus::ZeroDurationError;
|
||||
return nullptr;
|
||||
}
|
||||
sw->SetSampleRate(*WaveInfo.pSamplesPerSec);
|
||||
sw->NumChannels = *WaveInfo.pChannels;
|
||||
sw->RawPCMDataSize = WaveInfo.SampleDataSize;
|
||||
sw->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
|
||||
}
|
||||
else {
|
||||
if (ErrorReason.Equals(TEXT("Invalid WAVE file."), ESearchCase::CaseSensitive)) {
|
||||
status = TranscodingStatus::InvalidWAVEData;
|
||||
}
|
||||
else if (ErrorReason.Equals(TEXT("Unsupported WAVE file format: actual bit rate does not match the container size."), ESearchCase::CaseSensitive)) {
|
||||
status = TranscodingStatus::InvalidBitrate;
|
||||
}
|
||||
else if (ErrorReason.Equals(TEXT("Unsupported WAVE file format: subformat identifier not recognized."), ESearchCase::CaseSensitive)) {
|
||||
status = TranscodingStatus::InvalidBitrate;
|
||||
}
|
||||
else {
|
||||
status = TranscodingStatus::UnrecognizedReadWaveError;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
|
||||
FMemory::Memcpy(sw->RawPCMData, WaveInfo.SampleDataStart, sw->RawPCMDataSize);
|
||||
|
||||
if (!sw) {
|
||||
status = TranscodingStatus::InvalidUSoundWave;
|
||||
return nullptr;
|
||||
}
|
||||
status = TranscodingStatus::Success;
|
||||
return sw;
|
||||
}
|
||||
|
||||
#define DR_WAV_IMPLEMENTATION
|
||||
#include "dr_libs/dr_wav.h"
|
||||
|
||||
#define DR_MP3_IMPLEMENTATION
|
||||
#include "dr_libs/dr_mp3.h"
|
||||
|
||||
#define DR_FLAC_IMPLEMENTATION
|
||||
#include "dr_libs/dr_flac.h"
|
||||
|
||||
|
||||
/* Main internal get USoundWave from audio file */
|
||||
class USoundWave* URuntimeAudioImporterBPLibrary::GetUSoundWaveFromAudioFile_Internal(const FString& filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus >& status, bool DefineFormatAutomatically)
|
||||
{
|
||||
if (FPaths::FileExists(filePath) != true) {
|
||||
status = TranscodingStatus::AudioDoesNotExist;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int16_t* pSampleData;
|
||||
uint64 framesToWrite;
|
||||
uint32 channels;
|
||||
uint32 sampleRate;
|
||||
|
||||
if (DefineFormatAutomatically) {
|
||||
Format = GetAudioFormat(filePath);
|
||||
if (Format == AudioFormat::INVALID) {
|
||||
status = TranscodingStatus::InvalidAudioFormat;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (TranscodeAudioToWaveData(TCHAR_TO_ANSI(*filePath), Format, status, framesToWrite, pSampleData, channels, sampleRate) == false) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
drwav wavEncode;
|
||||
drwav_data_format format;
|
||||
format.container = drwav_container_riff;
|
||||
format.format = DR_WAVE_FORMAT_PCM;
|
||||
format.channels = channels;
|
||||
format.sampleRate = sampleRate;
|
||||
format.bitsPerSample = 16; // Unreal Engine 4 supports only 16 bit rate
|
||||
|
||||
void* pWavFileData;
|
||||
size_t wavFileSize;
|
||||
drwav_init_memory_write(&wavEncode, &pWavFileData, &wavFileSize, &format, NULL);
|
||||
drwav_write_pcm_frames(&wavEncode, framesToWrite, pSampleData);
|
||||
drwav_uninit(&wavEncode);
|
||||
|
||||
USoundWave* ReadyUSoundWave = GetSoundWaveObject((const uint8*)pWavFileData, wavFileSize, status); // A ready USoundWave static object
|
||||
|
||||
drwav_free(pWavFileData, NULL);
|
||||
return ReadyUSoundWave;
|
||||
}
|
||||
|
||||
|
||||
/* Automatically get audio format. Internal use only recommended */
|
||||
TEnumAsByte<AudioFormat> URuntimeAudioImporterBPLibrary::GetAudioFormat(const FString & filePath) {
|
||||
if (FPaths::GetExtension(filePath, false).Equals(TEXT("mp3"), ESearchCase::IgnoreCase)) {
|
||||
return AudioFormat::MP3;
|
||||
}
|
||||
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("wav"), ESearchCase::IgnoreCase)) {
|
||||
return AudioFormat::WAV;
|
||||
}
|
||||
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("flac"), ESearchCase::IgnoreCase)) {
|
||||
return AudioFormat::FLAC;
|
||||
}
|
||||
else {
|
||||
return AudioFormat::INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
/* Transcode Audio to Wave Data */
|
||||
bool URuntimeAudioImporterBPLibrary::TranscodeAudioToWaveData(const char* filePath, TEnumAsByte<AudioFormat> Format, TEnumAsByte<TranscodingStatus>& status, uint64& framesToWrite, int16_t*&pSampleData, uint32& channels, uint32& sampleRate) {
|
||||
switch (Format)
|
||||
{
|
||||
case MP3:
|
||||
{
|
||||
drmp3 mp3;
|
||||
if (!drmp3_init_file(&mp3, filePath, NULL)) {
|
||||
status = TranscodingStatus::FailedToOpenStream;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
pSampleData = (int16_t*)malloc((size_t)drmp3_get_pcm_frame_count(&mp3) * mp3.channels * sizeof(int16_t));
|
||||
framesToWrite = drmp3_read_pcm_frames_s16(&mp3, drmp3_get_pcm_frame_count(&mp3), pSampleData);
|
||||
channels = mp3.channels;
|
||||
sampleRate = mp3.sampleRate;
|
||||
drmp3_uninit(&mp3);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WAV:
|
||||
{
|
||||
drwav wav;
|
||||
if (!drwav_init_file(&wav, filePath, NULL)) {
|
||||
status = TranscodingStatus::FailedToOpenStream;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
pSampleData = (int16_t*)malloc((size_t)wav.totalPCMFrameCount * wav.channels * sizeof(int16_t));
|
||||
framesToWrite = drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, pSampleData);
|
||||
channels = wav.channels;
|
||||
sampleRate = wav.sampleRate;
|
||||
drwav_uninit(&wav);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FLAC:
|
||||
{
|
||||
drflac* pFlac = drflac_open_file(filePath, NULL);
|
||||
if (pFlac == NULL) {
|
||||
status = TranscodingStatus::FailedToOpenStream;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
pSampleData = (int16_t*)malloc((size_t)pFlac->totalPCMFrameCount * pFlac->channels * sizeof(int16_t));
|
||||
framesToWrite = drflac_read_pcm_frames_s16(pFlac, pFlac->totalPCMFrameCount, pSampleData);
|
||||
channels = pFlac->channels;
|
||||
sampleRate = pFlac->sampleRate;
|
||||
drflac_close(pFlac);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
status = TranscodingStatus::InvalidAudioFormat;
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FRuntimeAudioImporterModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
|
||||
};
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
|
||||
#include "Runtime/Engine/Classes/Sound/SoundWave.h"
|
||||
//#include "Runtime/Engine/Public/VorbisAudioInfo.h"
|
||||
#include "Developer/TargetPlatform/Public/Interfaces/IAudioFormat.h"
|
||||
|
||||
#include "Async/Async.h"
|
||||
|
||||
#include "RuntimeAudioImporterBPLibrary.generated.h"
|
||||
|
||||
|
||||
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum TranscodingStatus
|
||||
{
|
||||
Success UMETA(DisplayName = "Success"),
|
||||
FailedToOpenStream UMETA(DisplayName = "Failed to open stream"),
|
||||
UnsupportedBitDepth UMETA(DisplayName = "Unsupported bit depth (Unreal Engine only supports 16 bit)"),
|
||||
InvalidWAVEData UMETA(DisplayName = "Invalid WAVE data"),
|
||||
InvalidBitrate UMETA(DisplayName = "Invalid bitrate (Actual bit rate does not match the container size)"),
|
||||
InvalidSubformat UMETA(DisplayName = "InvalidSubformat (Subformat identifier not recognized)"),
|
||||
UnrecognizedReadWaveError UMETA(DisplayName = "Unrecognized ReadWave error"),
|
||||
USoundWaveDeclarationError UMETA(DisplayName = "USoundWave declaration error"),
|
||||
InvalidUSoundWave UMETA(DisplayName = "Invalid USoundWave (The USoundWave object contains incorrect data)"),
|
||||
InvalidAudioFormat UMETA(DisplayName = "Invalid audio format (Can't determine the format of the audio file)"),
|
||||
AudioDoesNotExist UMETA(DisplayName = "The audio file does not exist"),
|
||||
ZeroDurationError UMETA(DisplayName = "Transcoded audio has zero length")
|
||||
};
|
||||
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum AudioFormat
|
||||
{
|
||||
MP3 UMETA(DisplayName = "mp3"),
|
||||
WAV UMETA(DisplayName = "wav"),
|
||||
FLAC UMETA(DisplayName = "flac"),
|
||||
INVALID UMETA(DisplayName = "invalid (not defined format, internal use only)", Hidden)
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class RUNTIMEAUDIOIMPORTER_API URuntimeAudioImporterBPLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
public:
|
||||
|
||||
/* Transcode audio file to USoundWave static object */
|
||||
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, MP3, FLAC, WAV", ToolTip = "Transcode audio file to USoundWave static object"), Category = "RuntimeAudioImporter")
|
||||
static class USoundWave* GetSoundWaveFromAudioFile(const FString & filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus > & status, bool DefineFormatAutomatically);
|
||||
|
||||
private:
|
||||
|
||||
/* Main internal get USoundWave from audio file */
|
||||
static class USoundWave* GetUSoundWaveFromAudioFile_Internal(const FString & filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus > & status, bool DefineFormatAutomatically);
|
||||
|
||||
/* Get USoundWave static object reference */
|
||||
static class USoundWave* GetSoundWaveObject(const uint8 * WaveData, int32 WaveDataSize, TEnumAsByte < TranscodingStatus > & status);
|
||||
|
||||
/* Transcode Audio to Wave Data */
|
||||
static bool TranscodeAudioToWaveData(const char* filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus > & status, uint64 & framesToWrite, int16_t * &pSampleData, uint32 & channels, uint32 & sampleRate);
|
||||
|
||||
/* Automatically get audio format. Internal use only recommended */
|
||||
static TEnumAsByte<AudioFormat> GetAudioFormat(const FString & filePath);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Some copyright should be here...
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class RuntimeAudioImporter : ModuleRules
|
||||
{
|
||||
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user