从服务器加载文件到内存
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从服务器加载文件到内存相关的知识,希望对你有一定的参考价值。
参考技术A AB资源打包后有一个【目录文件】AssetBundle,他保存了所有AB资源的路径与名称,通过aLLAssetBundleURL链接路径 组拼 从【目录文件】获得的AB资源的名字,然后再协程方法内编写相关代码,从而实现从服务器加载资源的功能。详细见代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
public class DownLoadAssetBundle : MonoBehaviour
//AB资源文件保存在服务器中的位置(我的服务器寄了,加载不到了)
private string mainAssetBundleURL = @"http://120.24.90.173/Luademo/AssetBundles/AssetBundles";
private string aLLAssetBundleURL = @"http://120.24.90.173/Luademo/AssetBundles/";
void Start()
StartCoroutine("DownLoadMainAssetBundle");
void Update()
if (Input.GetKeyDown(KeyCode.Space))
LoadAssetBundle();
/// <summary>
/// 下载主[目录]AssetBundle文件
/// </summary>
IEnumerator DownLoadMainAssetBundle()
//创建一个获取 AssetBundle 文件的 web 请求.
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(mainAssetBundleURL);
//发送这个 web 请求.
yield return request.SendWebRequest();
//从 web 请求中获取内容,会返回一个 AssetBundle 类型的数据.
AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
if (ab == null)
Debug.Log("not ab");
//从这个“目录文件 AssetBundle”中获取 manifest 数据.
AssetBundleManifest manifest = ab.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//获取这个 manifest 文件中所有的 AssetBundle 的名称信息.
string[] names = manifest.GetAllAssetBundles();
for (int i = 0; i < names.Length; i++)
//组拼出下载的路径链接.
Debug.Log(aLLAssetBundleURL + names[i]);
//下载单个AssetBundle文件加载到场景中.
//StartCoroutine(DownLoadSingleAssetBundle(aLLAssetBundleURL + names[i]));
//下载AssetBundle并保存到本地.
StartCoroutine(DownLoadAssetBundleAndSave(aLLAssetBundleURL + names[i]));
/// <summary>
/// 下载单个AssetBundle文件加载到场景中
/// </summary>
IEnumerator DownLoadSingleAssetBundle(string url)
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url);
yield return request.SendWebRequest();
AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
//通过获取到的 AssetBundle 对象获取内部所有的资源的名称(路径),返回一个数组.
string[] names = ab.GetAllAssetNames();
for (int i = 0; i < names.Length; i++)
//Debug.Log(names[i]);
//截取路径地址中的文件名,且无后缀名. 需要引入 System.IO 命名空间.
string tempName = Path.GetFileNameWithoutExtension(names[i]);
Debug.Log(tempName);
//实例化.
GameObject obj = ab.LoadAsset<GameObject>(tempName);
GameObject.Instantiate<GameObject>(obj);
/// <summary>
/// 下载AssetBundle并保存到本地
/// </summary>
IEnumerator DownLoadAssetBundleAndSave(string url)
//UnityWebRequestAssetBundle.GetAssetBundle(string uri)使用这个API下载回来的资源它是不支持原始数据访问的.
UnityWebRequest request = UnityWebRequest.Get(url);
yield return request.SendWebRequest();
//表示下载状态是否完毕.
if (request.isDone)
//使用 IO 技术把这个 request 对象存储到本地.(需要后缀)
//SaveAssetBundle(Path.GetFileName(url), request.downloadHandler.data, request.downloadHandler.data.Length);
SaveAssetBundle2(Path.GetFileName(url), request);
/// <summary>
/// 方法1
/// 存储AssetBundle为本地文件
/// </summary>
private void SaveAssetBundle(string fileName, byte[] bytes, int count)
//创建一个文件信息对象.
FileInfo fileInfo = new FileInfo(Application.streamingAssetsPath + "//" + fileName);
//通过文件信息对象的“创建”方法,得到一个文件流对象.
FileStream fs = fileInfo.Create();
//通过文件流对象,往这个文件内写入信息.
//fs.Write(字节数组, 开始位置, 数据长度);
fs.Write(bytes, 0, count);
//文件写入存储到硬盘,关闭文件流对象,销毁文件对象.
fs.Flush();
fs.Close();
fs.Dispose();
Debug.Log(fileName + "下载完毕");
/// <summary>
/// 方法2
/// 存储AssetBundle为本地文件
/// </summary>
private void SaveAssetBundle2(string fileName, UnityWebRequest request)
//构造文件流.
FileStream fs = File.Create(Application.streamingAssetsPath + "//" + fileName);
//将字节流写入文件里,request.downloadHandler.data可以获取到下载资源的字节流.
fs.Write(request.downloadHandler.data, 0, request.downloadHandler.data.Length);
//文件写入存储到硬盘,关闭文件流对象,销毁文件对象.
fs.Flush();
fs.Close();
fs.Dispose();
Debug.Log(fileName + "下载完毕");
/// <summary>
/// 从本地加载 AB 资源并实例化
/// </summary>
private void LoadAssetBundle()
//从本地加载 AB 资源到内存.
AssetBundle assetBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/player1.ab");
//从 AB 资源中获取资源.
GameObject player = assetBundle.LoadAsset<GameObject>("Necromancer");
GameObject.Instantiate<GameObject>(player, Vector3.zero, Quaternion.identity);
如何使用 NAudio 将声音文件加载到内存中并稍后使用?
【中文标题】如何使用 NAudio 将声音文件加载到内存中并稍后使用?【英文标题】:How can I load a sound file into memory using NAudio and use it later? 【发布时间】:2018-05-08 19:50:32 【问题描述】:我每秒播放 5 次相同的声音文件(在它们之间随机选择),而且我总是加载到内存中,因此程序占用了大量内存。如何将声音文件加载到内存中,然后从那里开始?我正在使用 NAudio。当前代码:
var sound = "sounds/test.mp3";
using (var audioFile = new AudioFileReader(sound))
using (var outputDevice = new WaveOutEvent())
outputDevice.Init(audioFile);
outputDevice.Play();
while (outputDevice.PlaybackState == PlaybackState.Playing)
Thread.Sleep(1000);
threadStop();
【问题讨论】:
【参考方案1】:如果您删除 using
块,则不会释放 audioFile
和 outputDevice
。然后您可以将它们保留在内存中,每次播放音频时都会使用相同的引用。
使用using
块,您会重复实例化其内存可能不会立即释放的 NAudio 对象。
var sound = "sounds/test.mp3";
var audioFile = new AudioFileReader(sound);
var outputDevice = new WaveOutEvent();
outputDevice.Init(audioFile);
outputDevice.Play();
while (outputDevice.PlaybackState == PlaybackState.Playing)
Thread.Sleep(1000);
threadStop();
【讨论】:
但是这些变量每秒被替换约 5 次,所以整个地方都会出现内存泄漏。 然后为每个唯一的音频文件实例化一组对象。更新您的问题可能有助于澄清您在做什么。您的问题只是使用单个音频文件源,并且您声明它是“相同的声音文件”。 你没看错,我有多个声音文件。它们在按下一个键时播放。如果我理解正确,我应该为每个声音文件实例化一个新的 AudioFileReader 吗?(但只有一次) 这将是我的方法,为每个文件实例化一个新的AudioFileReader
。【参考方案2】:
我使用article 中的代码解决了整个问题。
它使用MixingSampleProvider
。我将声音加载到一个名为:CachedSound
的自定义类中。然后我使用另一个名为AudioPlaybackEngine
的类来播放它们。它处理混音器,我使用CachedSoundSampleProvider
类来读取缓存的声音。
代码如下所示:
class AudioPlaybackEngine : IDisposable
private readonly IWavePlayer outputDevice;
private readonly MixingSampleProvider mixer;
public AudioPlaybackEngine(int sampleRate = 44100, int channelCount = 2)
outputDevice = new WaveOutEvent();
mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channelCount));
mixer.ReadFully = true;
outputDevice.Init(mixer);
outputDevice.Play();
public void PlaySound(string fileName)
var input = new AudioFileReader(fileName);
AddMixerInput(new AutoDisposeFileReader(input));
private ISampleProvider ConvertToRightChannelCount(ISampleProvider input)
if (input.WaveFormat.Channels == mixer.WaveFormat.Channels)
return input;
if (input.WaveFormat.Channels == 1 && mixer.WaveFormat.Channels == 2)
return new MonoToStereoSampleProvider(input);
throw new NotImplementedException("Not yet implemented this channel count conversion");
public void PlaySound(CachedSound sound)
AddMixerInput(new CachedSoundSampleProvider(sound));
private void AddMixerInput(ISampleProvider input)
mixer.AddMixerInput(ConvertToRightChannelCount(input));
public void Dispose()
outputDevice.Dispose();
public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(44100, 2);
class CachedSound
public float[] AudioData get; private set;
public WaveFormat WaveFormat get; private set;
public CachedSound(string audioFileName)
using (var audioFileReader = new AudioFileReader(audioFileName))
// TODO: could add resampling in here if required
WaveFormat = audioFileReader.WaveFormat;
var wholeFile = new List<float>((int)(audioFileReader.Length / 4));
var readBuffer= new float[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels];
int samplesRead;
while((samplesRead = audioFileReader.Read(readBuffer,0,readBuffer.Length)) > 0)
wholeFile.AddRange(readBuffer.Take(samplesRead));
AudioData = wholeFile.ToArray();
class CachedSoundSampleProvider : ISampleProvider
private readonly CachedSound cachedSound;
private long position;
public CachedSoundSampleProvider(CachedSound cachedSound)
this.cachedSound = cachedSound;
public int Read(float[] buffer, int offset, int count)
var availableSamples = cachedSound.AudioData.Length - position;
var samplesToCopy = Math.Min(availableSamples, count);
Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy);
position += samplesToCopy;
return (int)samplesToCopy;
public WaveFormat WaveFormat get return cachedSound.WaveFormat;
// This class automatically disposes the file reader that it contains.
class AutoDisposeFileReader : ISampleProvider
private readonly AudioFileReader reader;
private bool isDisposed;
public AutoDisposeFileReader(AudioFileReader reader)
this.reader = reader;
this.WaveFormat = reader.WaveFormat;
public int Read(float[] buffer, int offset, int count)
if (isDisposed)
return 0;
int read = reader.Read(buffer, offset, count);
if (read == 0)
reader.Dispose();
isDisposed = true;
return read;
public WaveFormat WaveFormat get; private set;
【讨论】:
您好,我尝试使用您发布的此解决方案,但我无法通过扬声器听到任何声音,尽管我可以看到绿色条在“音量混合器”中上下移动,但没有显示任何内容在“设置”->“扬声器音量”中。帮忙? 你还能听到什么吗? (Youtube,音乐之类的东西) 是的,我可以,其他所有应用程序都可以,但我的应用程序没有输出真实的声音,尽管它显示在“音量混合器”中。 好的,我终于发现了我的问题,AudioPlaybackEngine 是静态的,它实际上是在另一个线程中实例化的,而不是我调用它的线程。而且我不完全知道为什么,但这阻止了它的工作。它现在可以工作,因为我在与调用 PlaySound() 的线程相同的线程中实例化 AudioPlaybackEngine。以上是关于从服务器加载文件到内存的主要内容,如果未能解决你的问题,请参考以下文章