从服务器加载文件到内存

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 块,则不会释放 audioFileoutputDevice。然后您可以将它们保留在内存中,每次播放音频时都会使用相同的引用。

使用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。

以上是关于从服务器加载文件到内存的主要内容,如果未能解决你的问题,请参考以下文章

在应用程序启动时将图像预加载到内存缓存中,使用 iOS 的 Objective c

vue加载nginx内存狂涨不加载

PHP之require加载

从文本文件中读取第一行而不将整个文本文件加载到内存中

如何在 PHP 中使用分析器查看正在加载的文件

如何在android中使用imageloader释放位图内存?