feat: Add audio import tool and standardize audio import settings
- Add editor tool to batch apply audio import settings based on configuration files - Standardize sample rates: SFX/Voice to 22050 Hz, Music to 44100 Hz - Set quality: SFX/Voice to 0.5, Music to 0.13 - Adjust load types based on audio duration - Set project sample rate to 48000 Hz - Update preload settings for voice and music files
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OCES.Audio
|
||||
{
|
||||
|
||||
public static class AudioImportTool
|
||||
{
|
||||
const string k_audioResourcePath = "Audios";
|
||||
const string k_audioConfigPath = "AudioData";
|
||||
|
||||
static string AudioAbsolutePath
|
||||
{
|
||||
get { return Path.Combine(Application.dataPath, "Resources", k_audioResourcePath); }
|
||||
}
|
||||
static string ConfigAbsolutePath
|
||||
{
|
||||
get { return Path.Combine(Application.dataPath, "Resources", k_audioConfigPath); }
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Audio/Apply Audio Import Settings")]
|
||||
public static void RunFromMenu()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog(
|
||||
"Apply Audio Import Settings",
|
||||
$"将对 {k_audioResourcePath} 下所有文件重新应用导入设置,确认继续?",
|
||||
"确认", "取消"))
|
||||
{
|
||||
Run();
|
||||
}
|
||||
}
|
||||
|
||||
// Unity -batchmode -executeMethod OCES.Audio.Editor.AudioImportTool.RunCLI
|
||||
public static void RunCli()
|
||||
{
|
||||
Debug.Log("[AudioImportTool] CLI 模式启动");
|
||||
Run();
|
||||
}
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
// 1. 加载配置表
|
||||
var audioObjectConfig = AudioConfigLoader.Load<AudioObjectConfig>(ConfigAbsolutePath, "AudioObject.bytes");
|
||||
var musicSegmentConfig = AudioConfigLoader.Load<MusicSegmentConfig>(ConfigAbsolutePath, "MusicSegment.bytes");
|
||||
if (audioObjectConfig == null || musicSegmentConfig == null)
|
||||
{
|
||||
Debug.LogError("[AudioImportTool] 配置表加载失败,终止");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 扫描文件
|
||||
List<string> supportedExtensions = new() { ".wav", ".ogg" };
|
||||
DirectoryInfo dir = new(AudioAbsolutePath);
|
||||
FileInfo[] files = dir.GetFiles().Where(f => supportedExtensions.Contains(f.Extension.ToLower())).ToArray();
|
||||
int total = files.Length, processed = 0, failed = 0;
|
||||
|
||||
AssetDatabase.StartAssetEditing(); // 批量操作,暂停自动刷新
|
||||
try
|
||||
{
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
// 转成 Assets/... 相对路径供 AssetDatabase 使用
|
||||
string assetPath = "Assets" + file.FullName
|
||||
.Replace(Application.dataPath, "")
|
||||
.Replace("\\", "/");
|
||||
|
||||
EditorUtility.DisplayProgressBar(
|
||||
"Audio Import Tool",
|
||||
$"处理: {file.Name} ({processed}/{total})",
|
||||
(float)processed / total);
|
||||
|
||||
bool ok = ProcessFile(assetPath, file.Name, audioObjectConfig, musicSegmentConfig);
|
||||
if (ok) processed++;
|
||||
else failed++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
AssetDatabase.StopAssetEditing();
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
Debug.Log($"[AudioImportTool] 完成:{processed} 成功,{failed} 失败,共 {total} 个文件");
|
||||
}
|
||||
|
||||
static bool ProcessFile(
|
||||
string assetPath, string fileName,
|
||||
AudioObjectConfig audioObjectConfig, MusicSegmentConfig musicSegmentConfig)
|
||||
{
|
||||
AudioImporter importer = AssetImporter.GetAtPath(assetPath) as AudioImporter;
|
||||
if (!importer)
|
||||
{
|
||||
Debug.LogWarning($"[AudioImportTool] 无法获取 AudioImporter: {assetPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioClip clip = AssetDatabase.LoadAssetAtPath<AudioClip>(assetPath);
|
||||
if (!clip)
|
||||
{
|
||||
Debug.LogWarning($"[AudioImportTool] 无法加载 AudioClip: {assetPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
float duration = clip.length;
|
||||
string fileNameNoExt = Path.GetFileNameWithoutExtension(fileName);
|
||||
AudioCategory category = ClassifyAudio(fileNameNoExt, audioObjectConfig, musicSegmentConfig, out AudioObject audioObject);
|
||||
|
||||
importer.ClearSampleSettingOverride("Android");
|
||||
importer.ClearSampleSettingOverride("iOS");
|
||||
|
||||
AudioImporterSampleSettings settings = importer.defaultSampleSettings;
|
||||
settings.compressionFormat = AudioCompressionFormat.Vorbis;
|
||||
|
||||
settings.sampleRateSetting = AudioSampleRateSetting.OverrideSampleRate;
|
||||
settings.preloadAudioData = audioObject?.Haptic != 0;
|
||||
switch (category)
|
||||
{
|
||||
case AudioCategory.Music:
|
||||
settings.sampleRateOverride = 44100;
|
||||
settings.quality = 0.13f;
|
||||
settings.loadType = duration switch
|
||||
{
|
||||
<= 5 => AudioClipLoadType.DecompressOnLoad,
|
||||
>= 15 => AudioClipLoadType.Streaming,
|
||||
_ => AudioClipLoadType.CompressedInMemory,
|
||||
};
|
||||
break;
|
||||
case AudioCategory.Voice:
|
||||
case AudioCategory.SFX:
|
||||
default:
|
||||
settings.sampleRateOverride = 22050; // 音效2022.3.62f3没有32kHz这一档,要是有的话这一档其实最合适
|
||||
settings.quality = 0.5f;
|
||||
settings.loadType = duration switch
|
||||
{
|
||||
>= 15 => AudioClipLoadType.Streaming,
|
||||
_ => AudioClipLoadType.DecompressOnLoad,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
importer.defaultSampleSettings = settings;
|
||||
importer.forceToMono = false;
|
||||
importer.SaveAndReimport();
|
||||
return true;
|
||||
}
|
||||
|
||||
enum AudioCategory {Music, SFX, Voice }
|
||||
|
||||
static AudioCategory ClassifyAudio(
|
||||
string fileName, // 不含扩展名
|
||||
AudioObjectConfig audioObjectConfig,
|
||||
MusicSegmentConfig musicSegmentConfig,
|
||||
out AudioObject matchedObject)
|
||||
{
|
||||
matchedObject = null;
|
||||
|
||||
// 1. MusicSegment 表命中 → Music
|
||||
MusicSegment segment = musicSegmentConfig.MusicSegmentList()
|
||||
.FirstOrDefault(musicSegment => musicSegment.Name == fileName);
|
||||
if (segment != null)
|
||||
return AudioCategory.Music;
|
||||
|
||||
// 2. AudioObject 表命中 → 按 MixingType
|
||||
AudioObject audioObject = audioObjectConfig.AudioObjectList()
|
||||
.FirstOrDefault(audioObject => audioObject.Name != null && audioObject.Name.Contains(fileName));
|
||||
if (audioObject != null)
|
||||
{
|
||||
matchedObject = audioObject;
|
||||
return audioObject.MixingType == MixingType.Voice
|
||||
? AudioCategory.Voice
|
||||
: AudioCategory.SFX; // SFX + Accent 都走 SFX 分支
|
||||
}
|
||||
|
||||
// 3. 文件名 fallback
|
||||
string lower = fileName.ToLowerInvariant();
|
||||
if (lower.Contains("music") || lower.Contains("bgm"))
|
||||
return AudioCategory.Music;
|
||||
if (lower.Contains("sfx"))
|
||||
return AudioCategory.SFX;
|
||||
if (lower.Contains("voice"))
|
||||
return AudioCategory.Voice;
|
||||
|
||||
// 4. 完全兜底
|
||||
Debug.LogWarning($"[AudioImportTool] 无法分类: {fileName},默认当作 SFX 处理");
|
||||
return AudioCategory.SFX;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 114d56912b8874446b70f4c83af327b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user