using System; using UnityEngine; using Object = UnityEngine.Object; #if UNITY_STANDALONE || UNITY_EDITOR using System.Collections; using System.IO; using UnityEngine.Networking; #endif namespace OCES { #if UNITY_STANDALONE || UNITY_EDITOR /// /// 从 StreamingAssets 加载资源的 IAssetProvider 实现。 /// 仅支持桌面平台(Windows / macOS / Linux)及 Editor。 /// 无内置缓存——每次调用都从磁盘读取,缓存由上层 ResourceLoader 负责。 /// 目前仅支持 AudioClip(.wav 格式)。 /// public class StreamingAssetProvider : IAssetProvider { public T Load(string path) where T : Object { if (typeof(T) != typeof(AudioClip)) { Debug.LogError($"[StreamingAssetProvider] Load<{typeof(T).Name}> 不支持,仅支持 AudioClip"); return null; } string fullPath = Path.Combine(Application.streamingAssetsPath, path); if (!File.Exists(fullPath)) { Debug.LogError($"[StreamingAssetProvider] 文件 {fullPath} 不存在。"); return null; } byte[] wavBytes = File.ReadAllBytes(fullPath); AudioClip clip = ParseWav(wavBytes, Path.GetFileNameWithoutExtension(path)); return clip as T; } public void LoadAsync(string path, MonoBehaviour coroutineHost, Action onComplete) where T : Object { coroutineHost.StartCoroutine(LoadAsyncCoroutine(path, onComplete)); } IEnumerator LoadAsyncCoroutine(string path, Action onComplete) where T : Object { if (typeof(T) != typeof(AudioClip)) { Debug.LogError($"[StreamingAssetProvider] LoadAsync<{typeof(T).Name}> 不支持,仅支持 AudioClip"); onComplete?.Invoke(null); yield break; } string fullPath = Path.Combine(Application.streamingAssetsPath, path); string url = "file://" + fullPath; using UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.WAV); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { AudioClip clip = DownloadHandlerAudioClip.GetContent(request); onComplete?.Invoke(clip as T); } else { Debug.LogError($"[StreamingAssetProvider] 加载失败: {url}, 错误: {request.error}"); onComplete?.Invoke(null); } } // ───────────────────────────────────────────── // WAV 解析(PCM 16-bit / 8-bit,单声道 / 立体声) // ───────────────────────────────────────────── static AudioClip ParseWav(byte[] wavBytes, string clipName) { int position = 0; // RIFF header if (wavBytes.Length < 44 || wavBytes[0] != 'R' || wavBytes[1] != 'I' || wavBytes[2] != 'F' || wavBytes[3] != 'F') { Debug.LogError("[StreamingAssetProvider] 无效的 WAV 文件:缺少 RIFF 头"); return null; } position += 4; // "RIFF" position += 4; // file size (unused) if (wavBytes[8] != 'W' || wavBytes[9] != 'A' || wavBytes[10] != 'V' || wavBytes[11] != 'E') { Debug.LogError("[StreamingAssetProvider] 无效的 WAV 文件:缺少 WAVE 标识"); return null; } position += 4; // "WAVE" int channels = 1; int sampleRate = 44100; int bitsPerSample = 16; byte[] pcmData = null; // 遍历 chunk while (position < wavBytes.Length - 8) { string chunkId = System.Text.Encoding.ASCII.GetString(wavBytes, position, 4); position += 4; int chunkSize = BitConverter.ToInt32(wavBytes, position); position += 4; if (chunkId == "fmt ") { int audioFormat = BitConverter.ToInt16(wavBytes, position); if (audioFormat != 1) // PCM = 1 { Debug.LogError($"[StreamingAssetProvider] 不支持的音频格式: {audioFormat},仅支持 PCM"); return null; } channels = BitConverter.ToInt16(wavBytes, position + 2); sampleRate = BitConverter.ToInt32(wavBytes, position + 4); bitsPerSample = BitConverter.ToInt16(wavBytes, position + 14); if (channels < 1 || channels > 2) { Debug.LogError($"[StreamingAssetProvider] 不支持的声道数: {channels}"); return null; } } else if (chunkId == "data") { pcmData = new byte[chunkSize]; Array.Copy(wavBytes, position, pcmData, 0, chunkSize); } position += chunkSize; } if (pcmData == null || pcmData.Length == 0) { Debug.LogError("[StreamingAssetProvider] WAV 文件中未找到 data chunk"); return null; } // PCM → float 样本 int sampleCount = pcmData.Length / (bitsPerSample / 8); float[] samples = new float[sampleCount]; if (bitsPerSample == 16) { for (int i = 0; i < sampleCount; i++) { short sample = BitConverter.ToInt16(pcmData, i * 2); samples[i] = sample / 32768f; } } else if (bitsPerSample == 8) { for (int i = 0; i < sampleCount; i++) { samples[i] = (pcmData[i] - 128f) / 128f; } } else { Debug.LogError($"[StreamingAssetProvider] 不支持的位深度: {bitsPerSample}"); return null; } AudioClip clip = AudioClip.Create(clipName, sampleCount / channels, channels, sampleRate, false); clip.SetData(samples, 0); return clip; } } #else /// /// 非桌面平台的桩实现——StreamingAssetProvider 仅支持桌面平台。 /// public class StreamingAssetProvider : IAssetProvider { public T Load(string path) where T : Object { Debug.LogError("[StreamingAssetProvider] 仅支持桌面平台 (Windows/macOS/Linux)"); return null; } public void LoadAsync(string path, MonoBehaviour coroutineHost, Action onComplete) where T : Object { Debug.LogError("[StreamingAssetProvider] 仅支持桌面平台 (Windows/macOS/Linux)"); onComplete?.Invoke(null); } } #endif }