mirror of
https://github.com/storytold/RuntimeAudioImporter.git
synced 2026-10-09 00:09:48 +00:00
add plugin data
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Respirant
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,31 @@
|
||||

|
||||
## Basic information
|
||||
This plugin will be useful for those who want to be able to import audio files of MP3, WAV and FLAC formats into the game in runtime (for example, if the game involves importing audio files from the player's device memory). It's easy to use in both C++ and Blueprints.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
- Low library size (≈ 1 mb)
|
||||
- Fast transcoding speed (≈ 200-900 ms)
|
||||
- Supported for major audio formats: MP3, WAV and FLAC
|
||||
- Automatic detection of audio format (by extension)
|
||||
- System for obtaining transcoding status for error detection
|
||||
- No any third party libraries and external dependencies
|
||||
- Support for all available devices (tested on Windows and Android, but there are no restrictions to work on other devices)
|
||||
|
||||
## How to install
|
||||
Just extract the "RuntimeAudioImporter" folder into your plugins project folder to get the following path: "[ProjectName]/Plugins/RuntimeAudioImporter".
|
||||
|
||||
## How to use
|
||||
There are two ways to use this plugin: using Blueprints and C++:
|
||||
1. Blueprints. Just reopen your Unreal project and find a node "GetSoundWaveFromAudioFile".
|
||||
2. C++. Open your "[ProjectName].Build.cs" and find a block of ` PublicDependencyModuleNames ` or ` PrivateDependencyModuleNames ` (it depends on your needs). Add `"RuntimeAudioImporter"` String inside this block. After just include "RuntimeAudioImporterBPLibrary.h" using ` #include "RuntimeAudioImporterBPLibrary.h" ` where needed.
|
||||
|
||||
## Additional information
|
||||
The in-memory audio transcoding operation is based on [dr_libs](https://github.com/mackron/dr_libs).
|
||||
|
||||
## Legal info
|
||||
|
||||
Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere.
|
||||
|
||||
Unreal® Engine, Copyright 1998 – 2020, Epic Games, Inc. All rights reserved.
|
||||
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 ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
Reference in New Issue
Block a user