Jump to content

Archived

This topic is now archived and is closed to further replies.

Trunex

Cargar audio desde el sistema de fichero

Recommended Posts

Hola a todos, estoy intentando hacer un pequeño reproductor para que el usuario pueda indicar una ruta de un directorio y mientras que está jugando pueda escuchar de fondo los audios que tenga en dicho directorio. Para solucionar el tipo de los audios (mp3 no es soportado en standalone) uso una variante de la librería NAudio adaptada a Mono. Actualmente me funciona, es capaz de leer los audios y reproducirlos. El problema es que cada vez que se crea el AudioClip (AudioClip.Create) Unity se queda pillado durante 1 segundo. He intentado crear el AudioClip en una hebra y no es posible, el AudioClip solamente se puede crear en la hebra principal. He buscado en la especificación de la api y hay un ejemplo de uso de AudioClip.PCMReaderCallback en el cual crea de forma dinámica una onda senoidal (https://docs.unity3d.com/ScriptReference/AudioClip.Create.html). Yo tengo un float[] en el cual tengo los datos del audio, ese float[] son los datos que antes le pasaba por AudioClip.SetData(), el problema que tengo es que no sé cómo puedo hacer que el float[] que ya tengo lo lea AudioClip.PCMReaderCallback.

He probado estas implementaciones y ninguna me ha funcionado, no me dan error pero no se escucha nada cuando lo reproduzco con el AudioSource.

    void OnAudioRead(float[] data)
    {
        int count = 0;
        while (count < data.Length)
        {
            data[count] = audio.Datos[count];
            count++;
        }
    }
    void OnAudioRead(float[] data)
    {
        int count = 0;
        int posicion = 0;
        while (count < data.Length)
        {
            data[count] = audio.Datos[posicion];
            count++;
        }
        posicion++;
    }
    void OnAudioRead(float[] data)
    {
         data = audio.Datos;
    }

Gracias por cualquier ayuda.

Share this post


Link to post
Share on other sites

A ver si entendí, ese float[] es el conjunto de datos de las muestras del buffer que ingresan al reproductor de audio (del clip en cuestión) ? o es el total de muestras ? porque en la doc dice las muestras leidas:

Delegate called each time AudioClip reads data.

1 hour ago, Trunex said:

He probado estas implementaciones y ninguna me ha funcionado, no me dan error pero no se escucha nada cuando lo reproduzco con el AudioSource.

Para mi lo que pasa (quizás, no lo se) es que el arreglo de floats se pasan al delegado por valor y no por referencia, entonces toda modificación que hagas a estos no se ve afectada en el arreglo que se reproduce. Quizás puedas setearle al clip los arreglos propios con el "SetData"(código ejemplo de la doc):

clip.SetData(samples, 0);

(no tengo idea si está bien, seguramente no :58_disappointed_relieved:)

void OnAudioRead(float[] data)
    {
        int count = 0;
        while (count < data.Length)
        {
            data[count] = audio.Datos[count];
            count++;
        }
                                   
        clip.SetData(data , 0);
    }

 

Share this post


Link to post
Share on other sites
13 minutes ago, lightbug said:

A ver si entendí, ese float[] es el conjunto de datos de las muestras del buffer que ingresan al reproductor de audio (del clip en cuestión) ? o es el total de muestras ? porque en la doc dice las muestras leidas:

El float[] que obtengo de leer el archivo de audio es el mismo float[] que se pasa por parámetro en clip.SetData(float[], int)

15 minutes ago, lightbug said:

Para mi lo que pasa (quizás, no lo se) es que el arreglo de floats se pasan al delegado por valor y no por referencia, entonces toda modificación que hagas a estos no se ve afectada en el arreglo que se reproduce.

Por lo que se ve en los parametros se pasa por valor, pero si ves el ejemplo de Unity (https://docs.unity3d.com/ScriptReference/AudioClip.Create.html) modifica es float[] data que se supone que esta pasado por valor.

17 minutes ago, lightbug said:

Quizás puedas setearle al clip los arreglos propios con el "SetData"(código ejemplo de la doc)

El problema que tengo es que al crear la instancia de AudioClip Unity se queda bloqueado durante 1 segundo, el SetData me funciona perfectamente y es inmediato, el problema es al crear la instancia del AudioClip al cual le voy a pasar los datos, no se si me explico...

Share this post


Link to post
Share on other sites

Ahh ya entendí, son todas las muestras del buffer, el callback creo que se corre cuando cada vez que se carga parte del clip en el audioSource para reproducción, lo mismo cada vez que necesita cambia de posición del cursor. Sí, en el ejemplo está claro, lo que hace es crear una sinusoidal. La variable "position" la actualiza cada vez que se reproduce para mantener la fase constante (solo sirve en la fase de la sinusoide).

Hiciste lo del Start no? de asignar el clip al audioSource y de reproducirlo? si no hiciste esto las llamadas no se realizan.

Share this post


Link to post
Share on other sites
1 hour ago, lightbug said:

Ahh ya entendí, son todas las muestras del buffer, el callback creo que se corre cuando cada vez que se carga parte del clip en el audioSource para reproducción, lo mismo cada vez que necesita cambia de posición del cursor. Sí, en el ejemplo está claro, lo que hace es crear una sinusoidal. La variable "position" la actualiza cada vez que se reproduce para mantener la fase constante (solo sirve en la fase de la sinusoide).

Hiciste lo del Start no? de asignar el clip al audioSource y de reproducirlo? si no hiciste esto las llamadas no se realizan.

Si claro, lo único que no sé es cambiar la función OnAudioRead para que en lugar de crear un sonido constante pueda pasarle (a medida de que me los va pidiendo) los datos del archivo de audio. 

Share this post


Link to post
Share on other sites
14 minutes ago, Trunex said:

Si claro, lo único que no sé es cambiar la función OnAudioRead para que en lugar de crear un sonido constante pueda pasarle (a medida de que me los va pidiendo) los datos del archivo de audio. 

En teoría si especficas el sampleRate y metés (para ese samplerate) los datos muestreados (tu archivo de audio) debería funcionar. A mi se me reproduce lo que le meta, señales square, ruido y demás.

Como tenés tu archivo guardado? está convertido a un array de valores?

Share this post


Link to post
Share on other sites
2 minutes ago, lightbug said:

Como tenés tu archivo guardado? está convertido a un array de valores?

Es un mp3 de una canción cualquiera descargado de internet, y al abrirlo pues obtengo los canales, la frecuencia, un float[] con los datos, etc.

5 minutes ago, lightbug said:

En teoría si especficas el sampleRate y metés (para ese samplerate) los datos muestreados (tu archivo de audio) debería funcionar. A mi se me reproduce lo que le meta, señales square, ruido y demás.

No se si te refieres al método SetData(float[], int) o a la función OnAudioRead, el SetData no lo puedo usar, ya que para poder usar el SetData tengo que crear el AudioClip con la el parámetro stream en false y llamar a ese método Create me bloquea el Unity durante 1 segundo, entonces lo único que puedo hacer es crear el AudioClip  con el parámetro stream en true y pasarle los datos por la función OnAudioRead así no se me quedaría bloqueado el juego; si te refieres a cómo debo implementar el OnAudioRead, ¿podrías enseñarme el código o un esquema de dicha implementación si no es mucha molestia?

Share this post


Link to post
Share on other sites
55 minutes ago, Trunex said:

No se si te refieres al método SetData(float[], int)

No, me refería al mismo AudioClip.Create:

public static AudioClip Create(string name, int lengthSamples, int channels, int frequency, bool stream, AudioClip.PCMReaderCallback pcmreadercallback, AudioClip.PCMSetPositionCallback pcmsetpositioncallback);

57 minutes ago, Trunex said:

¿podrías enseñarme el código o un esquema de dicha implementación si no es mucha molestia?

En realidad no hice nada que no esté en el ejemplo, lo unico me fabriqué mis 44000 floats

float[] samples = new float[44000] ;

en start fui creando variaciones y probando ondas, triangulares, cuadradas, etc, lo más simple de todo sería generar un Random.Range( 0f , 1f ) de prueba y ver si reproduce algo, podés meterlo en el callback pero te da un warning de que Random debería correr en el hilo principal (no se), funciona igual, pero con eso estás fabricando la señal (tipo un sistetizador) y vos lo que querés es partir de datos ya almacenados.

1 hour ago, Trunex said:

¿podrías enseñarme el código o un esquema de dicha implementación si no es mucha molestia?

Claro, no tengo la gran cosa, es solo para hacer pruebas, se escucha algo raro (algunos clicks) seguramente sea el position y el arreglo ... pero basicamente creás (en modo LOOP) la curva de animación que quieras, el script te crea los datos (los floats) y el OnAudioRead los lee/modifica

public AnimationCurve wave = AnimationCurve.Linear(0,1,1,0);
    AudioSource audioS;

    float[] waveData; 
    int position = 0;

    public int sampleRate = 44100;
    public int targetFreq = 1000;

    void Start()
    {
        int samplesPerPeriod = sampleRate / targetFreq;

        waveData = new float[sampleRate];        

        int sample = 0;
        float waveCursor = 0;
        float waveCursorStep = (float)targetFreq / ( sampleRate);

        while(sample < waveData.Length)
        {                  
            waveData[sample] = Mathf.Clamp01(wave.Evaluate(waveCursor));// + 0.1f * Random.Range(0 , 1f);
            waveCursor += waveCursorStep;
            sample++;
        }        

        AudioClip myClip = AudioClip.Create("Wave", sampleRate , 1, sampleRate / 2, false, OnAudioRead, OnAudioSetPosition);
        AudioSource aud = GetComponent<AudioSource>();
        aud.clip = myClip;
        aud.Play();
    }

    void OnAudioRead(float[] data)
    {
        int count = 0;
        while (count < data.Length)
        {
            data[count] = waveData[position + count] ;            
            count++;
        }
    }

    void OnAudioSetPosition(int newPosition)
    {
        position = newPosition;
    }

 

 

Share this post


Link to post
Share on other sites
1 hour ago, lightbug said:

Claro, no tengo la gran cosa, es solo para hacer pruebas, se escucha algo raro (algunos clicks) seguramente sea el position y el arreglo ... pero basicamente creás (en modo LOOP) la curva de animación que quieras, el script te crea los datos (los floats) y el OnAudioRead los lee/modifica

He probado su código, y funciona perfectamente, luego he cambiado los parámetros para adaptarlo a un fichero mp3 auténtico y me sigue sin reproducir ningún sonido.

Estos son los datos obtenidos del fichero:

Debug.Log("LengthSamples: " + audioFichero.Length); //78354432
Debug.Log("Frecuency: " + audioFichero.WaveFormat.SampleRate); //48000
Debug.Log("Datos audio: " + datosAudio.Length); //78354432
Debug.Log("Canales: " + audioFichero.WaveFormat.Channels); //2

Entonces, audioFichero es el contenedor que me devuelve la librería al abrir el archivo mp3, de este contenedor obtengo en orden: lengthSamples, frecuency y channels; datosAudio es el float[] con el contenido del audio (el equivalente a waveData  en su código). ¿Por casualidad tiene alguna idea de qué puede estar fallando?

Share this post


Link to post
Share on other sites
6 hours ago, Trunex said:

¿Por casualidad tiene alguna idea de qué puede estar fallando?

lo primero que se me viene a la cabeza es que en el script que estamos usando reproducimos datos crudos, osea un pcm (raw). Para el caso del mp3 (no se internamente exactamente que hace) pero se que miente, osea, el mp3 es el jpeg del audio, necesitas traducirlo nuevamente a datos reales. No se en realidad que es lo que estás metiendo en el archivo que estás usando, si son datos crudos, datos + metadata, etc. En conclusión, no se si debés decodificar el mp3 antes de poder usarlo o no(si es que la herramienta que estás usando te permite eso, que segun veo de github lo permite pero no se si lo estás usando como se debe)

Estuve leyendo esta parte de la doc (no se si es exactamente lo que usas):

Decompressing Frames

Once you have parsed a complete MP3 frame, it is time to decompress it so we can buffer the PCM audio. NAudio includes a choice of two MP3 frame decompressors, one using the ACM codecs, which should be present on Windows XP onwards, and one for using the DMO (DirectX Media Object) decoder, which is available with Windows Vista onwards. These two classes were internal with NAudio 1.4, but will be made public for NAudio 1.5. You could alternatively use a fully managed MP3 decoder, such as NLayer which is my C# port of the Java MP3 decoder JavaLayer. If I get time I will update NAudioDemo to allow you to select between the available frame decompressors.

Share this post


Link to post
Share on other sites
5 hours ago, lightbug said:

lo primero que se me viene a la cabeza es que en el script que estamos usando reproducimos datos crudos, osea un pcm (raw). Para el caso del mp3 (no se internamente exactamente que hace) pero se que miente, osea, el mp3 es el jpeg del audio, necesitas traducirlo nuevamente a datos reales. No se en realidad que es lo que estás metiendo en el archivo que estás usando, si son datos crudos, datos + metadata, etc. En conclusión, no se si debés decodificar el mp3 antes de poder usarlo o no(si es que la herramienta que estás usando te permite eso, que segun veo de github lo permite pero no se si lo estás usando como se debe)

Los datos que obtengo son los mismos datos que se le pasan por audioclip.SetData(float[], int), de echo lo he probado y a través del SetData si puedo escucharlo, pero necesito introducirlo a medida de que el audioclip lo necesita por tema de rendimiento, la clase que uso para leer el fichero es esta https://github.com/WulfMarius/NAudio-Unity/blob/master/NAudio/Wave/WaveStreams/AudioFileReader.cs como puede ver lo que hace es ver la extensión del archivo, y según dicha extensión pues usa un lector u otro. Tampoco entiendo por qué al llamar AudoClip.Create() con el stream en false es cuando se bloquea el juego durante un 1 segundo y sin embargo el SetData no tarda nada.

Share this post


Link to post
Share on other sites

×
×
  • Create New...