mirror of
https://github.com/storytold/RuntimeAudioImporter.git
synced 2026-10-09 00:09:48 +00:00
start 4.26
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.28315.86
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine", "Engine", "{94A6C6F3-99B3-346E-9557-ABF9D4064DBD}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Games", "Games", "{8E2F6A87-1826-34F4-940C-CC23A48F9FE4}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AudioImporterTest", "Intermediate\ProjectFiles\AudioImporterTest.vcxproj", "{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UE4", "Intermediate\ProjectFiles\UE4.vcxproj", "{41DC5BBD-56BC-4896-8D8D-5C13F4636397}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Visualizers", "Visualizers", "{1CCEC849-CC72-4C59-8C36-2F7C38706D4C}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
D:\UE4\UE_4.26\Engine\Extras\VisualStudioDebugging\UE4.natvis = D:\UE4\UE_4.26\Engine\Extras\VisualStudioDebugging\UE4.natvis
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
DebugGame Editor|Android = DebugGame Editor|Android
|
||||||
|
DebugGame Editor|Win32 = DebugGame Editor|Win32
|
||||||
|
DebugGame Editor|Win64 = DebugGame Editor|Win64
|
||||||
|
DebugGame|Android = DebugGame|Android
|
||||||
|
DebugGame|Win32 = DebugGame|Win32
|
||||||
|
DebugGame|Win64 = DebugGame|Win64
|
||||||
|
Development Editor|Android = Development Editor|Android
|
||||||
|
Development Editor|Win32 = Development Editor|Win32
|
||||||
|
Development Editor|Win64 = Development Editor|Win64
|
||||||
|
Development|Android = Development|Android
|
||||||
|
Development|Win32 = Development|Win32
|
||||||
|
Development|Win64 = Development|Win64
|
||||||
|
Shipping|Android = Shipping|Android
|
||||||
|
Shipping|Win32 = Shipping|Win32
|
||||||
|
Shipping|Win64 = Shipping|Win64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Android.ActiveCfg = Invalid|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Win32.ActiveCfg = Invalid|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Win64.ActiveCfg = DebugGame_Editor|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Win64.Build.0 = DebugGame_Editor|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Android.ActiveCfg = Android_DebugGame|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Android.Build.0 = Android_DebugGame|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win32.ActiveCfg = DebugGame|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win32.Build.0 = DebugGame|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win64.ActiveCfg = DebugGame|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win64.Build.0 = DebugGame|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Android.ActiveCfg = Invalid|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Win32.ActiveCfg = Invalid|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Win64.ActiveCfg = Development_Editor|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Win64.Build.0 = Development_Editor|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Android.ActiveCfg = Android_Development|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Android.Build.0 = Android_Development|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win32.ActiveCfg = Development|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win32.Build.0 = Development|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win64.ActiveCfg = Development|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win64.Build.0 = Development|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Android.ActiveCfg = Android_Shipping|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Android.Build.0 = Android_Shipping|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win32.ActiveCfg = Shipping|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win32.Build.0 = Shipping|Win32
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win64.ActiveCfg = Shipping|x64
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win64.Build.0 = Shipping|x64
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame Editor|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development Editor|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Shipping|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Shipping|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Shipping|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{41DC5BBD-56BC-4896-8D8D-5C13F4636397} = {94A6C6F3-99B3-346E-9557-ABF9D4064DBD}
|
||||||
|
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F} = {8E2F6A87-1826-34F4-940C-CC23A48F9FE4}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"EngineAssociation": "4.26",
|
||||||
|
"Category": "",
|
||||||
|
"Description": "",
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "AudioImporterTest",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Plugins": [
|
||||||
|
{
|
||||||
|
"Name": "RuntimeAudioImporter",
|
||||||
|
"Enabled": true,
|
||||||
|
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "AndroidMedia",
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "AndroidMoviePlayer",
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "AvfMedia",
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "MediaCompositing",
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ImgMedia",
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "WindowsMoviePlayer",
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "MediaPlayerEditor",
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "MagicLeapMedia",
|
||||||
|
"Enabled": false,
|
||||||
|
"SupportedTargetPlatforms": [
|
||||||
|
"Lumin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "WmfMedia",
|
||||||
|
"Enabled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
|
||||||
|
|
||||||
|
[/Script/EngineSettings.GameMapsSettings]
|
||||||
|
EditorStartupMap=/Game/MainLevelTest.MainLevelTest
|
||||||
|
LocalMapOptions=
|
||||||
|
TransitionMap=None
|
||||||
|
bUseSplitscreen=False
|
||||||
|
TwoPlayerSplitscreenLayout=Horizontal
|
||||||
|
ThreePlayerSplitscreenLayout=FavorTop
|
||||||
|
FourPlayerSplitscreenLayout=Grid
|
||||||
|
bOffsetPlayerGamepadIds=False
|
||||||
|
GameInstanceClass=/Script/Engine.GameInstance
|
||||||
|
GameDefaultMap=/Game/MainLevelTest.MainLevelTest
|
||||||
|
ServerDefaultMap=/Engine/Maps/Entry.Entry
|
||||||
|
GlobalDefaultGameMode=/Script/Engine.GameModeBase
|
||||||
|
GlobalDefaultServerGameMode=None
|
||||||
|
|
||||||
|
[/Script/HardwareTargeting.HardwareTargetingSettings]
|
||||||
|
TargetedHardwareClass=Mobile
|
||||||
|
AppliedTargetedHardwareClass=Mobile
|
||||||
|
DefaultGraphicsPerformance=Maximum
|
||||||
|
AppliedDefaultGraphicsPerformance=Maximum
|
||||||
|
|
||||||
|
[/Script/Engine.Engine]
|
||||||
|
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/AudioImporterTest")
|
||||||
|
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/AudioImporterTest")
|
||||||
|
+ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="AudioImporterTestGameModeBase")
|
||||||
|
|
||||||
|
[/Script/Engine.RendererSettings]
|
||||||
|
r.Mobile.DisableVertexFog=True
|
||||||
|
r.Shadow.CSM.MaxMobileCascades=2
|
||||||
|
r.MobileMSAA=1
|
||||||
|
r.Mobile.AllowDitheredLODTransition=False
|
||||||
|
r.Mobile.AllowSoftwareOcclusion=False
|
||||||
|
r.Mobile.VirtualTextures=False
|
||||||
|
r.DiscardUnusedQuality=False
|
||||||
|
r.AllowOcclusionQueries=True
|
||||||
|
r.MinScreenRadiusForLights=0.030000
|
||||||
|
r.MinScreenRadiusForDepthPrepass=0.030000
|
||||||
|
r.MinScreenRadiusForCSMDepth=0.010000
|
||||||
|
r.PrecomputedVisibilityWarning=False
|
||||||
|
r.TextureStreaming=True
|
||||||
|
Compat.UseDXT5NormalMaps=False
|
||||||
|
r.VirtualTextures=False
|
||||||
|
r.VirtualTexturedLightmaps=False
|
||||||
|
r.VT.TileSize=128
|
||||||
|
r.VT.TileBorderSize=4
|
||||||
|
r.vt.FeedbackFactor=16
|
||||||
|
r.VT.EnableCompressZlib=True
|
||||||
|
r.VT.EnableCompressCrunch=False
|
||||||
|
r.ClearCoatNormal=False
|
||||||
|
r.ReflectionCaptureResolution=128
|
||||||
|
r.Mobile.ReflectionCaptureCompression=False
|
||||||
|
r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True
|
||||||
|
r.ForwardShading=False
|
||||||
|
r.VertexFoggingForOpaque=True
|
||||||
|
r.AllowStaticLighting=True
|
||||||
|
r.NormalMapsForStaticLighting=False
|
||||||
|
r.GenerateMeshDistanceFields=False
|
||||||
|
r.DistanceFieldBuild.EightBit=False
|
||||||
|
r.GenerateLandscapeGIData=False
|
||||||
|
r.DistanceFieldBuild.Compress=False
|
||||||
|
r.TessellationAdaptivePixelsPerTriangle=48.000000
|
||||||
|
r.SeparateTranslucency=False
|
||||||
|
r.TranslucentSortPolicy=0
|
||||||
|
TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000)
|
||||||
|
r.CustomDepth=1
|
||||||
|
r.CustomDepthTemporalAAJitter=True
|
||||||
|
r.PostProcessing.PropagateAlpha=0
|
||||||
|
r.DefaultFeature.Bloom=True
|
||||||
|
r.DefaultFeature.AmbientOcclusion=False
|
||||||
|
r.DefaultFeature.AmbientOcclusionStaticFraction=True
|
||||||
|
r.DefaultFeature.AutoExposure=False
|
||||||
|
r.DefaultFeature.AutoExposure.Method=0
|
||||||
|
r.DefaultFeature.AutoExposure.Bias=1.000000
|
||||||
|
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=False
|
||||||
|
r.UsePreExposure=True
|
||||||
|
r.EyeAdaptation.EditorOnly=False
|
||||||
|
r.DefaultFeature.MotionBlur=False
|
||||||
|
r.DefaultFeature.LensFlare=False
|
||||||
|
r.TemporalAA.Upsampling=False
|
||||||
|
r.SSGI.Enable=False
|
||||||
|
r.DefaultFeature.AntiAliasing=0
|
||||||
|
r.DefaultFeature.LightUnits=1
|
||||||
|
r.DefaultBackBufferPixelFormat=4
|
||||||
|
r.Shadow.UnbuiltPreviewInGame=True
|
||||||
|
r.StencilForLODDither=False
|
||||||
|
r.EarlyZPass=3
|
||||||
|
r.EarlyZPassOnlyMaterialMasking=False
|
||||||
|
r.DBuffer=True
|
||||||
|
r.ClearSceneMethod=1
|
||||||
|
r.BasePassOutputsVelocity=False
|
||||||
|
r.VertexDeformationOutputsVelocity=False
|
||||||
|
r.SelectiveBasePassOutputs=False
|
||||||
|
bDefaultParticleCutouts=False
|
||||||
|
fx.GPUSimulationTextureSizeX=1024
|
||||||
|
fx.GPUSimulationTextureSizeY=1024
|
||||||
|
r.AllowGlobalClipPlane=False
|
||||||
|
r.GBufferFormat=1
|
||||||
|
r.MorphTarget.Mode=True
|
||||||
|
r.GPUCrashDebugging=False
|
||||||
|
vr.InstancedStereo=False
|
||||||
|
r.MobileHDR=True
|
||||||
|
vr.MobileMultiView=False
|
||||||
|
r.Mobile.UseHWsRGBEncoding=False
|
||||||
|
vr.RoundRobinOcclusion=False
|
||||||
|
vr.ODSCapture=False
|
||||||
|
r.MeshStreaming=False
|
||||||
|
r.WireframeCullThreshold=5.000000
|
||||||
|
r.RayTracing=False
|
||||||
|
r.RayTracing.UseTextureLod=False
|
||||||
|
r.SupportStationarySkylight=True
|
||||||
|
r.SupportLowQualityLightmaps=True
|
||||||
|
r.SupportPointLightWholeSceneShadows=True
|
||||||
|
r.SupportAtmosphericFog=True
|
||||||
|
r.SupportSkyAtmosphere=True
|
||||||
|
r.SupportSkyAtmosphereAffectsHeightFog=False
|
||||||
|
r.SkinCache.CompileShaders=False
|
||||||
|
r.SkinCache.DefaultBehavior=1
|
||||||
|
r.SkinCache.SceneMemoryLimitInMB=128.000000
|
||||||
|
r.Mobile.EnableStaticAndCSMShadowReceivers=True
|
||||||
|
r.Mobile.EnableMovableLightCSMShaderCulling=True
|
||||||
|
r.Mobile.AllowDistanceFieldShadows=True
|
||||||
|
r.Mobile.AllowMovableDirectionalLights=True
|
||||||
|
r.MobileNumDynamicPointLights=4
|
||||||
|
r.MobileDynamicPointLightsUseStaticBranch=True
|
||||||
|
r.Mobile.EnableMovableSpotlights=False
|
||||||
|
r.Mobile.EnableMovableSpotlightsShadow=False
|
||||||
|
r.GPUSkin.Support16BitBoneIndex=False
|
||||||
|
r.GPUSkin.Limit2BoneInfluences=False
|
||||||
|
r.SupportDepthOnlyIndexBuffers=True
|
||||||
|
r.SupportReversedIndexBuffers=True
|
||||||
|
r.LightPropagationVolume=False
|
||||||
|
r.Mobile.AmbientOcclusion=False
|
||||||
|
r.GPUSkin.UnlimitedBoneInfluences=False
|
||||||
|
r.GPUSkin.UnlimitedBoneInfluencesThreshold=8
|
||||||
|
r.Mobile.PlanarReflectionMode=0
|
||||||
|
bStreamSkeletalMeshLODs=(Default=False,PerPlatform=())
|
||||||
|
bDiscardSkeletalMeshOptionalLODs=(Default=False,PerPlatform=())
|
||||||
|
VisualizeCalibrationColorMaterialPath=None
|
||||||
|
VisualizeCalibrationCustomMaterialPath=None
|
||||||
|
VisualizeCalibrationGrayscaleMaterialPath=None
|
||||||
|
|
||||||
|
[/Script/Slate.SlateSettings]
|
||||||
|
bExplicitCanvasChildZOrder=True
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
[/Script/EngineSettings.GeneralProjectSettings]
|
||||||
|
ProjectID=E5F8CB474977D102BC3EC9B00A5B7D1C
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
git config --global user.email "gtreshchev@gmail.com"
|
||||||
|
git config --global user.name "Georgy Treshchev"
|
||||||
|
git add *
|
||||||
|
git commit -m "start 4.26"
|
||||||
|
git push -f -u origin test
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"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": "Georgy Treshchev",
|
||||||
|
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
|
||||||
|
"DocsURL": "https://unreal.blog/runtime-audio-importer",
|
||||||
|
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036",
|
||||||
|
"SupportURL": "https://t.me/unrealblog",
|
||||||
|
"CanContainContent": true,
|
||||||
|
"IsBetaVersion": false,
|
||||||
|
"IsExperimentalVersion": false,
|
||||||
|
"Installed": true,
|
||||||
|
"EngineVersion": "4.26.0",
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "RuntimeAudioImporter",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default",
|
||||||
|
"WhitelistPlatforms": [
|
||||||
|
"Win64",
|
||||||
|
"Win32",
|
||||||
|
"Mac",
|
||||||
|
"Android",
|
||||||
|
"IOS",
|
||||||
|
"Linux"
|
||||||
|
],
|
||||||
|
"BlacklistPlatforms": [
|
||||||
|
"XboxOne",
|
||||||
|
"PS4",
|
||||||
|
"TVOS",
|
||||||
|
"Switch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "RuntimeAudioImporterEditor",
|
||||||
|
"Type": "Editor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+346
@@ -0,0 +1,346 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "AdvancedPCMCompressor.h"
|
||||||
|
#include "Interfaces/IAudioFormat.h"
|
||||||
|
#include "Serialization/MemoryWriter.h"
|
||||||
|
#include "ADPCMAudioInfo.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define FORMAT_FOURCC(ch0, ch1, ch2, ch3)\
|
||||||
|
((uint32)(uint8)(ch0) | ((uint32)(uint8)(ch1) << 8) |\
|
||||||
|
((uint32)(uint8)(ch2) << 16) | ((uint32)(uint8)(ch3) << 24 ))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace BaseDataOperations
|
||||||
|
{
|
||||||
|
template <uint32 N>
|
||||||
|
void GenerateWaveFile(RiffDataChunk (& RiffDataChunks)[N], TArray<uint8>& CompressedDataStore)
|
||||||
|
{
|
||||||
|
uint32 RiffDataSize = sizeof(uint32);
|
||||||
|
|
||||||
|
for (uint32 Scan = 0; Scan < N; ++Scan)
|
||||||
|
{
|
||||||
|
const RiffDataChunk& Chunk = RiffDataChunks[Scan];
|
||||||
|
RiffDataSize += (sizeof(Chunk.ID) + sizeof(Chunk.DataSize) + Chunk.DataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32 OutputDataSize = RiffDataSize + sizeof(uint32) + sizeof(uint32);
|
||||||
|
|
||||||
|
CompressedDataStore.Empty(OutputDataSize);
|
||||||
|
FMemoryWriter CompressedData(CompressedDataStore);
|
||||||
|
|
||||||
|
uint32 rID = FORMAT_FOURCC('R', 'I', 'F', 'F');
|
||||||
|
CompressedData.Serialize(&rID, sizeof(rID));
|
||||||
|
CompressedData.Serialize(&RiffDataSize, sizeof(RiffDataSize));
|
||||||
|
|
||||||
|
rID = FORMAT_FOURCC('W', 'A', 'V', 'E');
|
||||||
|
CompressedData.Serialize(&rID, sizeof(rID));
|
||||||
|
|
||||||
|
for (uint32 Scan = 0; Scan < N; ++Scan)
|
||||||
|
{
|
||||||
|
RiffDataChunk& Chunk = RiffDataChunks[Scan];
|
||||||
|
|
||||||
|
CompressedData.Serialize(&Chunk.ID, sizeof(Chunk.ID));
|
||||||
|
CompressedData.Serialize(&Chunk.DataSize, sizeof(Chunk.DataSize));
|
||||||
|
CompressedData.Serialize(Chunk.Data, Chunk.DataSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, uint32 B>
|
||||||
|
T SignExtend(const T ValueToExtend)
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
T ExtendedValue:B;
|
||||||
|
} SignExtender;
|
||||||
|
return SignExtender.ExtendedValue = ValueToExtend;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T ReadFromByteStream(const uint8* ByteStream, int32& ReadIndex, bool bLittleEndian = true)
|
||||||
|
{
|
||||||
|
T ValueRaw = 0;
|
||||||
|
|
||||||
|
if (bLittleEndian)
|
||||||
|
{
|
||||||
|
#if PLATFORM_LITTLE_ENDIAN
|
||||||
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
||||||
|
#else
|
||||||
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
||||||
|
#endif // PLATFORM_LITTLE_ENDIAN
|
||||||
|
{
|
||||||
|
ValueRaw |= ByteStream[ReadIndex++] << 8 * ByteIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if PLATFORM_LITTLE_ENDIAN
|
||||||
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
||||||
|
#else
|
||||||
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
||||||
|
#endif // PLATFORM_LITTLE_ENDIAN
|
||||||
|
{
|
||||||
|
ValueRaw |= ByteStream[ReadIndex++] << 8 * ByteIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValueRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void WriteToByteStream(T Value, uint8* ByteStream, int32& WriteIndex, bool bLittleEndian = true)
|
||||||
|
{
|
||||||
|
if (bLittleEndian)
|
||||||
|
{
|
||||||
|
#if PLATFORM_LITTLE_ENDIAN
|
||||||
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
||||||
|
#else
|
||||||
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
||||||
|
#endif // PLATFORM_LITTLE_ENDIAN
|
||||||
|
{
|
||||||
|
ByteStream[WriteIndex++] = (Value >> (8 * ByteIndex)) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if PLATFORM_LITTLE_ENDIAN
|
||||||
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
||||||
|
#else
|
||||||
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
||||||
|
#endif // PLATFORM_LITTLE_ENDIAN
|
||||||
|
{
|
||||||
|
ByteStream[WriteIndex++] = (Value >> (8 * ByteIndex)) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T ReadFromArray(const T* ElementArray, int32& ReadIndex, int32 NumElements, int32 IndexStride = 1)
|
||||||
|
{
|
||||||
|
T OutputValue = 0;
|
||||||
|
|
||||||
|
if (ReadIndex >= 0 && ReadIndex < NumElements)
|
||||||
|
{
|
||||||
|
OutputValue = ElementArray[ReadIndex];
|
||||||
|
ReadIndex += IndexStride;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OutputValue;
|
||||||
|
}
|
||||||
|
} // end namespace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace LPCM
|
||||||
|
{
|
||||||
|
void GenerateCompressedData(const TArray<uint8>& InputPCMData, TArray<uint8>& CompressedDataStore,
|
||||||
|
const FSoundQualityInfo& QualityInfo)
|
||||||
|
{
|
||||||
|
WaveFormatHeader Format;
|
||||||
|
Format.nChannels = static_cast<uint16>(QualityInfo.NumChannels);
|
||||||
|
Format.nSamplesPerSec = QualityInfo.SampleRate;
|
||||||
|
Format.nBlockAlign = static_cast<uint16>(Format.nChannels * sizeof(int16));
|
||||||
|
Format.nAvgBytesPerSec = Format.nBlockAlign * QualityInfo.SampleRate;
|
||||||
|
Format.wBitsPerSample = 16;
|
||||||
|
Format.wFormatTag = WAVE_FORMAT_LPCM;
|
||||||
|
|
||||||
|
RiffDataChunk RiffDataChunks[2];
|
||||||
|
RiffDataChunks[0].ID = FORMAT_FOURCC('f', 'm', 't', ' ');
|
||||||
|
RiffDataChunks[0].DataSize = sizeof(Format);
|
||||||
|
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
|
||||||
|
|
||||||
|
RiffDataChunks[1].ID = FORMAT_FOURCC('d', 'a', 't', 'a');
|
||||||
|
RiffDataChunks[1].DataSize = InputPCMData.Num();
|
||||||
|
RiffDataChunks[1].Data = const_cast<uint8*>(InputPCMData.GetData());
|
||||||
|
|
||||||
|
BaseDataOperations::GenerateWaveFile(RiffDataChunks, CompressedDataStore);
|
||||||
|
}
|
||||||
|
} // end namespace LPCM
|
||||||
|
|
||||||
|
namespace ADPCM
|
||||||
|
{
|
||||||
|
template <typename T>
|
||||||
|
static void GetAdaptationTable(T (& OutAdaptationTable)[NUM_ADAPTATION_TABLE])
|
||||||
|
{
|
||||||
|
static T AdaptationTable[] =
|
||||||
|
{
|
||||||
|
230, 230, 230, 230, 307, 409, 512, 614,
|
||||||
|
768, 614, 512, 409, 307, 230, 230, 230
|
||||||
|
};
|
||||||
|
|
||||||
|
FMemory::Memcpy(&OutAdaptationTable, AdaptationTable, sizeof(AdaptationTable));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FAdaptationContext
|
||||||
|
{
|
||||||
|
int32 AdaptationTable[NUM_ADAPTATION_TABLE];
|
||||||
|
int32 AdaptationCoefficient1[NUM_ADAPTATION_COEFF];
|
||||||
|
int32 AdaptationCoefficient2[NUM_ADAPTATION_COEFF];
|
||||||
|
|
||||||
|
int32 AdaptationDelta;
|
||||||
|
int32 Coefficient1;
|
||||||
|
int32 Coefficient2;
|
||||||
|
int32 Sample1;
|
||||||
|
int32 Sample2;
|
||||||
|
|
||||||
|
FAdaptationContext() :
|
||||||
|
AdaptationDelta(0),
|
||||||
|
Coefficient1(0),
|
||||||
|
Coefficient2(0),
|
||||||
|
Sample1(0),
|
||||||
|
Sample2(0)
|
||||||
|
{
|
||||||
|
GetAdaptationTable(AdaptationTable);
|
||||||
|
GetAdaptationCoefficients(AdaptationCoefficient1, AdaptationCoefficient2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8 EncodeNibble(FAdaptationContext& Context, int16 NextSample)
|
||||||
|
{
|
||||||
|
int32 PredictedSample = (Context.Sample1 * Context.Coefficient1 + Context.Sample2 * Context.Coefficient2) / 256;
|
||||||
|
int32 ErrorDelta = (NextSample - PredictedSample) / Context.AdaptationDelta;
|
||||||
|
ErrorDelta = FMath::Clamp(ErrorDelta, -8, 7);
|
||||||
|
|
||||||
|
PredictedSample += (Context.AdaptationDelta * ErrorDelta);
|
||||||
|
PredictedSample = FMath::Clamp(PredictedSample, -32768, 32767);
|
||||||
|
|
||||||
|
int8 SmallDelta = static_cast<int8>(ErrorDelta);
|
||||||
|
const uint8 EncodedNibble = reinterpret_cast<uint8&>(SmallDelta) & 0x0F;
|
||||||
|
|
||||||
|
Context.Sample2 = Context.Sample1;
|
||||||
|
Context.Sample1 = static_cast<int16>(PredictedSample);
|
||||||
|
Context.AdaptationDelta = (Context.AdaptationDelta * Context.AdaptationTable[EncodedNibble]) / 256;
|
||||||
|
Context.AdaptationDelta = FMath::Max(Context.AdaptationDelta, 16);
|
||||||
|
|
||||||
|
return EncodedNibble;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 EncodeBlock(const int16* InputPCMSamples, int32 SampleStride, int32 NumSamples, int32 BlockSize,
|
||||||
|
uint8* EncodedADPCMData)
|
||||||
|
{
|
||||||
|
FAdaptationContext Context;
|
||||||
|
int32 ReadIndex = 0;
|
||||||
|
int32 WriteIndex = 0;
|
||||||
|
|
||||||
|
const uint8 CoefficientIndex = 0;
|
||||||
|
Context.AdaptationDelta = Context.AdaptationTable[0];
|
||||||
|
|
||||||
|
Context.Sample2 = BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
|
||||||
|
Context.Sample1 = BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
|
||||||
|
Context.Coefficient1 = Context.AdaptationCoefficient1[CoefficientIndex];
|
||||||
|
Context.Coefficient2 = Context.AdaptationCoefficient2[CoefficientIndex];
|
||||||
|
|
||||||
|
BaseDataOperations::WriteToByteStream<uint8>(CoefficientIndex, EncodedADPCMData, WriteIndex);
|
||||||
|
BaseDataOperations::WriteToByteStream<int16>(Context.AdaptationDelta, EncodedADPCMData, WriteIndex);
|
||||||
|
BaseDataOperations::WriteToByteStream<int16>(Context.Sample1, EncodedADPCMData, WriteIndex);
|
||||||
|
BaseDataOperations::WriteToByteStream<int16>(Context.Sample2, EncodedADPCMData, WriteIndex);
|
||||||
|
|
||||||
|
while (WriteIndex < BlockSize)
|
||||||
|
{
|
||||||
|
EncodedADPCMData[WriteIndex] = EncodeNibble(
|
||||||
|
Context, BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride)) << 4;
|
||||||
|
EncodedADPCMData[WriteIndex++] |= EncodeNibble(
|
||||||
|
Context, BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride));
|
||||||
|
}
|
||||||
|
|
||||||
|
return WriteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenerateCompressedData(const TArray<uint8>& InputPCMData, TArray<uint8>& CompressedDataStore,
|
||||||
|
const FSoundQualityInfo& QualityInfo)
|
||||||
|
{
|
||||||
|
const int32 SourceSampleStride = QualityInfo.NumChannels;
|
||||||
|
|
||||||
|
const int32 SourceNumSamples = QualityInfo.SampleDataSize / 2;
|
||||||
|
const int32 SourceNumSamplesPerChannel = SourceNumSamples / QualityInfo.NumChannels;
|
||||||
|
|
||||||
|
const int32 CompressedNumSamplesPerByte = 2;
|
||||||
|
const int32 PreambleSamples = 2;
|
||||||
|
const int32 BlockSize = 512;
|
||||||
|
const int32 PreambleSize = 2 * PreambleSamples + 3;
|
||||||
|
const int32 CompressedSamplesPerBlock = (BlockSize - PreambleSize) * CompressedNumSamplesPerByte +
|
||||||
|
PreambleSamples;
|
||||||
|
const int32 NumBlocksPerChannel = (SourceNumSamplesPerChannel + CompressedSamplesPerBlock - 1) /
|
||||||
|
CompressedSamplesPerBlock;
|
||||||
|
|
||||||
|
const uint32 EncodedADPCMDataSize = NumBlocksPerChannel * BlockSize * QualityInfo.NumChannels;
|
||||||
|
uint8* EncodedADPCMData = static_cast<uint8*>(FMemory::Malloc(EncodedADPCMDataSize));
|
||||||
|
FMemory::Memzero(EncodedADPCMData, EncodedADPCMDataSize);
|
||||||
|
|
||||||
|
const int16* InputPCMSamples = reinterpret_cast<const int16*>(InputPCMData.GetData());
|
||||||
|
uint8* EncodedADPCMChannelData = EncodedADPCMData;
|
||||||
|
|
||||||
|
for (uint32 ChannelIndex = 0; ChannelIndex < QualityInfo.NumChannels; ++ChannelIndex)
|
||||||
|
{
|
||||||
|
const int16* ChannelPCMSamples = InputPCMSamples + ChannelIndex;
|
||||||
|
int32 SourceSampleOffset = 0;
|
||||||
|
int32 DestDataOffset = 0;
|
||||||
|
|
||||||
|
for (int32 BlockIndex = 0; BlockIndex < NumBlocksPerChannel; ++BlockIndex)
|
||||||
|
{
|
||||||
|
EncodeBlock(ChannelPCMSamples + SourceSampleOffset, SourceSampleStride,
|
||||||
|
SourceNumSamples - SourceSampleOffset, BlockSize, EncodedADPCMChannelData + DestDataOffset);
|
||||||
|
|
||||||
|
SourceSampleOffset += CompressedSamplesPerBlock * SourceSampleStride;
|
||||||
|
DestDataOffset += BlockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
EncodedADPCMChannelData += DestDataOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
ADPCMFormatHeader Format;
|
||||||
|
Format.BaseFormat.nChannels = static_cast<uint16>(QualityInfo.NumChannels);
|
||||||
|
Format.BaseFormat.nSamplesPerSec = QualityInfo.SampleRate;
|
||||||
|
Format.BaseFormat.nBlockAlign = static_cast<uint16>(BlockSize);
|
||||||
|
Format.BaseFormat.wBitsPerSample = 4;
|
||||||
|
Format.BaseFormat.wFormatTag = WAVE_FORMAT_ADPCM;
|
||||||
|
Format.wSamplesPerBlock = static_cast<uint16>(CompressedSamplesPerBlock);
|
||||||
|
Format.BaseFormat.nAvgBytesPerSec = ((Format.BaseFormat.nSamplesPerSec / Format.wSamplesPerBlock) * Format.
|
||||||
|
BaseFormat.nBlockAlign);
|
||||||
|
Format.wNumCoef = NUM_ADAPTATION_COEFF;
|
||||||
|
Format.SamplesPerChannel = SourceNumSamplesPerChannel;
|
||||||
|
Format.BaseFormat.cbSize = sizeof(Format) - sizeof(Format.BaseFormat);
|
||||||
|
|
||||||
|
RiffDataChunk RiffDataChunks[2];
|
||||||
|
RiffDataChunks[0].ID = FORMAT_FOURCC('f', 'm', 't', ' ');
|
||||||
|
RiffDataChunks[0].DataSize = sizeof(Format);
|
||||||
|
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
|
||||||
|
|
||||||
|
RiffDataChunks[1].ID = FORMAT_FOURCC('d', 'a', 't', 'a');
|
||||||
|
RiffDataChunks[1].DataSize = EncodedADPCMDataSize;
|
||||||
|
RiffDataChunks[1].Data = EncodedADPCMData;
|
||||||
|
|
||||||
|
BaseDataOperations::GenerateWaveFile(RiffDataChunks, CompressedDataStore);
|
||||||
|
FMemory::Free(EncodedADPCMData);
|
||||||
|
}
|
||||||
|
} // end namespace ADPCM
|
||||||
|
|
||||||
|
|
||||||
|
bool FAdvancedPCMCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
|
||||||
|
TArray<uint8>& OutBuffer)
|
||||||
|
{
|
||||||
|
if (QualityInfo.Quality == 100)
|
||||||
|
{
|
||||||
|
LPCM::GenerateCompressedData(SrcBuffer, OutBuffer, QualityInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ADPCM::GenerateCompressedData(SrcBuffer, OutBuffer, QualityInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OutBuffer.Num() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAdvancedPCMCompressor::AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize)
|
||||||
|
{
|
||||||
|
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
|
||||||
|
NewBuffer.Empty(ChunkReserveSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAdvancedPCMCompressor::AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData,
|
||||||
|
int32 ChunkDataSize)
|
||||||
|
{
|
||||||
|
TArray<uint8>& TargetBuffer = OutBuffers[OutBuffers.Num() - 1];
|
||||||
|
TargetBuffer.Append(ChunkData, ChunkDataSize);
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "OpusCompressor.h"
|
||||||
|
#include "Interfaces/IAudioFormat.h"
|
||||||
|
#include "Serialization/MemoryWriter.h"
|
||||||
|
#include "OpusAudioInfo.h"
|
||||||
|
|
||||||
|
#define OUTSIDE_SPEEX
|
||||||
|
|
||||||
|
THIRD_PARTY_INCLUDES_START
|
||||||
|
#include "opus_multistream.h"
|
||||||
|
#include "speex_resampler.h"
|
||||||
|
THIRD_PARTY_INCLUDES_END
|
||||||
|
|
||||||
|
|
||||||
|
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
|
||||||
|
|
||||||
|
bool FOpusCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
|
||||||
|
TArray<uint8>& CompressedDataStore)
|
||||||
|
{
|
||||||
|
const uint16 OpusSampleRate = GetBestOutputSampleRate(QualityInfo.SampleRate);
|
||||||
|
const int32 OpusFrameSizeMs = 60;
|
||||||
|
const int32 OpusFrameSizeSamples = (OpusSampleRate * OpusFrameSizeMs) / 1000;
|
||||||
|
const uint32 SampleStride = SAMPLE_SIZE * QualityInfo.NumChannels;
|
||||||
|
const int32 BytesPerFrame = OpusFrameSizeSamples * SampleStride;
|
||||||
|
|
||||||
|
TArray<uint8> SrcBufferCopy;
|
||||||
|
if (QualityInfo.SampleRate != OpusSampleRate)
|
||||||
|
{
|
||||||
|
if (!ResamplePCM(QualityInfo.NumChannels, SrcBuffer, QualityInfo.SampleRate, SrcBufferCopy, OpusSampleRate))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SrcBufferCopy = SrcBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 EncSize = opus_encoder_get_size(QualityInfo.NumChannels);
|
||||||
|
OpusEncoder* Encoder = static_cast<OpusEncoder*>(FMemory::Malloc(EncSize));
|
||||||
|
const int32 EncError = opus_encoder_init(Encoder, OpusSampleRate, QualityInfo.NumChannels, OPUS_APPLICATION_AUDIO);
|
||||||
|
if (EncError != OPUS_OK)
|
||||||
|
{
|
||||||
|
FMemory::Free(Encoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 BitRate = GetBitRateFromQuality(QualityInfo);
|
||||||
|
opus_encoder_ctl(Encoder, OPUS_SET_BITRATE(BitRate));
|
||||||
|
|
||||||
|
CompressedDataStore.Empty();
|
||||||
|
FMemoryWriter CompressedData(CompressedDataStore);
|
||||||
|
int32 SrcBufferOffset = 0;
|
||||||
|
|
||||||
|
int32 FramesToEncode = SrcBufferCopy.Num() / BytesPerFrame;
|
||||||
|
const uint32 AccurateSampleCount = SrcBufferCopy.Num() / SampleStride;
|
||||||
|
|
||||||
|
if (SrcBufferCopy.Num() % BytesPerFrame != 0)
|
||||||
|
{
|
||||||
|
int32 FrameDiff = BytesPerFrame - (SrcBufferCopy.Num() % BytesPerFrame);
|
||||||
|
SrcBufferCopy.AddZeroed(FrameDiff);
|
||||||
|
FramesToEncode++;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(QualityInfo.NumChannels <= MAX_uint8);
|
||||||
|
check(FramesToEncode <= MAX_uint16);
|
||||||
|
SerializeHeaderData(CompressedData, OpusSampleRate, AccurateSampleCount, QualityInfo.NumChannels, FramesToEncode);
|
||||||
|
|
||||||
|
TArray<uint8> TempCompressedData;
|
||||||
|
TempCompressedData.AddUninitialized(BytesPerFrame);
|
||||||
|
|
||||||
|
while (SrcBufferOffset < SrcBufferCopy.Num())
|
||||||
|
{
|
||||||
|
const int32 CompressedDataLength = opus_encode(
|
||||||
|
Encoder, reinterpret_cast<const opus_int16*>(SrcBufferCopy.GetData() + SrcBufferOffset),
|
||||||
|
OpusFrameSizeSamples, TempCompressedData.GetData(), TempCompressedData.Num());
|
||||||
|
|
||||||
|
if (CompressedDataLength < 0)
|
||||||
|
{
|
||||||
|
const char* ErrorStr = opus_strerror(CompressedDataLength);
|
||||||
|
UE_LOG(LogAudio, Warning, TEXT("Failed to encode: [%d] %s"), CompressedDataLength, ANSI_TO_TCHAR(ErrorStr));
|
||||||
|
|
||||||
|
FMemory::Free(Encoder);
|
||||||
|
|
||||||
|
CompressedDataStore.Empty();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(CompressedDataLength < MAX_uint16);
|
||||||
|
SerialiseFrameData(CompressedData, TempCompressedData.GetData(), CompressedDataLength);
|
||||||
|
SrcBufferOffset += BytesPerFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FMemory::Free(Encoder);
|
||||||
|
|
||||||
|
return CompressedDataStore.Num() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 FOpusCompressor::GetBestOutputSampleRate(int32 SampleRate)
|
||||||
|
{
|
||||||
|
static const uint16 ValidSampleRates[] =
|
||||||
|
{
|
||||||
|
0,
|
||||||
|
8000,
|
||||||
|
12000,
|
||||||
|
16000,
|
||||||
|
24000,
|
||||||
|
48000,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int32 Index = UE_ARRAY_COUNT(ValidSampleRates) - 2; Index >= 0; Index--)
|
||||||
|
{
|
||||||
|
if (SampleRate > ValidSampleRates[Index])
|
||||||
|
{
|
||||||
|
return ValidSampleRates[Index + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FOpusCompressor::ResamplePCM(uint32 NumChannels, const TArray<uint8>& InBuffer, uint32 InSampleRate,
|
||||||
|
TArray<uint8>& OutBuffer, uint32 OutSampleRate)
|
||||||
|
{
|
||||||
|
int32 err = 0;
|
||||||
|
SpeexResamplerState* resampler = speex_resampler_init(NumChannels, InSampleRate, OutSampleRate,
|
||||||
|
SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
||||||
|
if (err != RESAMPLER_ERR_SUCCESS)
|
||||||
|
{
|
||||||
|
speex_resampler_destroy(resampler);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32 SampleStride = SAMPLE_SIZE * NumChannels;
|
||||||
|
const float Duration = (float)InBuffer.Num() / (InSampleRate * SampleStride);
|
||||||
|
const int32 SafeCopySize = (Duration + 1) * OutSampleRate * SampleStride;
|
||||||
|
OutBuffer.Empty(SafeCopySize);
|
||||||
|
OutBuffer.AddUninitialized(SafeCopySize);
|
||||||
|
uint32 InSamples = InBuffer.Num() / SampleStride;
|
||||||
|
uint32 OutSamples = OutBuffer.Num() / SampleStride;
|
||||||
|
|
||||||
|
if (NumChannels == 1)
|
||||||
|
{
|
||||||
|
err = speex_resampler_process_int(resampler, 0, (const short*)(InBuffer.GetData()), &InSamples,
|
||||||
|
(short*)(OutBuffer.GetData()), &OutSamples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err = speex_resampler_process_interleaved_int(resampler, (const short*)(InBuffer.GetData()), &InSamples,
|
||||||
|
(short*)(OutBuffer.GetData()), &OutSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
speex_resampler_destroy(resampler);
|
||||||
|
if (err != RESAMPLER_ERR_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 WrittenBytes = (int32)(OutSamples * SampleStride);
|
||||||
|
if (WrittenBytes < OutBuffer.Num())
|
||||||
|
{
|
||||||
|
OutBuffer.SetNum(WrittenBytes, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FOpusCompressor::GetBitRateFromQuality(FSoundQualityInfo& QualityInfo)
|
||||||
|
{
|
||||||
|
const int32 OriginalBitRate = QualityInfo.SampleRate * QualityInfo.NumChannels * SAMPLE_SIZE * 8;
|
||||||
|
return static_cast<float>(OriginalBitRate) * FMath::GetMappedRangeValueClamped(
|
||||||
|
FVector2D(1, 100), FVector2D(0.04, 0.25), QualityInfo.Quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FOpusCompressor::SerializeHeaderData(FMemoryWriter& CompressedData, uint16 SampleRate, uint32 TrueSampleCount,
|
||||||
|
uint8 NumChannels, uint16 NumFrames)
|
||||||
|
{
|
||||||
|
const char* OpusIdentifier = OPUS_ID_STRING;
|
||||||
|
CompressedData.Serialize((void*)OpusIdentifier, FCStringAnsi::Strlen(OpusIdentifier) + 1);
|
||||||
|
CompressedData.Serialize(&SampleRate, sizeof(uint16));
|
||||||
|
CompressedData.Serialize(&TrueSampleCount, sizeof(uint32));
|
||||||
|
CompressedData.Serialize(&NumChannels, sizeof(uint8));
|
||||||
|
CompressedData.Serialize(&NumFrames, sizeof(uint16));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FOpusCompressor::SerialiseFrameData(FMemoryWriter& CompressedData, uint8* FrameData, uint16 FrameSize)
|
||||||
|
{
|
||||||
|
CompressedData.Serialize(&FrameSize, sizeof(uint16));
|
||||||
|
CompressedData.Serialize(FrameData, FrameSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FOpusCompressor::AddDataChunk(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkSize)
|
||||||
|
{
|
||||||
|
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
|
||||||
|
NewBuffer.Empty(ChunkSize);
|
||||||
|
NewBuffer.AddUninitialized(ChunkSize);
|
||||||
|
FMemory::Memcpy(NewBuffer.GetData(), ChunkData, ChunkSize);
|
||||||
|
return ChunkSize;
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "PreimportedSoundAsset.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "RuntimeAudioImporter.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterModule"
|
||||||
|
|
||||||
|
void FRuntimeAudioImporterModule::StartupModule()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FRuntimeAudioImporterModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FRuntimeAudioImporterModule, RuntimeAudioImporter)
|
||||||
+646
@@ -0,0 +1,646 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "RuntimeAudioImporterLibrary.h"
|
||||||
|
|
||||||
|
#include "AudioDevice.h"
|
||||||
|
#include "AudioCompressionSettingsUtils.h"
|
||||||
|
#include "Interfaces/IAudioFormat.h"
|
||||||
|
|
||||||
|
// Compressors for platform-specific audio formats
|
||||||
|
#include "VorbisCompressor.h"
|
||||||
|
#include "OpusCompressor.h"
|
||||||
|
#include "AdvancedPCMCompressor.h"
|
||||||
|
|
||||||
|
|
||||||
|
URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::CreateRuntimeAudioImporter()
|
||||||
|
{
|
||||||
|
URuntimeAudioImporterLibrary* Importer = NewObject<URuntimeAudioImporterLibrary>();
|
||||||
|
Importer->AddToRoot();
|
||||||
|
return Importer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::ImportAudioFromFile(
|
||||||
|
const FString& FilePath, TEnumAsByte<EAudioFormat> Format, const FBuffersDetailsStruct& BuffersDetails)
|
||||||
|
{
|
||||||
|
if (!FPaths::FileExists(FilePath))
|
||||||
|
{
|
||||||
|
// Trying to get a file inside packaged build
|
||||||
|
IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile();
|
||||||
|
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::AudioDoesNotExist);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Format == EAudioFormat::Auto)
|
||||||
|
{
|
||||||
|
Format = GetAudioFormat(FilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<uint8> AudioBuffer;
|
||||||
|
if (!FFileHelper::LoadFileToArray(AudioBuffer, *FilePath))
|
||||||
|
{
|
||||||
|
// Callback Dispatcher OnResult
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::LoadFileToArrayError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImportAudioFromBuffer(AudioBuffer, Format, BuffersDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::ImportAudioFromPreimportedSound(UPreimportedSoundAsset* PreimportedSoundAssetRef,
|
||||||
|
const FBuffersDetailsStruct& BuffersDetails)
|
||||||
|
{
|
||||||
|
ImportAudioFromBuffer(PreimportedSoundAssetRef->AudioDataArray, EAudioFormat::Mp3, BuffersDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::ImportAudioFromBuffer(const TArray<uint8>& AudioDataArray,
|
||||||
|
const TEnumAsByte<EAudioFormat>& Format,
|
||||||
|
const FBuffersDetailsStruct& BuffersDetails)
|
||||||
|
{
|
||||||
|
AsyncTask(ENamedThreads::AnyThread, [=]()
|
||||||
|
{
|
||||||
|
ImportAudioFromBuffer_Internal(AudioDataArray, Format, BuffersDetails);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URuntimeAudioImporterLibrary::DestroySoundWave(USoundWave* ReadySoundWave)
|
||||||
|
{
|
||||||
|
if (ReadySoundWave->IsValidLowLevel())
|
||||||
|
{
|
||||||
|
//Begin USoundWave destroy (block changing some data)
|
||||||
|
ReadySoundWave->ConditionalBeginDestroy();
|
||||||
|
ReadySoundWave->BeginDestroy();
|
||||||
|
|
||||||
|
//Delete all audio data from physical memory
|
||||||
|
ReadySoundWave->RawData.RemoveBulkData();
|
||||||
|
ReadySoundWave->RemoveAudioResource();
|
||||||
|
AsyncTask(ENamedThreads::AudioThread, [=]()
|
||||||
|
{
|
||||||
|
ReadySoundWave->InvalidateCompressedData(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Finish USoundWave destroy (complete object deletion as UObject)
|
||||||
|
ReadySoundWave->IsReadyForFinishDestroy(); //This is not needed for verification, but for the initial removal
|
||||||
|
ReadySoundWave->ConditionalFinishDestroy();
|
||||||
|
ReadySoundWave->FinishDestroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::ImportAudioFromBuffer_Internal(
|
||||||
|
const TArray<uint8>& AudioDataArray, const TEnumAsByte<EAudioFormat>& Format,
|
||||||
|
const FBuffersDetailsStruct& BuffersDetails)
|
||||||
|
{
|
||||||
|
BuffersDetailsInfo = BuffersDetails;
|
||||||
|
|
||||||
|
|
||||||
|
if (Format == EAudioFormat::Auto || Format == EAudioFormat::Invalid)
|
||||||
|
{
|
||||||
|
// Callback Dispatcher OnResult
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::InvalidAudioFormat);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transcoding importer Audio Data to PCM Data
|
||||||
|
if (!TranscodeAudioDataArrayToPCMData(AudioDataArray.GetData(), AudioDataArray.Num() - 2, Format))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ready USoundWave static object
|
||||||
|
USoundWave* ReadySoundWave = DefineSoundWave();
|
||||||
|
|
||||||
|
if (ReadySoundWave == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(100);
|
||||||
|
|
||||||
|
// Callback Dispatcher OnResult, with the created SoundWave object
|
||||||
|
OnResult_Internal(ReadySoundWave, ETranscodingStatus::SuccessImporting);
|
||||||
|
}
|
||||||
|
|
||||||
|
USoundWave* URuntimeAudioImporterLibrary::DefineSoundWave()
|
||||||
|
{
|
||||||
|
USoundWave* SoundWaveRef = NewObject<USoundWave>(USoundWave::StaticClass());
|
||||||
|
if (!SoundWaveRef)
|
||||||
|
{
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::SoundWaveDeclarationError);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(40);
|
||||||
|
|
||||||
|
// Fill SoundWave basic information (e.g. duration, number of channels, etc)
|
||||||
|
FillSoundWaveBasicInfo(SoundWaveRef);
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(45);
|
||||||
|
|
||||||
|
// Whether to fill the compressed buffer or not. If not filled, it is necessary to fill Raw (Wave) data so that the engine will automatically generate compressed data.
|
||||||
|
if (BuffersDetailsInfo.CompressionFormat == ECompressionFormat::RawData)
|
||||||
|
{
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(60);
|
||||||
|
|
||||||
|
// Transcode PCM to Raw (Wave) Data
|
||||||
|
if (!TranscodePCMToWAVData())
|
||||||
|
{
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// free unneeded PCM Data
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(75);
|
||||||
|
|
||||||
|
// Fill Raw (Wave) Data
|
||||||
|
FillRawWaveData(SoundWaveRef);
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(95);
|
||||||
|
|
||||||
|
return SoundWaveRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We don't need to fill RawPCMData buffer since it is not used at runtime during audio playback. But in case you want to fill this buffer - uncomment the code below (you should also remove some RawPCMData cleanups in code before it is executed)
|
||||||
|
sw->RawPCMData = static_cast<uint8*>(FMemory::Malloc(sw->RawPCMDataSize));
|
||||||
|
FMemory::Memmove(sw->RawPCMData, TranscodingFillInfo.PCMInfo.RawPCMData, sw->RawPCMDataSize);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Set a new sound compression quality
|
||||||
|
SoundWaveRef->CompressionQuality = BuffersDetailsInfo.SoundCompressionQuality;
|
||||||
|
|
||||||
|
// This variable won't be used anywhere, but it is recommended to set a decompression type to realtime since it will be decompressed at runtime
|
||||||
|
SoundWaveRef->DecompressionType = EDecompressionType::DTYPE_RealTime;
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(60);
|
||||||
|
|
||||||
|
// Transcode Raw PCM data to Compressed data
|
||||||
|
if (!TranscodePCMToCompressedData(SoundWaveRef))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(75);
|
||||||
|
|
||||||
|
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::NotStarted);
|
||||||
|
|
||||||
|
// Fill the compressed data
|
||||||
|
FillCompressedData(SoundWaveRef);
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(95);
|
||||||
|
|
||||||
|
|
||||||
|
return SoundWaveRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::FillSoundWaveBasicInfo(USoundWave* SoundWaveRef) const
|
||||||
|
{
|
||||||
|
SoundWaveRef->RawPCMDataSize = TranscodingFillInfo.PCMInfo.RawPCMDataSize;
|
||||||
|
SoundWaveRef->Duration = TranscodingFillInfo.SoundWaveBasicInfo.Duration;
|
||||||
|
SoundWaveRef->SetSampleRate(TranscodingFillInfo.SoundWaveBasicInfo.SampleRate);
|
||||||
|
SoundWaveRef->NumChannels = TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum;
|
||||||
|
SoundWaveRef->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
|
||||||
|
|
||||||
|
if (SoundWaveRef->NumChannels == 4)
|
||||||
|
{
|
||||||
|
SoundWaveRef->bIsAmbisonics = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::FillRawWaveData(USoundWave* SoundWaveRef) const
|
||||||
|
{
|
||||||
|
SoundWaveRef->RawData.Lock(LOCK_READ_WRITE);
|
||||||
|
FMemory::Memmove(SoundWaveRef->RawData.Realloc(TranscodingFillInfo.WAVInfo.WaveDataSize),
|
||||||
|
TranscodingFillInfo.WAVInfo.WaveData, TranscodingFillInfo.WAVInfo.WaveDataSize);
|
||||||
|
SoundWaveRef->RawData.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::FillCompressedData(USoundWave* SoundWaveRef)
|
||||||
|
{
|
||||||
|
// Get the name of the runtime format that will be used during audio playback
|
||||||
|
const FName CurrentAudioFormat = GEngine->GetAudioDeviceManager()->GetActiveAudioDevice()->
|
||||||
|
GetRuntimeFormat(SoundWaveRef);
|
||||||
|
|
||||||
|
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::InProgress);
|
||||||
|
|
||||||
|
// Fill the compressed data
|
||||||
|
FByteBulkData* CompressedBulkData = &SoundWaveRef->CompressedFormatData.GetFormat(
|
||||||
|
GetPlatformSpecificFormat(CurrentAudioFormat));
|
||||||
|
CompressedBulkData->Lock(LOCK_READ_WRITE);
|
||||||
|
FMemory::Memmove(CompressedBulkData->Realloc(TranscodingFillInfo.CompressedInfo.CompressedFormatData.Num()),
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData.GetData(),
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Num());
|
||||||
|
CompressedBulkData->Unlock();
|
||||||
|
|
||||||
|
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::Done);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URuntimeAudioImporterLibrary::TranscodePCMToCompressedData(USoundWave* SoundWaveToGetData)
|
||||||
|
{
|
||||||
|
// Raw PCM data Array
|
||||||
|
TArray<uint8> RawPCMArray = TArray<uint8>(TranscodingFillInfo.PCMInfo.RawPCMData,
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMDataSize);
|
||||||
|
|
||||||
|
// Sound quality information for compression purposes
|
||||||
|
FSoundQualityInfo SoundQualityInfo = GenerateSoundQualityInfo(SoundWaveToGetData);
|
||||||
|
|
||||||
|
switch (BuffersDetailsInfo.CompressionFormat)
|
||||||
|
{
|
||||||
|
case OggVorbis:
|
||||||
|
{
|
||||||
|
if (!FVorbisCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData))
|
||||||
|
{
|
||||||
|
// Clearing all data in case of error for performance reasons
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
|
||||||
|
RawPCMArray.Empty();
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OggOpus:
|
||||||
|
{
|
||||||
|
if (!FOpusCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData))
|
||||||
|
{
|
||||||
|
// Clearing all data in case of error for performance reasons
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
|
||||||
|
RawPCMArray.Empty();
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ADPCMLPCM:
|
||||||
|
{
|
||||||
|
if (!FAdvancedPCMCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Clearing all data in case of error for performance reasons
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
|
||||||
|
RawPCMArray.Empty();
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// Clearing all data in case of error for performance reasons
|
||||||
|
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
|
||||||
|
RawPCMArray.Empty();
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::InvalidCompressedFormat);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the compressed data is generated, RawPCMData is no longer needed.
|
||||||
|
RawPCMArray.Empty();
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(85);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Including dr_wav, dr_mp3 and dr_flac libraries
|
||||||
|
*/
|
||||||
|
#include "ThirdParty/dr_wav.h"
|
||||||
|
#include "ThirdParty/dr_mp3.h"
|
||||||
|
#include "ThirdParty/dr_flac.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replacing standard CPP memory functions (malloc, realloc, free) with memory management functions that are maintained by Epic as the engine evolves.
|
||||||
|
*/
|
||||||
|
void* Unreal_Malloc(size_t sz, void* pUserData)
|
||||||
|
{
|
||||||
|
return FMemory::Malloc(sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Unreal_Realloc(void* p, size_t sz, void* pUserData)
|
||||||
|
{
|
||||||
|
return FMemory::Realloc(p, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unreal_Free(void* p, void* pUserData)
|
||||||
|
{
|
||||||
|
FMemory::Free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URuntimeAudioImporterLibrary::TranscodeAudioDataArrayToPCMData(const uint8* AudioData, uint32 AudioDataSize,
|
||||||
|
TEnumAsByte<EAudioFormat> Format)
|
||||||
|
{
|
||||||
|
OnProgress_Internal(5);
|
||||||
|
switch (Format)
|
||||||
|
{
|
||||||
|
case EAudioFormat::Mp3:
|
||||||
|
{
|
||||||
|
drmp3_allocation_callbacks allocationCallbacksDecoding;
|
||||||
|
allocationCallbacksDecoding.pUserData = nullptr;
|
||||||
|
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
|
||||||
|
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
|
||||||
|
allocationCallbacksDecoding.onFree = Unreal_Free;
|
||||||
|
|
||||||
|
drmp3 mp3;
|
||||||
|
|
||||||
|
if (!drmp3_init_memory(&mp3, AudioData, AudioDataSize, &allocationCallbacksDecoding))
|
||||||
|
{
|
||||||
|
// Callback Dispatcher OnResult
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(10);
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
|
||||||
|
static_cast<size_t>(drmp3_get_pcm_frame_count(&mp3)) * mp3.channels * sizeof(int16_t)));
|
||||||
|
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(15);
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drmp3_read_pcm_frames_s16(
|
||||||
|
&mp3, drmp3_get_pcm_frame_count(&mp3),
|
||||||
|
reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(35);
|
||||||
|
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
|
||||||
|
RawPCMNumOfFrames * mp3.channels * sizeof(int16_t));
|
||||||
|
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(drmp3_get_pcm_frame_count(&mp3)) / mp3.
|
||||||
|
sampleRate;
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = mp3.channels;
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = mp3.sampleRate;
|
||||||
|
|
||||||
|
|
||||||
|
drmp3_uninit(&mp3);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case EAudioFormat::Wav:
|
||||||
|
{
|
||||||
|
drwav_allocation_callbacks allocationCallbacksDecoding;
|
||||||
|
allocationCallbacksDecoding.pUserData = nullptr;
|
||||||
|
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
|
||||||
|
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
|
||||||
|
allocationCallbacksDecoding.onFree = Unreal_Free;
|
||||||
|
|
||||||
|
drwav wav;
|
||||||
|
|
||||||
|
if (!drwav_init_memory(&wav, AudioData, AudioDataSize, &allocationCallbacksDecoding))
|
||||||
|
|
||||||
|
{
|
||||||
|
// Callback Dispatcher OnResult
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(10);
|
||||||
|
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
|
||||||
|
static_cast<size_t>(wav.totalPCMFrameCount) * wav.channels * sizeof(int16_t)));
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(15);
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drwav_read_pcm_frames_s16(
|
||||||
|
&wav, wav.totalPCMFrameCount, reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(35);
|
||||||
|
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
|
||||||
|
RawPCMNumOfFrames * wav.channels * sizeof(int16_t));
|
||||||
|
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(wav.totalPCMFrameCount) / wav.
|
||||||
|
sampleRate;
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = wav.channels;
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = wav.sampleRate;
|
||||||
|
|
||||||
|
|
||||||
|
drwav_uninit(&wav);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case EAudioFormat::Flac:
|
||||||
|
{
|
||||||
|
drflac_allocation_callbacks allocationCallbacksDecoding;
|
||||||
|
allocationCallbacksDecoding.pUserData = nullptr;
|
||||||
|
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
|
||||||
|
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
|
||||||
|
allocationCallbacksDecoding.onFree = Unreal_Free;
|
||||||
|
|
||||||
|
|
||||||
|
drflac* pFlac = drflac_open_memory(AudioData, AudioDataSize, &allocationCallbacksDecoding);
|
||||||
|
|
||||||
|
if (pFlac == nullptr)
|
||||||
|
{
|
||||||
|
// Callback Dispatcher OnResult
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(10);
|
||||||
|
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
|
||||||
|
static_cast<size_t>(pFlac->totalPCMFrameCount) * pFlac->channels * sizeof(int16_t)));
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(15);
|
||||||
|
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drflac_read_pcm_frames_s16(
|
||||||
|
pFlac, pFlac->totalPCMFrameCount, reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(35);
|
||||||
|
|
||||||
|
|
||||||
|
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
|
||||||
|
RawPCMNumOfFrames * pFlac->channels * sizeof(int16_t));
|
||||||
|
|
||||||
|
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(pFlac->totalPCMFrameCount) / pFlac->
|
||||||
|
sampleRate;
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = pFlac->channels;
|
||||||
|
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = pFlac->sampleRate;
|
||||||
|
drflac_close(pFlac);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// Callback Dispatcher OnResult
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::InvalidAudioFormat);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URuntimeAudioImporterLibrary::TranscodePCMToWAVData()
|
||||||
|
{
|
||||||
|
drwav wavEncode;
|
||||||
|
drwav_data_format format;
|
||||||
|
format.container = drwav_container_riff;
|
||||||
|
format.format = DR_WAVE_FORMAT_PCM;
|
||||||
|
format.channels = TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum;
|
||||||
|
format.sampleRate = TranscodingFillInfo.SoundWaveBasicInfo.SampleRate;
|
||||||
|
format.bitsPerSample = 16; // Unreal Engine supports only 16-bit rate
|
||||||
|
|
||||||
|
drwav_allocation_callbacks allocationCallbacks;
|
||||||
|
allocationCallbacks.pUserData = nullptr;
|
||||||
|
allocationCallbacks.onMalloc = Unreal_Malloc;
|
||||||
|
allocationCallbacks.onRealloc = Unreal_Realloc;
|
||||||
|
allocationCallbacks.onFree = Unreal_Free;
|
||||||
|
|
||||||
|
// Initializing PCM data writing to memory
|
||||||
|
if (!drwav_init_memory_write(&wavEncode, reinterpret_cast<void**>(&TranscodingFillInfo.WAVInfo.WaveData),
|
||||||
|
reinterpret_cast<size_t*>(&TranscodingFillInfo.WAVInfo.WaveDataSize), &format,
|
||||||
|
&allocationCallbacks))
|
||||||
|
{
|
||||||
|
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
|
||||||
|
OnResult_Internal(nullptr, ETranscodingStatus::PCMToWavDataError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(40);
|
||||||
|
|
||||||
|
// Write Raw PCM Data
|
||||||
|
drwav_write_pcm_frames(&wavEncode, TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames,
|
||||||
|
reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
|
||||||
|
|
||||||
|
// Callback Dispatcher OnProgress
|
||||||
|
OnProgress_Internal(60);
|
||||||
|
|
||||||
|
|
||||||
|
drwav_uninit(&wavEncode);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEnumAsByte<EAudioFormat> URuntimeAudioImporterLibrary::GetAudioFormat(const FString& FilePath)
|
||||||
|
{
|
||||||
|
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("mp3"), ESearchCase::IgnoreCase))
|
||||||
|
{
|
||||||
|
return EAudioFormat::Mp3;
|
||||||
|
}
|
||||||
|
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("wav"), ESearchCase::IgnoreCase))
|
||||||
|
{
|
||||||
|
return EAudioFormat::Wav;
|
||||||
|
}
|
||||||
|
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("flac"), ESearchCase::IgnoreCase))
|
||||||
|
{
|
||||||
|
return EAudioFormat::Flac;
|
||||||
|
}
|
||||||
|
return EAudioFormat::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
FSoundQualityInfo URuntimeAudioImporterLibrary::GenerateSoundQualityInfo(USoundWave* SoundWaveToGetData) const
|
||||||
|
{
|
||||||
|
FSoundQualityInfo SoundQualityInfo;
|
||||||
|
SoundQualityInfo.Duration = SoundWaveToGetData->Duration;
|
||||||
|
SoundQualityInfo.Quality = SoundWaveToGetData->CompressionQuality;
|
||||||
|
SoundQualityInfo.NumChannels = SoundWaveToGetData->NumChannels;
|
||||||
|
|
||||||
|
// since we cannot get the Sample Rate from the SoundWave, we will get it from the data of the initial PCM transcoding
|
||||||
|
SoundQualityInfo.SampleRate = TranscodingFillInfo.SoundWaveBasicInfo.SampleRate;
|
||||||
|
|
||||||
|
SoundQualityInfo.SampleDataSize = SoundWaveToGetData->RawPCMDataSize;
|
||||||
|
SoundQualityInfo.bStreaming = SoundWaveToGetData->IsStreaming(*FPlatformCompressionUtilities::GetCookOverrides());
|
||||||
|
SoundQualityInfo.DebugName = SoundWaveToGetData->GetFullName();
|
||||||
|
return SoundQualityInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FName URuntimeAudioImporterLibrary::GetPlatformSpecificFormat(const FName Format)
|
||||||
|
{
|
||||||
|
const FPlatformAudioCookOverrides* CompressionOverrides = FPlatformCompressionUtilities::GetCookOverrides();
|
||||||
|
|
||||||
|
// Platforms that require compression overrides get concatenated formats.
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FName PlatformSpecificFormat;
|
||||||
|
if (CompressionOverrides)
|
||||||
|
{
|
||||||
|
FString HashedString = *Format.ToString();
|
||||||
|
FPlatformAudioCookOverrides::GetHashSuffix(CompressionOverrides, HashedString);
|
||||||
|
PlatformSpecificFormat = *HashedString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlatformSpecificFormat = Format;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// Cache the concatenated hash:
|
||||||
|
static FName PlatformSpecificFormat;
|
||||||
|
static FName CachedFormat;
|
||||||
|
if (!Format.IsEqual(CachedFormat))
|
||||||
|
{
|
||||||
|
if (CompressionOverrides)
|
||||||
|
{
|
||||||
|
FString HashedString = *Format.ToString();
|
||||||
|
FPlatformAudioCookOverrides::GetHashSuffix(CompressionOverrides, HashedString);
|
||||||
|
PlatformSpecificFormat = *HashedString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlatformSpecificFormat = Format;
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedFormat = Format;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
return PlatformSpecificFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::OnProgress_Internal(int32 Percentage)
|
||||||
|
{
|
||||||
|
AsyncTask(ENamedThreads::GameThread, [=]()
|
||||||
|
{
|
||||||
|
OnProgress.Broadcast(Percentage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void URuntimeAudioImporterLibrary::OnResult_Internal(USoundWave* ReadySoundWave,
|
||||||
|
const TEnumAsByte<ETranscodingStatus>& Status)
|
||||||
|
{
|
||||||
|
AsyncTask(ENamedThreads::GameThread, [=]()
|
||||||
|
{
|
||||||
|
RemoveFromRoot();
|
||||||
|
OnResult.Broadcast(this, ReadySoundWave, Status);
|
||||||
|
});
|
||||||
|
}
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "VorbisCompressor.h"
|
||||||
|
#include "Interfaces/IAudioFormat.h"
|
||||||
|
#include "Serialization/MemoryWriter.h"
|
||||||
|
#include "VorbisAudioInfo.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if PLATFORM_SUPPORTS_VORBIS_CODEC
|
||||||
|
THIRD_PARTY_INCLUDES_START
|
||||||
|
#include "vorbis/vorbisenc.h"
|
||||||
|
#include "vorbis/vorbisfile.h"
|
||||||
|
THIRD_PARTY_INCLUDES_END
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define VORBIS_QUALITY_MODIFIER 0.85
|
||||||
|
|
||||||
|
#define SAMPLES_TO_READ 1024
|
||||||
|
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
|
||||||
|
|
||||||
|
|
||||||
|
bool FVorbisCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
|
||||||
|
TArray<uint8>& CompressedDataStore)
|
||||||
|
{
|
||||||
|
#if WITH_OGGVORBIS
|
||||||
|
{
|
||||||
|
short ReadBuffer[SAMPLES_TO_READ * SAMPLE_SIZE * 2];
|
||||||
|
|
||||||
|
ogg_stream_state Stream_State;
|
||||||
|
ogg_page Ogg_Page;
|
||||||
|
ogg_packet Ogg_Packet;
|
||||||
|
vorbis_info Vorbis_Info;
|
||||||
|
vorbis_comment Vorbis_Comment;
|
||||||
|
vorbis_dsp_state Dsp_State;
|
||||||
|
vorbis_block Vorbis_Block;
|
||||||
|
uint32 i;
|
||||||
|
bool bEndOfBitstream;
|
||||||
|
|
||||||
|
CompressedDataStore.Empty();
|
||||||
|
FMemoryWriter CompressedData(CompressedDataStore);
|
||||||
|
uint32 BufferOffset = 0;
|
||||||
|
|
||||||
|
float CompressionQuality = (float)(QualityInfo.Quality * VORBIS_QUALITY_MODIFIER) / 100.0f;
|
||||||
|
CompressionQuality = FMath::Clamp(CompressionQuality, -0.1f, 1.0f);
|
||||||
|
|
||||||
|
vorbis_info_init(&Vorbis_Info);
|
||||||
|
|
||||||
|
if (vorbis_encode_init_vbr(&Vorbis_Info, QualityInfo.NumChannels, QualityInfo.SampleRate, CompressionQuality))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vorbis_comment_init( &Vorbis_Comment );
|
||||||
|
vorbis_comment_add_tag( &Vorbis_Comment, "ENCODER", "RuntimeAudioImporter" );
|
||||||
|
|
||||||
|
vorbis_analysis_init(&Dsp_State, &Vorbis_Info);
|
||||||
|
vorbis_block_init(&Dsp_State, &Vorbis_Block);
|
||||||
|
|
||||||
|
ogg_stream_init(&Stream_State, 0);
|
||||||
|
|
||||||
|
ogg_packet header;
|
||||||
|
ogg_packet header_comm;
|
||||||
|
ogg_packet header_code;
|
||||||
|
|
||||||
|
vorbis_analysis_headerout(&Dsp_State, &Vorbis_Comment, &header, &header_comm, &header_code);
|
||||||
|
ogg_stream_packetin(&Stream_State, &header);
|
||||||
|
ogg_stream_packetin(&Stream_State, &header_comm);
|
||||||
|
ogg_stream_packetin(&Stream_State, &header_code);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int result = ogg_stream_flush(&Stream_State, &Ogg_Page);
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompressedData.Serialize(Ogg_Page.header, Ogg_Page.header_len);
|
||||||
|
CompressedData.Serialize(Ogg_Page.body, Ogg_Page.body_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bEndOfBitstream = false;
|
||||||
|
while (!bEndOfBitstream)
|
||||||
|
{
|
||||||
|
uint32 BytesToRead = FMath::Min(SAMPLES_TO_READ * QualityInfo.NumChannels * SAMPLE_SIZE,
|
||||||
|
QualityInfo.SampleDataSize - BufferOffset);
|
||||||
|
FMemory::Memcpy(ReadBuffer, SrcBuffer.GetData() + BufferOffset, BytesToRead);
|
||||||
|
BufferOffset += BytesToRead;
|
||||||
|
|
||||||
|
if (BytesToRead == 0)
|
||||||
|
{
|
||||||
|
vorbis_analysis_wrote(&Dsp_State, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float** buffer = vorbis_analysis_buffer(&Dsp_State, SAMPLES_TO_READ);
|
||||||
|
|
||||||
|
if (QualityInfo.NumChannels == 1)
|
||||||
|
{
|
||||||
|
for (i = 0; i < BytesToRead / SAMPLE_SIZE; i++)
|
||||||
|
{
|
||||||
|
buffer[0][i] = (ReadBuffer[i]) / 32768.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (i = 0; i < BytesToRead / (SAMPLE_SIZE * 2); i++)
|
||||||
|
{
|
||||||
|
buffer[0][i] = (ReadBuffer[i * 2]) / 32768.0f;
|
||||||
|
buffer[1][i] = (ReadBuffer[i * 2 + 1]) / 32768.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vorbis_analysis_wrote(&Dsp_State, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (vorbis_analysis_blockout(&Dsp_State, &Vorbis_Block) == 1)
|
||||||
|
{
|
||||||
|
vorbis_analysis(&Vorbis_Block, NULL);
|
||||||
|
vorbis_bitrate_addblock(&Vorbis_Block);
|
||||||
|
|
||||||
|
while (vorbis_bitrate_flushpacket(&Dsp_State, &Ogg_Packet))
|
||||||
|
{
|
||||||
|
ogg_stream_packetin(&Stream_State, &Ogg_Packet);
|
||||||
|
|
||||||
|
while (!bEndOfBitstream)
|
||||||
|
{
|
||||||
|
int result = ogg_stream_pageout(&Stream_State, &Ogg_Page);
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CompressedData.Serialize(Ogg_Page.header, Ogg_Page.header_len);
|
||||||
|
CompressedData.Serialize(Ogg_Page.body, Ogg_Page.body_len);
|
||||||
|
|
||||||
|
if (ogg_page_eos(&Ogg_Page))
|
||||||
|
{
|
||||||
|
bEndOfBitstream = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ogg_stream_clear(&Stream_State);
|
||||||
|
vorbis_block_clear(&Vorbis_Block);
|
||||||
|
vorbis_dsp_clear(&Dsp_State);
|
||||||
|
vorbis_comment_clear(&Vorbis_Comment);
|
||||||
|
vorbis_info_clear(&Vorbis_Info);
|
||||||
|
}
|
||||||
|
return CompressedDataStore.Num() > 0;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif // WITH_OGGVOBVIS
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TArray<int32> FVorbisCompressor::GetChannelOrder(int32 NumChannels)
|
||||||
|
{
|
||||||
|
TArray<int32> ChannelOrder;
|
||||||
|
|
||||||
|
switch (NumChannels)
|
||||||
|
{
|
||||||
|
case 6:
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < NumChannels; i++)
|
||||||
|
{
|
||||||
|
ChannelOrder.Add(VorbisChannelInfo::Order[NumChannels - 1][i]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < NumChannels; i++)
|
||||||
|
{
|
||||||
|
ChannelOrder.Add(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChannelOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32 FVorbisCompressor::GetMinimumSizeForInitialChunk(FName Format, const TArray<uint8>& SrcBuffer)
|
||||||
|
{
|
||||||
|
return 6 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FVorbisCompressor::AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize)
|
||||||
|
{
|
||||||
|
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
|
||||||
|
NewBuffer.Empty(ChunkReserveSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FVorbisCompressor::AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize)
|
||||||
|
{
|
||||||
|
TArray<uint8>& TargetBuffer = OutBuffers[OutBuffers.Num() - 1];
|
||||||
|
TargetBuffer.Append(ChunkData, ChunkDataSize);
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
struct FSoundQualityInfo;
|
||||||
|
struct RiffDataChunk
|
||||||
|
{
|
||||||
|
uint32 ID;
|
||||||
|
uint32 DataSize;
|
||||||
|
uint8* Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class RUNTIMEAUDIOIMPORTER_API FAdvancedPCMCompressor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
|
||||||
|
TArray<uint8>& OutBuffer);
|
||||||
|
|
||||||
|
static void AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize);
|
||||||
|
|
||||||
|
static void AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize);
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
struct FSoundQualityInfo;
|
||||||
|
|
||||||
|
class RUNTIMEAUDIOIMPORTER_API FOpusCompressor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
|
||||||
|
TArray<uint8>& OutBuffer);
|
||||||
|
|
||||||
|
static uint16 GetBestOutputSampleRate(int32 SampleRate);
|
||||||
|
|
||||||
|
static bool ResamplePCM(uint32 NumChannels, const TArray<uint8>& InBuffer, uint32 InSampleRate,
|
||||||
|
TArray<uint8>& OutBuffer, uint32 OutSampleRate);
|
||||||
|
|
||||||
|
static int32 GetBitRateFromQuality(FSoundQualityInfo& QualityInfo);
|
||||||
|
|
||||||
|
static void SerializeHeaderData(FMemoryWriter& CompressedData, uint16 SampleRate, uint32 TrueSampleCount,
|
||||||
|
uint8 NumChannels, uint16 NumFrames);
|
||||||
|
|
||||||
|
static void SerialiseFrameData(FMemoryWriter& CompressedData, uint8* FrameData, uint16 FrameSize);
|
||||||
|
|
||||||
|
static int32 AddDataChunk(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkSize);
|
||||||
|
};
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "PreimportedSoundAsset.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-imported asset which collects MP3 audio data. Used if you want to load the MP3 file into the editor in advance
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType, Category = "RuntimeAudioImporter")
|
||||||
|
class RUNTIMEAUDIOIMPORTER_API UPreimportedSoundAsset : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Audio data array. Only MP3 data is intended to be here */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<uint8> AudioDataArray;
|
||||||
|
|
||||||
|
/** Information about the basic details of an audio file. Used only for convenience in the editor */
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
UPROPERTY(Category = "File Path", VisibleAnywhere, Meta = (DisplayName = "Source file path"))
|
||||||
|
FString SourceFilePath;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Info", VisibleAnywhere, Meta = (DisplayName = "Sound duration"))
|
||||||
|
FString SoundDuration;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Info", VisibleAnywhere, Meta = (DisplayName = "Number of channels"))
|
||||||
|
int32 NumberOfChannels;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Info", VisibleAnywhere, Meta = (DisplayName = "Sample rate"))
|
||||||
|
int32 SampleRate;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// Respirant 2020.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
class FRuntimeAudioImporterModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
+358
@@ -0,0 +1,358 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Sound/SoundWave.h"
|
||||||
|
#include "Async/Async.h"
|
||||||
|
|
||||||
|
#include "PreimportedSoundAsset.h"
|
||||||
|
|
||||||
|
#include "RuntimeAudioImporterLibrary.generated.h"
|
||||||
|
|
||||||
|
/** Possible audio importing results */
|
||||||
|
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
|
||||||
|
enum ETranscodingStatus
|
||||||
|
{
|
||||||
|
/** Success importing */
|
||||||
|
SuccessImporting UMETA(DisplayName = "Success Importing"),
|
||||||
|
|
||||||
|
/** Failed to read Audio Data Array */
|
||||||
|
FailedToReadAudioDataArray UMETA(DisplayName = "Failed to read Audio Data Array"),
|
||||||
|
|
||||||
|
/** SoundWave declaration error */
|
||||||
|
SoundWaveDeclarationError UMETA(DisplayName = "SoundWave declaration error"),
|
||||||
|
|
||||||
|
/** Invalid audio format (Can't determine the format of the audio file) */
|
||||||
|
InvalidAudioFormat UMETA(DisplayName = "Invalid audio format"),
|
||||||
|
|
||||||
|
/** The audio file does not exist */
|
||||||
|
AudioDoesNotExist UMETA(DisplayName = "Audio does not exist"),
|
||||||
|
|
||||||
|
/** Load file to array error */
|
||||||
|
LoadFileToArrayError UMETA(DisplayName = "Load file to array error"),
|
||||||
|
|
||||||
|
/** Transcoding PCM to Wav data error */
|
||||||
|
PCMToWavDataError UMETA(DisplayName = "PCM to Wav data error"),
|
||||||
|
|
||||||
|
/** Generate compressed data error */
|
||||||
|
GenerateCompressedDataError UMETA(DisplayName = "Generate compressed data error"),
|
||||||
|
|
||||||
|
/** Invalid compressed format error */
|
||||||
|
InvalidCompressedFormat UMETA(DisplayName = "Invalid compressed format")
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Possible audio formats (extensions) */
|
||||||
|
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
|
||||||
|
enum EAudioFormat
|
||||||
|
{
|
||||||
|
/** Determine format automatically */
|
||||||
|
Auto UMETA(DisplayName = "Determine format automatically"),
|
||||||
|
|
||||||
|
/** MP3 format */
|
||||||
|
Mp3 UMETA(DisplayName = "mp3"),
|
||||||
|
|
||||||
|
/** WAV format */
|
||||||
|
Wav UMETA(DisplayName = "wav"),
|
||||||
|
|
||||||
|
/** FLAC format */
|
||||||
|
Flac UMETA(DisplayName = "flac"),
|
||||||
|
|
||||||
|
/** Invalid format */
|
||||||
|
Invalid UMETA(DisplayName = "invalid (not defined format, CPP use only)", Hidden)
|
||||||
|
};
|
||||||
|
|
||||||
|
/** List of all supported compression formats */
|
||||||
|
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
|
||||||
|
enum ECompressionFormat
|
||||||
|
{
|
||||||
|
/** Raw (Wav) data format. Not recommended because it doesn't work on packaged build */
|
||||||
|
RawData UMETA(DisplayName = "Raw Data"),
|
||||||
|
|
||||||
|
/** Ogg Vorbis format. It is used, for example, on Android and Windows */
|
||||||
|
OggVorbis UMETA(DisplayName = "Ogg Vorbis"),
|
||||||
|
|
||||||
|
/** ADPCM or LPCM formats. LPCM will be used if the compression quality is 100%, otherwise ADPCM */
|
||||||
|
ADPCMLPCM UMETA(DisplayName = "ADPCM/LPCM"),
|
||||||
|
|
||||||
|
/** Ogg Opus format. Mostly not used anywhere, but can be used for personal purposes */
|
||||||
|
OggOpus UMETA(DisplayName = "Ogg Opus")
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Information about which buffers to fill */
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FBuffersDetailsStruct
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Needed compression format */
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
TEnumAsByte<ECompressionFormat> CompressionFormat = OggVorbis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sound compression quality. The range of numbers is from 0 to 100, in percent. How much to compress the sound, where 0 is the maximum loss, 100 is the minimum.
|
||||||
|
* Not used if CompressionFormat is "RawData"
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
int32 SoundCompressionQuality = 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Basic SoundWave data. CPP use only. */
|
||||||
|
struct FSoundWaveBasicStruct
|
||||||
|
{
|
||||||
|
/** Number of channels */
|
||||||
|
int32 ChannelsNum;
|
||||||
|
|
||||||
|
/** Sample rate (samples per second, sampling frequency) */
|
||||||
|
uint32 SampleRate;
|
||||||
|
|
||||||
|
/** Sound wave duration, sec */
|
||||||
|
float Duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Raw PCM Data */
|
||||||
|
struct FPCMStruct
|
||||||
|
{
|
||||||
|
/** Raw PCM data pointer */
|
||||||
|
uint8* RawPCMData;
|
||||||
|
|
||||||
|
/** Number of PCM frames */
|
||||||
|
uint64 RawPCMNumOfFrames;
|
||||||
|
|
||||||
|
/** Raw PCM data size */
|
||||||
|
uint32 RawPCMDataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Wav (or Raw) Data */
|
||||||
|
struct FWAVStruct
|
||||||
|
{
|
||||||
|
/** Wave data pointer */
|
||||||
|
uint8* WaveData;
|
||||||
|
|
||||||
|
/** Wave data size */
|
||||||
|
int32 WaveDataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Compressed Data (Ogg Vorbis, ADPCM, etc) */
|
||||||
|
struct FCompressedStruct
|
||||||
|
{
|
||||||
|
/** Array, which contains compressed format data */
|
||||||
|
TArray<uint8> CompressedFormatData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Main, mostly in-memory information (like PCM, Wav, etc) */
|
||||||
|
struct FTranscodingFillStruct
|
||||||
|
{
|
||||||
|
/** SoundWave basic info (e.g. duration, number of channels, etc) */
|
||||||
|
FSoundWaveBasicStruct SoundWaveBasicInfo;
|
||||||
|
|
||||||
|
/** Raw PCM Data info */
|
||||||
|
FPCMStruct PCMInfo;
|
||||||
|
|
||||||
|
/** Wav (or Raw) Data info */
|
||||||
|
FWAVStruct WAVInfo;
|
||||||
|
|
||||||
|
/** Compressed Data info (Ogg Vorbis, ADPCM, etc) */
|
||||||
|
FCompressedStruct CompressedInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declaration of the FSoundQualityInfo structure
|
||||||
|
struct FSoundQualityInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare delegate which will be called during the transcoding process
|
||||||
|
*
|
||||||
|
* @param Percentage Percentage of importing completed (0-100%)
|
||||||
|
*/
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnProgress, const int32, Percentage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare a delegate that will be called on the transcoding result
|
||||||
|
*
|
||||||
|
* @param RuntimeAudioImporterObject Runtime Audio Importer object reference
|
||||||
|
* @param ReadySoundWave Ready SoundWave object reference
|
||||||
|
* @param Status TranscodingStatus Enum in case an error occurs
|
||||||
|
*/
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnResult, class URuntimeAudioImporterLibrary*,
|
||||||
|
RuntimeAudioImporterObjectRef,
|
||||||
|
const USoundWave*, SoundWaveRef,
|
||||||
|
const TEnumAsByte < ETranscodingStatus >&, Status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime Audio Importer object
|
||||||
|
* Allows you to do various things with audio files in real time, for example, import audio files into a SoundWave engine object
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType, Category = "RuntimeAudioImporter")
|
||||||
|
class RUNTIMEAUDIOIMPORTER_API URuntimeAudioImporterLibrary : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Bind to know when the transcoding is on progress */
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "RuntimeAudioImporter")
|
||||||
|
FOnProgress OnProgress;
|
||||||
|
|
||||||
|
/** Bind to know when the transcoding is complete (even if it fails) */
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "RuntimeAudioImporter")
|
||||||
|
FOnResult OnResult;
|
||||||
|
|
||||||
|
/** Transcoding fill info. CPP use only */
|
||||||
|
FTranscodingFillStruct TranscodingFillInfo = FTranscodingFillStruct();
|
||||||
|
|
||||||
|
/** Buffers details info. CPP use only */
|
||||||
|
FBuffersDetailsStruct BuffersDetailsInfo = FBuffersDetailsStruct();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a RuntimeAudioImporter object
|
||||||
|
*
|
||||||
|
* @return The RuntimeAudioImporter object. Bind to it's OnProgress and OnResult events to know when it is in the process of transcoding and imported
|
||||||
|
* @note You must place the returned reference to the RuntimeAudioImporterLibrary in a separate variable so that the UObject is not removed during garbage collection
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, meta = (Keywords = "Create, Audio, Runtime, MP3, FLAC, WAV"), Category =
|
||||||
|
"RuntimeAudioImporter")
|
||||||
|
static URuntimeAudioImporterLibrary* CreateRuntimeAudioImporter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transcode audio file to SoundWave object
|
||||||
|
*
|
||||||
|
* @param FilePath Path to the audio file to import
|
||||||
|
* @param Format Audio file format (extension)
|
||||||
|
* @param BuffersDetails Information about which buffers to fill
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV"),
|
||||||
|
Category = "RuntimeAudioImporter")
|
||||||
|
void ImportAudioFromFile(const FString& FilePath, TEnumAsByte<EAudioFormat> Format,
|
||||||
|
const FBuffersDetailsStruct& BuffersDetails);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import audio file from the preimported sound asset
|
||||||
|
*
|
||||||
|
* @param PreimportedSoundAssetRef PreimportedSoundAsset object reference. Should contains "BaseAudioDataArray" buffer
|
||||||
|
* @param BuffersDetails Information about which buffers to fill
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3"),
|
||||||
|
Category = "RuntimeAudioImporter")
|
||||||
|
void ImportAudioFromPreimportedSound(UPreimportedSoundAsset* PreimportedSoundAssetRef,
|
||||||
|
const FBuffersDetailsStruct& BuffersDetails);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transcode audio file to USoundWave static object
|
||||||
|
*
|
||||||
|
* @param AudioDataArray Array of Audio byte data
|
||||||
|
* @param Format Audio file format (extension)
|
||||||
|
* @param BuffersDetails Information about which buffers to fill
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV"),
|
||||||
|
Category = "RuntimeAudioImporter")
|
||||||
|
void ImportAudioFromBuffer(const TArray<uint8>& AudioDataArray, const TEnumAsByte<EAudioFormat>& Format,
|
||||||
|
const FBuffersDetailsStruct& BuffersDetails);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy SoundWave object. After the SoundWave is no longer needed, you need to call this function to free memory
|
||||||
|
*
|
||||||
|
* @param ReadySoundWave SoundWave object reference need to be destroyed
|
||||||
|
* @return Whether the destruction of the SoundWave was successful or not
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, meta = (Keywords = "MP3, FLAC, WAV, Destroy"), Category = "RuntimeAudioImporter")
|
||||||
|
static bool DestroySoundWave(USoundWave* ReadySoundWave);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Internal main audio transcoding function
|
||||||
|
*
|
||||||
|
* @param AudioDataArray Array of Audio byte data
|
||||||
|
* @param Format Audio file format (extension)
|
||||||
|
* @param BuffersDetails Information about which buffers to fill
|
||||||
|
*/
|
||||||
|
void ImportAudioFromBuffer_Internal(const TArray<uint8>& AudioDataArray,
|
||||||
|
const TEnumAsByte<EAudioFormat>& Format,
|
||||||
|
const FBuffersDetailsStruct& BuffersDetails);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define SoundWave object reference
|
||||||
|
*
|
||||||
|
* @return Returns a ready USoundWave object
|
||||||
|
*/
|
||||||
|
USoundWave* DefineSoundWave();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill SoundWave basic information (e.g. duration, number of channels, etc)
|
||||||
|
*
|
||||||
|
* @param SoundWaveRef SoundWave object reference
|
||||||
|
*/
|
||||||
|
void FillSoundWaveBasicInfo(USoundWave* SoundWaveRef) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill SoundWave Raw (Wave) data buffer
|
||||||
|
*
|
||||||
|
* @param SoundWaveRef SoundWave object reference
|
||||||
|
*/
|
||||||
|
void FillRawWaveData(USoundWave* SoundWaveRef) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill SoundWave compressed data buffer
|
||||||
|
*
|
||||||
|
* @param SoundWaveRef SoundWave object reference
|
||||||
|
*/
|
||||||
|
void FillCompressedData(USoundWave* SoundWaveRef);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transcode Audio from PCM to 16-bit WAV data
|
||||||
|
*
|
||||||
|
* @return Whether the transcoding was successful or not
|
||||||
|
*/
|
||||||
|
bool TranscodePCMToCompressedData(USoundWave* SoundWaveToGetData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transcode Audio from Audio Data to PCM Data
|
||||||
|
*
|
||||||
|
* @param AudioData Pointer to memory location of Audio byte data
|
||||||
|
* @param AudioDataSize Memory size allocated for Audio byte data
|
||||||
|
* @param Format Format of the audio file (e.g. mp3. flac, etc)
|
||||||
|
* @return Whether the transcoding was successful or not
|
||||||
|
*/
|
||||||
|
bool TranscodeAudioDataArrayToPCMData(const uint8* AudioData, uint32 AudioDataSize,
|
||||||
|
TEnumAsByte<EAudioFormat> Format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transcode Audio from PCM to 16-bit WAV data
|
||||||
|
*
|
||||||
|
* @return Whether the transcoding was successful or not
|
||||||
|
*/
|
||||||
|
bool TranscodePCMToWAVData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Audio Format by extension
|
||||||
|
*
|
||||||
|
* @param FilePath File path, where to find the format (by extension)
|
||||||
|
* @return Returns the found audio format (e.g. mp3. flac, etc) by EAudioFormat Enum
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "RuntimeAudioImporter")
|
||||||
|
static TEnumAsByte<EAudioFormat> GetAudioFormat(const FString& FilePath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate FSoundQualityInfo Enum based on SoundWave and already defined Sample Rate
|
||||||
|
*/
|
||||||
|
FSoundQualityInfo GenerateSoundQualityInfo(USoundWave* SoundWaveToGetData) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the platform-specific format name based on the base format to find the compressed buffer to fill
|
||||||
|
*
|
||||||
|
* @param Format Base format name (it can be "OGG", "LPCM", etc)
|
||||||
|
*/
|
||||||
|
static FName GetPlatformSpecificFormat(const FName Format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio transcoding progress callback
|
||||||
|
* @param Percentage Percentage of importing completion (0-100%)
|
||||||
|
*/
|
||||||
|
void OnProgress_Internal(int32 Percentage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio transcoding finished callback
|
||||||
|
*
|
||||||
|
* @param ReadySoundWave A ready SoundWave object
|
||||||
|
* @param Status TranscodingStatus Enum in case an error occurs
|
||||||
|
*/
|
||||||
|
void OnResult_Internal(USoundWave* ReadySoundWave, const TEnumAsByte<ETranscodingStatus>& Status);
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
struct FSoundQualityInfo;
|
||||||
|
|
||||||
|
class RUNTIMEAUDIOIMPORTER_API FVorbisCompressor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
|
||||||
|
TArray<uint8>& OutBuffer);
|
||||||
|
|
||||||
|
static int32 GetMinimumSizeForInitialChunk(FName Format, const TArray<uint8>& SrcBuffer);
|
||||||
|
|
||||||
|
static TArray<int32> GetChannelOrder(int32 NumChannels);
|
||||||
|
|
||||||
|
static void AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize);
|
||||||
|
|
||||||
|
static void AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize);
|
||||||
|
};
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class RuntimeAudioImporter : ModuleRules
|
||||||
|
{
|
||||||
|
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
//PublicDefinitions.Add("WITH_OGGVORBIS=0");
|
||||||
|
|
||||||
|
PublicDefinitions.AddRange(
|
||||||
|
new string[] {
|
||||||
|
"DR_WAV_IMPLEMENTATION=1",
|
||||||
|
"DR_MP3_IMPLEMENTATION=1",
|
||||||
|
"DR_FLAC_IMPLEMENTATION=1"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"AudioPlatformConfiguration"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
AddEngineThirdPartyPrivateStaticDependencies(Target,
|
||||||
|
"UEOgg",
|
||||||
|
"Vorbis",
|
||||||
|
"libOpus"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PublicDefinitions.Add("WITH_OGGVORBIS");
|
||||||
|
}
|
||||||
|
}
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "PreimportedSoundFactory.h"
|
||||||
|
|
||||||
|
#include "PreimportedSoundAsset.h" //maybe #include "RuntimeAudioImporter/Public/PreimportedSoundAsset.h"
|
||||||
|
#include "Misc/FileHelper.h"
|
||||||
|
|
||||||
|
/** Defining custom logging */
|
||||||
|
DEFINE_LOG_CATEGORY(LogPreimportedSoundFactory)
|
||||||
|
|
||||||
|
#include "ThirdParty/dr_mp3.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replacing standard CPP memory functions (malloc, realloc, free) with memory management functions that are maintained by Epic as the engine evolves.
|
||||||
|
*/
|
||||||
|
void* Unreal_Malloc(size_t sz, void* pUserData)
|
||||||
|
{
|
||||||
|
return FMemory::Malloc(sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Unreal_Realloc(void* p, size_t sz, void* pUserData)
|
||||||
|
{
|
||||||
|
return FMemory::Realloc(p, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unreal_Free(void* p, void* pUserData)
|
||||||
|
{
|
||||||
|
FMemory::Free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
UPreimportedSoundFactory::UPreimportedSoundFactory()
|
||||||
|
{
|
||||||
|
Formats.Add(TEXT("mp3;MP3 Preimported Audio Format"));
|
||||||
|
Formats.Add(TEXT("imp;IMP (the same as MP3) Preimported Audio Format"));
|
||||||
|
SupportedClass = UPreimportedSoundAsset::StaticClass();
|
||||||
|
bCreateNew = false; // turned off for import
|
||||||
|
bEditAfterNew = false; // turned off for import
|
||||||
|
bEditorImport = true;
|
||||||
|
bText = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool UPreimportedSoundFactory::FactoryCanImport(const FString& Filename)
|
||||||
|
{
|
||||||
|
return FPaths::GetExtension(Filename).Equals(TEXT("mp3")) || FPaths::GetExtension(Filename).Equals(TEXT("imp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
UObject* UPreimportedSoundFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName,
|
||||||
|
EObjectFlags Flags, const FString& Filename, const TCHAR* Parms,
|
||||||
|
FFeedbackContext* Warn, bool& bOutOperationCanceled)
|
||||||
|
{
|
||||||
|
UPreimportedSoundAsset* PreimportedSoundAsset = nullptr;
|
||||||
|
|
||||||
|
TArray<uint8> AudioDataArray;
|
||||||
|
|
||||||
|
if (FFileHelper::LoadFileToArray(AudioDataArray, *Filename))
|
||||||
|
{
|
||||||
|
drmp3_allocation_callbacks allocationCallbacksDecoding;
|
||||||
|
allocationCallbacksDecoding.pUserData = nullptr;
|
||||||
|
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
|
||||||
|
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
|
||||||
|
allocationCallbacksDecoding.onFree = Unreal_Free;
|
||||||
|
|
||||||
|
drmp3 mp3;
|
||||||
|
|
||||||
|
if (!drmp3_init_memory(&mp3, AudioDataArray.GetData(), AudioDataArray.Num() - 2, &allocationCallbacksDecoding))
|
||||||
|
{
|
||||||
|
UE_LOG(LogPreimportedSoundFactory, Log, TEXT("The file you are trying to import is not an MP3 file."));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreimportedSoundAsset = NewObject<UPreimportedSoundAsset>(InParent, InClass, InName, Flags);
|
||||||
|
PreimportedSoundAsset->AudioDataArray = AudioDataArray;
|
||||||
|
PreimportedSoundAsset->SourceFilePath = Filename;
|
||||||
|
|
||||||
|
PreimportedSoundAsset->SoundDuration = ConvertSecToFormattedDuration(
|
||||||
|
static_cast<int32>(drmp3_get_pcm_frame_count(&mp3)) / mp3.sampleRate);
|
||||||
|
PreimportedSoundAsset->NumberOfChannels = mp3.channels;
|
||||||
|
PreimportedSoundAsset->SampleRate = mp3.sampleRate;
|
||||||
|
|
||||||
|
drmp3_uninit(&mp3);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogPreimportedSoundFactory, Log, TEXT("Unable to read the audio file. Check file permissions."));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bOutOperationCanceled = false;
|
||||||
|
|
||||||
|
return PreimportedSoundAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UPreimportedSoundFactory::ConvertSecToFormattedDuration(int32 Seconds)
|
||||||
|
{
|
||||||
|
FString FinalString;
|
||||||
|
|
||||||
|
const int32 NewHours = Seconds / 3600;
|
||||||
|
if (NewHours > 0)
|
||||||
|
{
|
||||||
|
FinalString += ((NewHours < 10) ? TEXT("0") + FString::FromInt(NewHours) : FString::FromInt(NewHours)) +
|
||||||
|
TEXT(":");
|
||||||
|
}
|
||||||
|
|
||||||
|
Seconds = Seconds % 3600;
|
||||||
|
|
||||||
|
const int32 NewMinutes = Seconds / 60;
|
||||||
|
if (NewMinutes > 0)
|
||||||
|
{
|
||||||
|
FinalString += ((NewMinutes < 10) ? TEXT("0") + FString::FromInt(NewMinutes) : FString::FromInt(NewMinutes)) +
|
||||||
|
TEXT(":");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 NewSeconds = Seconds % 60;
|
||||||
|
if (NewSeconds > 0)
|
||||||
|
{
|
||||||
|
FinalString += (NewSeconds < 10) ? TEXT("0") + FString::FromInt(NewSeconds) : FString::FromInt(NewSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FinalString;
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#include "RuntimeAudioImporterEditor.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterEditorModule"
|
||||||
|
|
||||||
|
void FRuntimeAudioImporterEditorModule::StartupModule()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FRuntimeAudioImporterEditorModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FRuntimeAudioImporterEditorModule, RuntimeAudioImporterEditor)
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Factories/Factory.h"
|
||||||
|
#include "PreimportedSoundFactory.generated.h"
|
||||||
|
|
||||||
|
/** Declaring custom logging */
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogPreimportedSoundFactory, Error, All);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for pre-importing audio files. Only MP3 is available for pre-import because it takes up the least amount of memory.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class RUNTIMEAUDIOIMPORTEREDITOR_API UPreimportedSoundFactory : public UFactory
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPreimportedSoundFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the audio file can be imported.
|
||||||
|
*
|
||||||
|
* @param Filename The name of the importing audio file
|
||||||
|
* @note Only MP3 is available for pre-import because it takes up the least amount of memory.
|
||||||
|
*/
|
||||||
|
virtual bool FactoryCanImport(const FString& Filename) override;
|
||||||
|
|
||||||
|
virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags,
|
||||||
|
const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn,
|
||||||
|
bool& bOutOperationCanceled) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert sound duration from seconds to formatted string (hh:mm:ss)
|
||||||
|
*
|
||||||
|
* @param Seconds Audio duration in seconds
|
||||||
|
*/
|
||||||
|
static FString ConvertSecToFormattedDuration(int32 Seconds);
|
||||||
|
};
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// Respirant 2020.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
class FRuntimeAudioImporterEditorModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
// Georgy Treshchev 2021.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class RuntimeAudioImporterEditor : ModuleRules
|
||||||
|
{
|
||||||
|
public RuntimeAudioImporterEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PublicDefinitions.AddRange(
|
||||||
|
new string[] {
|
||||||
|
"DR_MP3_IMPLEMENTATION=1"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
"RuntimeAudioImporter"
|
||||||
|
// ... add other public dependencies that you statically link with here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"UnrealEd"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+12214
File diff suppressed because it is too large
Load Diff
+4750
File diff suppressed because it is too large
Load Diff
+6407
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class AudioImporterTestTarget : TargetRules
|
||||||
|
{
|
||||||
|
public AudioImporterTestTarget( TargetInfo Target) : base(Target)
|
||||||
|
{
|
||||||
|
Type = TargetType.Game;
|
||||||
|
DefaultBuildSettings = BuildSettingsVersion.V2;
|
||||||
|
ExtraModuleNames.AddRange( new string[] { "AudioImporterTest" } );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class AudioImporterTest : ModuleRules
|
||||||
|
{
|
||||||
|
public AudioImporterTest(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(new string[] { });
|
||||||
|
|
||||||
|
// Uncomment if you are using Slate UI
|
||||||
|
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||||
|
|
||||||
|
// Uncomment if you are using online features
|
||||||
|
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
||||||
|
|
||||||
|
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "AudioImporterTest.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, AudioImporterTest, "AudioImporterTest" );
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "AudioImporterTestGameModeBase.h"
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/GameModeBase.h"
|
||||||
|
#include "AudioImporterTestGameModeBase.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class AUDIOIMPORTERTEST_API AAudioImporterTestGameModeBase : public AGameModeBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class AudioImporterTestEditorTarget : TargetRules
|
||||||
|
{
|
||||||
|
public AudioImporterTestEditorTarget( TargetInfo Target) : base(Target)
|
||||||
|
{
|
||||||
|
Type = TargetType.Editor;
|
||||||
|
DefaultBuildSettings = BuildSettingsVersion.V2;
|
||||||
|
ExtraModuleNames.AddRange( new string[] { "AudioImporterTest" } );
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user