Files
AudioSystem/Assets/Scripts/OCES/AssetsManagement/StreamingAssetProvider.cs
T
Oliver 3c2558f5e7 WIP: Live mixing support.
checkpoint: StreamingAsset Loader.
2026-05-18 19:25:09 +08:00

194 lines
7.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
/// <summary>
/// 从 StreamingAssets 加载资源的 IAssetProvider 实现。
/// 仅支持桌面平台(Windows / macOS / Linux)及 Editor。
/// 无内置缓存——每次调用都从磁盘读取,缓存由上层 ResourceLoader 负责。
/// 目前仅支持 AudioClip.wav 格式)。
/// </summary>
public class StreamingAssetProvider : IAssetProvider
{
public T Load<T>(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<T>(string path, MonoBehaviour coroutineHost, Action<T> onComplete) where T : Object
{
coroutineHost.StartCoroutine(LoadAsyncCoroutine(path, onComplete));
}
IEnumerator LoadAsyncCoroutine<T>(string path, Action<T> 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
/// <summary>
/// 非桌面平台的桩实现——StreamingAssetProvider 仅支持桌面平台。
/// </summary>
public class StreamingAssetProvider : IAssetProvider
{
public T Load<T>(string path) where T : Object
{
Debug.LogError("[StreamingAssetProvider] 仅支持桌面平台 (Windows/macOS/Linux)");
return null;
}
public void LoadAsync<T>(string path, MonoBehaviour coroutineHost, Action<T> onComplete) where T : Object
{
Debug.LogError("[StreamingAssetProvider] 仅支持桌面平台 (Windows/macOS/Linux)");
onComplete?.Invoke(null);
}
}
#endif
}