From b2265578e2ba08e587cbfa72bddd9ec4aebfab75 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Mon, 20 Apr 2026 03:04:32 +0300 Subject: [PATCH] Improve AudioClip support --- AssetStudioCLI/ParallelExporter.cs | 59 ++--- AssetStudioGUI/AssetStudioGUIForm.cs | 131 ++-------- AssetStudioGUI/ParallelExport.cs | 59 ++--- .../Audio/AudioClipConverter.cs | 242 +++++++++++------- .../Audio/AudioClipExtension.cs | 59 +++-- 5 files changed, 272 insertions(+), 278 deletions(-) diff --git a/AssetStudioCLI/ParallelExporter.cs b/AssetStudioCLI/ParallelExporter.cs index c8eb62f..584c7b4 100644 --- a/AssetStudioCLI/ParallelExporter.cs +++ b/AssetStudioCLI/ParallelExporter.cs @@ -103,18 +103,19 @@ namespace AssetStudioCLI { debugLog = string.Empty; var m_AudioClip = (AudioClip)item.Asset; - var m_AudioData = BigArrayPool.Shared.Rent(m_AudioClip.m_AudioData.Size); + var audioBuff = BigArrayPool.Shared.Rent(m_AudioClip.m_AudioData.Size); try { string exportFullPath; - var dataLen = m_AudioClip.m_AudioData.GetData(m_AudioData); + var dataLen = m_AudioClip.m_AudioData.GetData(audioBuff); if (dataLen <= 0) { Logger.Error($"Export error. \"{item.Text}\": AudioData was not found"); return false; } - var converter = new AudioClipConverter(m_AudioClip); - if (CLIOptions.o_audioFormat.Value != AudioFormat.None && (converter.IsSupport || converter.IsLegacy)) + var isWav = m_AudioClip.m_Type == FMODSoundType.WAV || m_AudioClip.IsLegacyRawWav(); + var converter = new AudioClipConverter(m_AudioClip, audioBuff); + if (CLIOptions.o_audioFormat.Value != AudioFormat.None && !isWav && converter.IsSupport) { if (!TryExportFile(exportPath, item, ".wav", out exportFullPath)) return false; @@ -125,12 +126,9 @@ namespace AssetStudioCLI debugLog += GenerateAudioClipInfo(m_AudioClip); } - var buffer = converter.IsLegacy - ? converter.RawAudioClipToWav(ref debugLog) - : converter.ConvertToWav(m_AudioData, ref debugLog); - if (buffer == null) + if (!converter.TryConvertToWav(out var buffer, ref debugLog)) { - Logger.Error($"{debugLog}Export error. \"{item.Text}\": Failed to convert fmod audio to Wav"); + Logger.Error($"{debugLog}Export error. \"{item.Text}\": Failed to convert audio to wav"); return false; } File.WriteAllBytes(exportFullPath, buffer); @@ -142,12 +140,21 @@ namespace AssetStudioCLI if (CLIOptions.o_logLevel.Value <= LoggerEvent.Debug) { - debugLog += $"Exporting non-fmod {item.TypeString} \"{m_AudioClip.m_Name}\"..\n"; + debugLog += $"Exporting {item.TypeString} \"{m_AudioClip.m_Name}\"..\n"; debugLog += GenerateAudioClipInfo(m_AudioClip); } + using (var file = File.OpenWrite(exportFullPath)) { - file.Write(m_AudioData, 0, m_AudioClip.m_AudioData.Size); + if (m_AudioClip.IsLegacyRawWav()) + { + var buffer = converter.WrapWavData(ref debugLog); + file.Write(buffer, 0, buffer.Length); + } + else + { + file.Write(audioBuff, 0, m_AudioClip.m_AudioData.Size); + } } } debugLog += $"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\""; @@ -155,33 +162,23 @@ namespace AssetStudioCLI } finally { - BigArrayPool.Shared.Return(m_AudioData, clearArray: true); + BigArrayPool.Shared.Return(audioBuff, clearArray: true); } } private static string GenerateAudioClipInfo(AudioClip m_AudioClip) { var sb = new StringBuilder(); - if (m_AudioClip.version >= (2, 6)) - { - sb.AppendLine(m_AudioClip.version < 5 - ? $"AudioClip type: {m_AudioClip.m_Type}" - : $"AudioClip compression format: {m_AudioClip.m_CompressionFormat}"); - if (m_AudioClip.version >= 5) - { - sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}"); - sb.AppendLine($"AudioClip sample rate: {m_AudioClip.m_Frequency}"); - sb.AppendLine($"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}"); - } - } - else - { - var isRawWav = m_AudioClip.m_Format != 0x05; - sb.AppendLine($"Is raw wav data: {isRawWav}"); - if (isRawWav) - sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}"); + sb.AppendLine($"AudioClip compression format: {m_AudioClip.GetCompressionFormatString()}"); + if (m_AudioClip.m_Length > 0) + sb.AppendLine($"AudioClip length: {m_AudioClip.m_Length:0.0##}"); + if (m_AudioClip.m_Frequency > 0) sb.AppendLine($"AudioClip sample rate: {m_AudioClip.m_Frequency}"); - } + if (m_AudioClip.m_Channels > 0) + sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}"); + if (m_AudioClip.version >= 5) + sb.AppendLine($"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}"); + return sb.ToString(); } diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index aec3bbb..74ed895 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -1047,91 +1047,13 @@ namespace AssetStudioGUI private void PreviewAudioClip(AssetItem assetItem, AudioClip m_AudioClip) { //Info - assetItem.InfoText = "Compression format: "; - if (m_AudioClip.version < 5) + assetItem.InfoText = $"Compression format: {m_AudioClip.GetCompressionFormatString()}"; + if (m_AudioClip.m_AudioData.Size <= 0) { - switch (m_AudioClip.m_Type) - { - case FMODSoundType.AAC: - assetItem.InfoText += "AAC"; - break; - case FMODSoundType.AIFF: - assetItem.InfoText += "AIFF"; - break; - case FMODSoundType.IT: - assetItem.InfoText += "Impulse tracker"; - break; - case FMODSoundType.MOD: - assetItem.InfoText += "Protracker / Fasttracker MOD"; - break; - case FMODSoundType.MPEG: - assetItem.InfoText += "MP2/MP3 MPEG"; - break; - case FMODSoundType.OGGVORBIS: - assetItem.InfoText += "Ogg vorbis"; - break; - case FMODSoundType.S3M: - assetItem.InfoText += "ScreamTracker 3"; - break; - case FMODSoundType.WAV: - assetItem.InfoText += "Microsoft WAV"; - break; - case FMODSoundType.XM: - assetItem.InfoText += "FastTracker 2 XM"; - break; - case FMODSoundType.XMA: - assetItem.InfoText += "Xbox360 XMA"; - break; - case FMODSoundType.VAG: - assetItem.InfoText += "PlayStation Portable ADPCM"; - break; - case FMODSoundType.AUDIOQUEUE: - assetItem.InfoText += "iPhone"; - break; - default: - assetItem.InfoText += $"Unknown ({m_AudioClip.m_Type})"; - break; - } - } - else - { - switch (m_AudioClip.m_CompressionFormat) - { - case AudioCompressionFormat.PCM: - assetItem.InfoText += "PCM"; - break; - case AudioCompressionFormat.Vorbis: - assetItem.InfoText += "Vorbis"; - break; - case AudioCompressionFormat.ADPCM: - assetItem.InfoText += "ADPCM"; - break; - case AudioCompressionFormat.MP3: - assetItem.InfoText += "MP3"; - break; - case AudioCompressionFormat.PSMVAG: - assetItem.InfoText += "PlayStation Portable ADPCM"; - break; - case AudioCompressionFormat.HEVAG: - assetItem.InfoText += "PSVita ADPCM"; - break; - case AudioCompressionFormat.XMA: - assetItem.InfoText += "Xbox360 XMA"; - break; - case AudioCompressionFormat.AAC: - assetItem.InfoText += "AAC"; - break; - case AudioCompressionFormat.GCADPCM: - assetItem.InfoText += "Nintendo 3DS/Wii DSP"; - break; - case AudioCompressionFormat.ATRAC9: - assetItem.InfoText += "PSVita ATRAC9"; - break; - default: - assetItem.InfoText += "Unknown"; - break; - } + Logger.Warning("AudioData size shouldn't be 0"); + return; } + soundBuff = BigArrayPool.Shared.Rent(m_AudioClip.m_AudioData.Size); var dataLen = m_AudioClip.m_AudioData.GetData(soundBuff); if (dataLen <= 0) @@ -1140,30 +1062,33 @@ namespace AssetStudioGUI var exinfo = new FMOD.CREATESOUNDEXINFO(); exinfo.cbsize = Marshal.SizeOf(exinfo); exinfo.length = (uint)m_AudioClip.m_Size; + var mode = FMOD.MODE.OPENMEMORY | FMOD.MODE.LOWMEM | FMOD.MODE.IGNORETAGS | FMOD.MODE.ACCURATETIME | FMOD.MODE.MPEGSEARCH | loopMode; + if (m_AudioClip.IsLegacyRawWav()) + { + exinfo.format = (LegacyAudioType)m_AudioClip.m_Format == LegacyAudioType.RawWavU8Mono ? FMOD.SOUND_FORMAT.PCM8 : FMOD.SOUND_FORMAT.PCM16; + exinfo.numchannels = m_AudioClip.m_Channels; + exinfo.defaultfrequency = m_AudioClip.m_Frequency; + mode |= FMOD.MODE.OPENRAW; + if (m_AudioClip.m_AudioData.Endian == EndianType.BigEndian && exinfo.format == FMOD.SOUND_FORMAT.PCM16) + { + AudioClipConverter.SwapPcm16Endian(soundBuff.AsSpan(0, m_AudioClip.m_AudioData.Size)); + } + } - var result = system.createStream(soundBuff, FMOD.MODE.OPENMEMORY | FMOD.MODE.LOWMEM | FMOD.MODE.IGNORETAGS | FMOD.MODE.ACCURATETIME | loopMode, ref exinfo, out sound); + var result = system.createStream(soundBuff, mode, ref exinfo, out sound); if (result != FMOD.RESULT.OK) { - if (m_AudioClip.version < (2, 6) || m_AudioClip.version >= 5) - { - var legacyFormat = m_AudioClip.IsLegacyConvertSupport() - ? "\nLegacy audio format: Raw wav data" - : ""; - var channels = m_AudioClip.m_Channels > 0 - ? $"\nChannel count: {m_AudioClip.m_Channels}" - : ""; - var bits = m_AudioClip.version >= 5 - ? $"\nBit depth: {m_AudioClip.m_BitsPerSample}" - : ""; - assetItem.InfoText += - legacyFormat + - $"\nLength: {m_AudioClip.m_Length:0.0##}" + - $"\nSample rate: {m_AudioClip.m_Frequency}" + - channels + - bits; - } + if (m_AudioClip.m_Length > 0) + assetItem.InfoText += $"\nLength: {m_AudioClip.m_Length:0.0##}"; + if (m_AudioClip.m_Frequency > 0) + assetItem.InfoText += $"\nSample rate: {m_AudioClip.m_Frequency}"; + if (m_AudioClip.m_Channels > 0) + assetItem.InfoText += $"\nChannel count: {m_AudioClip.m_Channels}"; + if (m_AudioClip.version >= 5) + assetItem.InfoText += $"\nBit depth: {m_AudioClip.m_BitsPerSample}"; + var errorMsg = result == FMOD.RESULT.ERR_VERSION - ? "Unsupported version of fmod sound. Try to export raw and convert with an external tool instead." + ? "Unsupported version of FMOD sound. Try to export and convert with an external tool instead." : $"Preview not available, try to export instead. {FMOD.Error.String(result)}"; StatusStripUpdate(errorMsg); FMODreset(); diff --git a/AssetStudioGUI/ParallelExport.cs b/AssetStudioGUI/ParallelExport.cs index 0461622..212bffe 100644 --- a/AssetStudioGUI/ParallelExport.cs +++ b/AssetStudioGUI/ParallelExport.cs @@ -102,18 +102,19 @@ namespace AssetStudioGUI { debugLog = string.Empty; var m_AudioClip = (AudioClip)item.Asset; - var m_AudioData = BigArrayPool.Shared.Rent(m_AudioClip.m_AudioData.Size); + var audioBuff = BigArrayPool.Shared.Rent(m_AudioClip.m_AudioData.Size); try { string exportFullPath; - var dataLen = m_AudioClip.m_AudioData.GetData(m_AudioData); + var dataLen = m_AudioClip.m_AudioData.GetData(audioBuff); if (dataLen <= 0) { Logger.Warning($"Failed to export \"{item.Text}\": AudioData was not found"); return false; } - var converter = new AudioClipConverter(m_AudioClip); - if (Properties.Settings.Default.convertAudio && (converter.IsSupport || converter.IsLegacy)) + var isWav = m_AudioClip.m_Type == FMODSoundType.WAV || m_AudioClip.IsLegacyRawWav(); + var converter = new AudioClipConverter(m_AudioClip, audioBuff); + if (Properties.Settings.Default.convertAudio && !isWav && converter.IsSupport) { if (!TryExportFile(exportPath, item, ".wav", out exportFullPath)) return false; @@ -124,12 +125,9 @@ namespace AssetStudioGUI debugLog += GenerateAudioClipInfo(m_AudioClip); } - var buffer = converter.IsLegacy - ? converter.RawAudioClipToWav(ref debugLog) - : converter.ConvertToWav(m_AudioData, ref debugLog); - if (buffer == null) + if (!converter.TryConvertToWav(out var buffer, ref debugLog)) { - Logger.Warning($"{debugLog}Failed to export \"{item.Text}\": Failed to convert fmod audio to Wav"); + Logger.Warning($"{debugLog}Failed to export \"{item.Text}\": Failed to convert audio to wav"); return false; } File.WriteAllBytes(exportFullPath, buffer); @@ -141,12 +139,21 @@ namespace AssetStudioGUI if (GUILogger.ShowDebugMessage) { - debugLog += $"Exporting non-fmod {item.TypeString} \"{m_AudioClip.m_Name}\"..\n"; + debugLog += $"Exporting {item.TypeString} \"{m_AudioClip.m_Name}\"..\n"; debugLog += GenerateAudioClipInfo(m_AudioClip); } + using (var file = File.OpenWrite(exportFullPath)) { - file.Write(m_AudioData, 0, m_AudioClip.m_AudioData.Size); + if (m_AudioClip.IsLegacyRawWav()) + { + var buffer = converter.WrapWavData(ref debugLog); + file.Write(buffer, 0, buffer.Length); + } + else + { + file.Write(audioBuff, 0, m_AudioClip.m_AudioData.Size); + } } } debugLog += $"{item.TypeString} \"{item.Text}\" exported to \"{exportFullPath}\""; @@ -154,33 +161,23 @@ namespace AssetStudioGUI } finally { - BigArrayPool.Shared.Return(m_AudioData, clearArray: true); + BigArrayPool.Shared.Return(audioBuff, clearArray: true); } } private static string GenerateAudioClipInfo(AudioClip m_AudioClip) { var sb = new StringBuilder(); - if (m_AudioClip.version >= (2, 6)) - { - sb.AppendLine(m_AudioClip.version < 5 - ? $"AudioClip type: {m_AudioClip.m_Type}" - : $"AudioClip compression format: {m_AudioClip.m_CompressionFormat}"); - if (m_AudioClip.version >= 5) - { - sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}"); - sb.AppendLine($"AudioClip sample rate: {m_AudioClip.m_Frequency}"); - sb.AppendLine($"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}"); - } - } - else - { - var isRawWav = m_AudioClip.m_Format != 0x05; - sb.AppendLine($"Is raw wav data: {isRawWav}"); - if (isRawWav) - sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}"); + sb.AppendLine($"AudioClip compression format: {m_AudioClip.GetCompressionFormatString()}"); + if (m_AudioClip.m_Length > 0) + sb.AppendLine($"AudioClip length: {m_AudioClip.m_Length:0.0##}"); + if (m_AudioClip.m_Frequency > 0) sb.AppendLine($"AudioClip sample rate: {m_AudioClip.m_Frequency}"); - } + if (m_AudioClip.m_Channels > 0) + sb.AppendLine($"AudioClip channel count: {m_AudioClip.m_Channels}"); + if (m_AudioClip.version >= 5) + sb.AppendLine($"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}"); + return sb.ToString(); } diff --git a/AssetStudioUtility/Audio/AudioClipConverter.cs b/AssetStudioUtility/Audio/AudioClipConverter.cs index bbacf10..c2a122b 100644 --- a/AssetStudioUtility/Audio/AudioClipConverter.cs +++ b/AssetStudioUtility/Audio/AudioClipConverter.cs @@ -1,16 +1,24 @@ -using System; +using FMOD; +using System; using System.Runtime.InteropServices; -using FMOD; using WavHelper; namespace AssetStudio { public sealed class AudioClipConverter { - private AudioClip m_AudioClip; private static FMOD.System system; - public bool IsSupport => m_AudioClip.IsConvertSupport(); - public bool IsLegacy => m_AudioClip.IsLegacyConvertSupport(); + private static byte[] oggMagic = { 0x4F, 0x67, 0x67, 0x53 }; //OggS + private static byte[] fsb5Magic = { 0x46, 0x53, 0x42, 0x35 }; //FSB5 + private static byte[] fsb4Magic = { 0x46, 0x53, 0x42, 0x34 }; //FSB4 + + private readonly AudioClip m_AudioClip; + private byte[] audioBuff; + private CREATESOUNDEXINFO exinfo; + private readonly SOUND_TYPE fmodSoundType; + private readonly bool isLegacyRawWav; + + public bool IsSupport { get; } static AudioClipConverter() { @@ -26,42 +34,71 @@ namespace AssetStudio } } - public AudioClipConverter(AudioClip audioClip) + public AudioClipConverter(AudioClip audioClip, byte[] audioBuff) { m_AudioClip = audioClip; - } + this.audioBuff = audioBuff; - public byte[] ConvertToWav(byte[] m_AudioData, ref string debugLog) - { - var exinfo = new CREATESOUNDEXINFO(); + exinfo = new CREATESOUNDEXINFO(); exinfo.cbsize = Marshal.SizeOf(exinfo); exinfo.length = (uint)m_AudioClip.m_Size; - var result = system.createSound(m_AudioData, MODE.OPENMEMORY | MODE.LOWMEM | MODE.ACCURATETIME, ref exinfo, out var sound); - if (ErrorCheck(result, ref debugLog)) - return null; + var mode = MODE.OPENMEMORY | MODE.LOWMEM | MODE.MPEGSEARCH; + if (m_AudioClip.IsLegacyRawWav()) + { + exinfo.format = (LegacyAudioType)m_AudioClip.m_Format == LegacyAudioType.RawWavU8Mono ? SOUND_FORMAT.PCM8 : SOUND_FORMAT.PCM16; + exinfo.numchannels = m_AudioClip.m_Channels; + exinfo.defaultfrequency = m_AudioClip.m_Frequency; + mode |= MODE.OPENRAW; + isLegacyRawWav = true; + } + var result = system.createStream(audioBuff, mode, ref exinfo, out var sound); + if (result != RESULT.OK) + return; + _ = sound.getFormat(out fmodSoundType, out var soundFormat, out _, out _); + sound.release(); + sound.clearHandle(); + IsSupport = soundFormat > SOUND_FORMAT.NONE && soundFormat < SOUND_FORMAT.BITSTREAM; + } + + public bool TryConvertToWav(out byte[] wavBuffer, ref string debugLog) + { + wavBuffer = null; + if (!IsSupport) + return false; + + var mode = MODE.OPENMEMORY | MODE.LOWMEM | MODE.ACCURATETIME | MODE.MPEGSEARCH; + if (isLegacyRawWav) + { + mode |= MODE.OPENRAW; + if (m_AudioClip.m_AudioData.Endian == EndianType.BigEndian && exinfo.format == SOUND_FORMAT.PCM16) + { + SwapPcm16Endian(audioBuff.AsSpan(0, (int)m_AudioClip.m_Size)); + } + } + var result = system.createSound(audioBuff, mode, ref exinfo, out var sound); + if (ErrorCheck(result, ref debugLog)) + return false; result = sound.getNumSubSounds(out var numsubsounds); if (ErrorCheck(result, ref debugLog)) - return null; - - byte[] buff; + return false; if (numsubsounds > 0) { result = sound.getSubSound(0, out var subsound); if (ErrorCheck(result, ref debugLog)) - return null; - buff = SoundToWav(subsound, ref debugLog); + return false; + wavBuffer = SoundToWav(subsound, ref debugLog); subsound.release(); subsound.clearHandle(); } else { - buff = SoundToWav(sound, ref debugLog); + wavBuffer = SoundToWav(sound, ref debugLog); } - sound.release(); sound.clearHandle(); - return buff; + + return true; } private byte[] SoundToWav(Sound sound, ref string debugLog) @@ -99,47 +136,39 @@ namespace AssetStudio result = sound.@lock(0, length, out var ptr1, out var ptr2, out var len1, out var len2); if (ErrorCheck(result, ref debugLog)) return null; - var wavDataLength = convertToPcm16 - ? len1 / 2 - : len1; - var buffer = new byte[wavDataLength + 44]; - if (convertToPcm16) + var wavDataLength = convertToPcm16 ? len1 / 2 : len1; + var wavBuffer = new byte[wavDataLength + 44]; + if (bits == 8) { - ReadAsPcm16(ptr1, buffer, 44, len1, ref debugLog); + ReadAsPcm8U(ptr1, wavBuffer, 44, len1, ref debugLog); + } + else if (convertToPcm16) + { + ReadAsPcm16(ptr1, wavBuffer, 44, len1, ref debugLog); } else { - Marshal.Copy(ptr1, buffer, 44, (int)len1); + Marshal.Copy(ptr1, wavBuffer, 44, (int)len1); } result = sound.unlock(ptr1, ptr2, len1, len2); if (ErrorCheck(result, ref debugLog)) return null; //添加wav头 var wavHeader = new WavHeader(wavDataLength, audioFormat, channels, (uint)frequency, bits); - wavHeader.WriteToArray(buffer); - return buffer; + wavHeader.WriteToArray(wavBuffer); + return wavBuffer; } - public byte[] RawAudioClipToWav(ref string debugLog) + private static void ReadAsPcm8U(IntPtr srcPtr, byte[] wavBuffer, int offset, uint pcmDataLen, ref string debugLog) { - var audioSize = (uint)m_AudioClip.m_Size; - var channels = m_AudioClip.m_Channels; - var sampleRate = m_AudioClip.m_Frequency; - var audioFormat = WavAudioFormat.PCM; - var bits = 16; - - debugLog += "[Legacy wav converter] Generating wav header..\n"; - var buffer = new byte[audioSize + 44]; - var dataLen = m_AudioClip.m_AudioData.GetData(buffer, 44); - if (dataLen > 0) + for (var i = 0; i < pcmDataLen; i++) { - var wavHeader = new WavHeader(audioSize, audioFormat, channels, (uint)sampleRate, bits); - wavHeader.WriteToArray(buffer); + wavBuffer[offset++] = (byte)(Marshal.ReadByte(srcPtr, i) - 0x80); } - return buffer; + debugLog += "Finished FMOD_PCM8_SIGNED -> PCM8_UNSIGNED converting\n"; } - private static void ReadAsPcm16(IntPtr srcPtr, byte[] destBuffer, int offset, uint pcmDataLen, ref string debugLog) + private static void ReadAsPcm16(IntPtr srcPtr, byte[] wavBuffer, int offset, uint pcmDataLen, ref string debugLog) { var pcmFloatVal = new byte[4]; for (var i = 0; i < pcmDataLen; i += 4) @@ -149,11 +178,46 @@ namespace AssetStudio pcmFloatVal[j] = Marshal.ReadByte(srcPtr, i + j); } var pcm16Val = (short)MathHelper.Clamp(BitConverter.ToSingle(pcmFloatVal, 0) * short.MaxValue, short.MinValue, short.MaxValue); - destBuffer[offset] = (byte)(pcm16Val & 255); - destBuffer[offset + 1] = (byte)(pcm16Val >> 8); + wavBuffer[offset] = (byte)(pcm16Val & 255); + wavBuffer[offset + 1] = (byte)(pcm16Val >> 8); offset += 2; } - debugLog += "Finished PCMFLOAT -> PCM16 converting\n"; + debugLog += "Finished FMOD_PCMFLOAT -> PCM16 converting\n"; + } + + //For raw wav data in unity 1.x - 2.5 + //we can handle it with fmod as well, but this way should be better, I guess + public byte[] WrapWavData(ref string debugLog) + { + if (!isLegacyRawWav) + return null; + + var legacyType = (LegacyAudioType)m_AudioClip.m_Format; + var audioSize = (int)m_AudioClip.m_Size; + var channels = m_AudioClip.m_Channels; + var sampleRate = m_AudioClip.m_Frequency; + var audioFormat = WavAudioFormat.PCM; + var bits = legacyType == LegacyAudioType.RawWavU8Mono ? 8 : 16; + + debugLog += "[Legacy AudioClip] Generating wav header..\n"; + if (m_AudioClip.m_AudioData.Endian == EndianType.BigEndian && bits == 16) + { + SwapPcm16Endian(audioBuff.AsSpan(0, audioSize)); + } + var wavBuffer = new byte[audioSize + 44]; + var wavHeader = new WavHeader((uint)audioSize, audioFormat, channels, (uint)sampleRate, bits); + wavHeader.WriteToArray(wavBuffer); + audioBuff.AsSpan(0, audioSize).CopyTo(wavBuffer.AsSpan(44)); + + return wavBuffer; + } + + public static void SwapPcm16Endian(Span buffer) + { + for (var i = 0; i < buffer.Length; i += 2) + { + buffer.Slice(i, 2).Reverse(); + } } private static bool ErrorCheck(RESULT result, ref string log) @@ -168,34 +232,46 @@ namespace AssetStudio public string GetExtensionName() { + //version based + if (isLegacyRawWav) + return ".wav"; + + //signature based + var magic = audioBuff.AsSpan(0, 4); + if (magic.SequenceEqual(fsb5Magic) || magic.SequenceEqual(fsb4Magic)) + return ".fsb"; + if (magic.SequenceEqual(oggMagic)) + return ".ogg"; + + //audioClip based if (m_AudioClip.version < 5) { + if (m_AudioClip.version < (2, 6)) + { + switch (fmodSoundType) //fmod based + { + case SOUND_TYPE.MPEG: return ".mp3"; + case SOUND_TYPE.OGGVORBIS: return ".ogg"; + } + } + switch (m_AudioClip.m_Type) { - case FMODSoundType.AAC: - return ".m4a"; - case FMODSoundType.AIFF: - return ".aif"; - case FMODSoundType.IT: - return ".it"; - case FMODSoundType.MOD: - return ".mod"; - case FMODSoundType.MPEG: - return ".mp3"; - case FMODSoundType.OGGVORBIS: - return ".ogg"; - case FMODSoundType.S3M: - return ".s3m"; - case FMODSoundType.WAV: - return ".wav"; - case FMODSoundType.XM: - return ".xm"; - case FMODSoundType.XMA: - return ".wav"; - case FMODSoundType.VAG: - return ".vag"; - case FMODSoundType.AUDIOQUEUE: - return ".fsb"; + case FMODSoundType.AAC: return ".m4a"; + case FMODSoundType.AIFF: return ".aif"; + case FMODSoundType.ASF: return ".wma"; + case FMODSoundType.FLAC: return ".flac"; + case FMODSoundType.FSB: return ".fsb"; + case FMODSoundType.IT: return ".it"; + case FMODSoundType.MOD: return ".mod"; + case FMODSoundType.MPEG: return ".mp3"; + case FMODSoundType.OGGVORBIS: return ".ogg"; + case FMODSoundType.S3M: return ".s3m"; + case FMODSoundType.WAV: return ".wav"; + case FMODSoundType.XM: return ".xm"; + case FMODSoundType.XMA: return ".xma"; + case FMODSoundType.VAG: return ".vag"; + case FMODSoundType.AUDIOQUEUE: return ".fsb"; } } @@ -203,28 +279,10 @@ namespace AssetStudio { switch (m_AudioClip.m_CompressionFormat) { - case AudioCompressionFormat.PCM: - return ".fsb"; - case AudioCompressionFormat.Vorbis: - return ".fsb"; - case AudioCompressionFormat.ADPCM: - return ".fsb"; - case AudioCompressionFormat.MP3: - return ".fsb"; - case AudioCompressionFormat.PSMVAG: - return ".fsb"; - case AudioCompressionFormat.HEVAG: - return ".fsb"; - case AudioCompressionFormat.XMA: - return ".fsb"; - case AudioCompressionFormat.AAC: - return ".m4a"; - case AudioCompressionFormat.GCADPCM: - return ".fsb"; - case AudioCompressionFormat.ATRAC9: - return ".fsb"; + case AudioCompressionFormat.AAC: return ".m4a"; } } + return ".AudioClip"; } } diff --git a/AssetStudioUtility/Audio/AudioClipExtension.cs b/AssetStudioUtility/Audio/AudioClipExtension.cs index 3615cfe..fb85d4e 100644 --- a/AssetStudioUtility/Audio/AudioClipExtension.cs +++ b/AssetStudioUtility/Audio/AudioClipExtension.cs @@ -2,43 +2,60 @@ { public static class AudioClipExtension { - public static bool IsConvertSupport(this AudioClip m_AudioClip) + public static string GetCompressionFormatString(this AudioClip m_AudioClip) { + if (m_AudioClip.version < (2, 6)) + { + switch ((LegacyAudioType)m_AudioClip.m_Format) + { + case LegacyAudioType.RawWavU8Mono: return "PCM WAV (8-bit unsigned)"; + case LegacyAudioType.RawWavS16Mono: + case LegacyAudioType.RawWavS16Stereo: return "PCM WAV (16-bit signed)"; + case LegacyAudioType.CompressedAudio: return "Externally compressed audio (OGG/MP3/..)"; + default: return $"Unknown ({m_AudioClip.m_Format})"; + } + } if (m_AudioClip.version < 5) { switch (m_AudioClip.m_Type) { - case FMODSoundType.AIFF: - case FMODSoundType.IT: - case FMODSoundType.MOD: - case FMODSoundType.S3M: - case FMODSoundType.XM: - case FMODSoundType.XMA: - case FMODSoundType.AUDIOQUEUE: - return true; - default: - return false; + case FMODSoundType.AAC: return "AAC"; + case FMODSoundType.AIFF: return "AIFF"; + case FMODSoundType.IT: return "Impulse tracker"; + case FMODSoundType.MOD: return "Protracker / Fasttracker MOD"; + case FMODSoundType.MPEG: return "MP2/MP3 MPEG"; + case FMODSoundType.OGGVORBIS: return "Ogg vorbis"; + case FMODSoundType.S3M: return "ScreamTracker 3"; + case FMODSoundType.WAV: return "Microsoft WAV"; + case FMODSoundType.XM: return "FastTracker 2 XM"; + case FMODSoundType.XMA: return "Xbox360 XMA"; + case FMODSoundType.VAG: return "PlayStation Portable ADPCM"; + case FMODSoundType.AUDIOQUEUE: return "iPhone"; + default: return $"Unknown ({m_AudioClip.m_Type})"; } } else { switch (m_AudioClip.m_CompressionFormat) { - case AudioCompressionFormat.PCM: - case AudioCompressionFormat.Vorbis: - case AudioCompressionFormat.ADPCM: - case AudioCompressionFormat.MP3: - case AudioCompressionFormat.XMA: - return true; - default: - return false; + case AudioCompressionFormat.PCM: return "PCM"; + case AudioCompressionFormat.Vorbis: return "Vorbis"; + case AudioCompressionFormat.ADPCM: return "ADPCM"; + case AudioCompressionFormat.MP3: return "MP3"; + case AudioCompressionFormat.PSMVAG: return "PlayStation Portable ADPCM"; + case AudioCompressionFormat.HEVAG: return "PSVita ADPCM"; + case AudioCompressionFormat.XMA: return "Xbox360 XMA"; + case AudioCompressionFormat.AAC: return "AAC"; + case AudioCompressionFormat.GCADPCM: return "Nintendo 3DS/Wii DSP"; + case AudioCompressionFormat.ATRAC9: return "PSVita ATRAC9"; + default: return "Unknown"; } } } - public static bool IsLegacyConvertSupport(this AudioClip m_AudioClip) + public static bool IsLegacyRawWav(this AudioClip m_AudioClip) { - return m_AudioClip.version < (2, 6) && m_AudioClip.m_Format != 0x05; + return m_AudioClip.version < (2, 6) && m_AudioClip.m_Format > 0 && (LegacyAudioType)m_AudioClip.m_Format < LegacyAudioType.CompressedAudio; } } }