[Java Android]: Mezclador de canciones problema memoria

Iniciado por kondrag_X1, 25 Febrero 2015, 12:16 PM

0 Miembros y 1 Visitante están viendo este tema.

kondrag_X1

Hola Gente  :P, les comento.

Estoy haciendo una app en android para mezclar canciones, lo más sencilla posible,es decir, canciones con misma frecuencia mismo formato 16 PCM (wav). He intentado hacer el procesamiento directo es decir coger cargar todas las canciones en memoria y hacerlo pero no es posible ya que el wav es un sistema de sonido sin perdidas y pesan muchísimo los archivos. Ahora trato de hacer un procesamiento por bloques pero no quisiera utilizar Vector, List etc ya que son objetos costosos para memoria y nivel computacional(o eso he creído leer). entonces que puedo hacer ir guardando el resultado en un archivo y después leerlo?


    public short[] mixFiles() {
        short[] output = new short[0];
        try {

            InputStream is1 = act.getResources().openRawResource(R.raw.mar);
            InputStream is2 = act.getResources().openRawResource(R.raw.musica);
            InputStream is3 = act.getResources().openRawResource(R.raw.aura);

            InputStream inputStream = null;

            //procesado por bloques
            byte[] bytes    = null;
            float[] mixed      = null;
            short[] sBuffer = null;
            int i           = 0;

            boolean  read_is1 = true;
            boolean  read_is2 = true;
            boolean  read_is3 = true;

            //leera toda la señal
            while( read_is1 && read_is2 && read_is3 ) {
                mixed =  new float[1024];
                //cada iteracion leera una parte de cada archivo
                for (int k = 0; k < 3; k++) {
                    switch (k) {
                        case 0:
                            inputStream = is1;
                            break;
                        case 1:
                            inputStream = is2;
                            break;
                        case 2:
                            inputStream = is3;
                            break;
                    }
                    try {

                        //leo los bytes y guardo en bytes[]
                        bytes    = new byte[1024];
                        new DataInputStream(inputStream).readFully(bytes, 1024*i, 1024);

                        //rellena el sbuffer con el formato para la reproducción.
                        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(sBuffer);

                    }catch(EOFException ex){
                        //si entro aqui, es porque he leido entero el fichero con valor k
                        switch (k)
                        {
                            case 0:
                                read_is1   = false;
                                is1.close();
                                break;
                            case 1:
                                read_is2   = false;
                                is2.close();
                                break;
                            case 2:
                                read_is3  = false;
                                is3.close();
                                break;
                        }
                    }catch (IOException e) {
                        //no hago nada, solo inicializo el array de bytes a 0.
                        bytes = new byte[1024];
                    }
                    //no quiero utilizar vectores ya que gastan memoria
                    //prodria guardar la suma en dico el archivo?
                    for (int j = 0; j < bytes.length; j++)
                    {
                        mixed[j] = mixed[j] + (bytes[j] / 32768.0f);
                        // reduce the volume a bit:
                        mixed[j] *= 0.8;
                        // hard clipping
                        if (mixed[j] > 1.0f) mixed[j] = 1.0f;
                        if (mixed[j] < -1.0f) mixed[j] = -1.0f;
                        short outputSample = (short) (mixed[j] * 32768.0f);
                    }
                }
                pasarGarbageCollector();
                mixed     = null;
                bytes   = null;

                i += 1;
            }
            return output;
}

Usuario Invitado

#1
1) Te recomiendo leer el libro Código limpio de R. C. Martin.

2) Acostúmbrate a hacer expresivo el código no los comentarios. Cuando un código es limpio es expresivo y fácil de entender. Coloca nombres a las variables de manera que se sepa que valores guardan.

Respecto a tu duda. puedes hacerlo de ambas formas pero por comodida y practicidad puedes hacerlo con ArrayList. No sé si estarás enterado pero en la actualidad ArrayList es una de lae colecciones más eficientes que tenemos disponibles en Java, incluso en algunas ocasiones es mucho más eficiente que arrays convencionales.

Usa ArrayList, dudo que afecte el rendimiento de tu aplicación.

PD: Disculpa si encuentras errores ortográficos, estoy en móvil.
"La vida es muy peligrosa. No por las personas que hacen el mal, si no por las que se sientan a ver lo que pasa." Albert Einstein

kondrag_X1

muchísimas gracias la verdad es que estoy un poco desconectado de java. lo mío es c como seguramente puedes ver por el código. voy a probar con arrayList y os comento.

Esta tarde haré mas expresivo el código.

saludos y mil gracias

kondrag_X1

Buenas continuo con el problema de antes, pero ahora , para mas agilidad, he portado el código a java y así depurarlo más rápido.

El problema basicamente es que cuando cargo tres canciones sin comprimir de alrededor 90 megas para mezclarlas cuando guardo el resultado de la mezcla en un Array me dice:
con 22838 elementos en el array. java.lang.OutOfMemoryError

public static short[] mixFiles() throws FileNotFoundException
{

int LEN_BUFFER = 1024;
/*
        InputStream is1 = act.getResources().openRawResource(R.raw.mar);
        InputStream is2 = act.getResources().openRawResource(R.raw.musica);
        InputStream is3 = act.getResources().openRawResource(R.raw.aura);*/

        InputStream is1 = new FileInputStream("/Users/alarcon/Desktop/Musica/musica.wav");
        InputStream is2 = new FileInputStream("/Users/alarcon/Desktop/Musica/aura.wav");
        InputStream is3 = new FileInputStream("/Users/alarcon/Desktop/Musica/mar.wav");

        InputStream inputStream = null;
        ArrayList<Short> salida = new ArrayList<Short>();

        //procesado por bloques
        byte[] samples = null;
        float[] samples_mixed;
        ByteBuffer byteBuffer;
        short[] samples_short = new short[LEN_BUFFER];
        ShortBuffer shortBuffer;
        int i = 0;

        boolean read_is1 = true;
        boolean read_is2 = true;
        boolean read_is3 = true;

        //leera toda la señal
        while (read_is1 && read_is2 && read_is3) {
            samples_mixed = new float[LEN_BUFFER];
            //cada iteracion leera una parte de cada archivo
            for (int k = 0; k < 3; k++) {
                switch (k) {
                    case 0:
                        inputStream = is1;
                        break;
                    case 1:
                        inputStream = is2;
                        break;
                    case 2:
                        inputStream = is3;
                        break;
                }
                try {

                    //leo los bytes y guardo en bytes[]
                    samples = new byte[LEN_BUFFER];
                    new DataInputStream(inputStream).readFully(samples, 0, LEN_BUFFER);
                    inputStream.close();

                    //rellena el sbuffer con el formato para la reproducción.
                    byteBuffer = ByteBuffer.wrap(samples).order(ByteOrder.LITTLE_ENDIAN);
                    shortBuffer = byteBuffer.asShortBuffer();
                    samples_short = new short[samples.length/2];
                    shortBuffer.get(samples_short);
                   
                    System.out.println("Longitud buffer short: "+samples_short.length);
                }catch ( BufferUnderflowException flow){
                        flow.getCause();
                        flow.getLocalizedMessage();
                        flow.getStackTrace();
                        flow.printStackTrace();
                }catch (EOFException ex) {
                    //si entro aqui, es porque he leido entero el fichero con valor k
                    try {
                        switch (k) {
                            case 0:
                                read_is1 = false;
                                is1.close();
                                break;
                            case 1:
                                read_is2 = false;
                                is2.close();
                                break;
                            case 2:
                                read_is3 = false;
                                is3.close();
                                break;
                        }
                    }catch (IOException e){}
                } catch (IOException e) {
                    //no hago nada, solo inicializo el array de bytes a 0.
                    samples_short = new short[LEN_BUFFER];
                }
                for (int j = 0; j < samples_short.length-1; j++) {
                    samples_mixed[j] = samples_mixed[j] + (samples_short[j] / 32768.0f);
                    // reduce the volume a bit:
                    samples_mixed[j] *= 0.8;
                    // hard clipping
                    if (samples_mixed[j] > 1.0f) samples_mixed[j] = 1.0f;
                    if (samples_mixed[j] < -1.0f) samples_mixed[j] = -1.0f;
                    salida.add(new Short((short) (samples_mixed[j] * 32768.0f)));
                }
            }

            i += 1;
            System.out.println("valor i: "+i);
        }

        Short [] aux = ((Short[]) salida.toArray());
        short [] o   = new short[aux.length];

        for(int a = 0;a < aux.length ;a++)
            o[a] = aux[a].shortValue();

     return  o;
}


siendo la línea de abajo donde me dice que se consume toda la memoria.

salida.add(new Short((short) (samples_mixed[j] * 32768.0f)));


y este el error que me produce la máquina virtual de android y java.


Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at MezclaSonido.mixFiles(MezclaSonido.java:120)
at MezclaSonido.main(MezclaSonido.java:19)


He leido que el fallo es debido a que se queda sin memoria la máquina virtual pero cuando intento de poner los parámetros en el campo run->configurations de eclipse -Xms512M -Xmx1024M ampliando así la memoria ram en la maquina virtual sigue pasando lo mismo.

Alguien conoce el tema lo suficiente para echarme una mano. 

Usuario Invitado

A mí me parece un memory leak, aunque desconozco si el manejo de archivos multimedia grandes pueda consumir tanta RAM. Vamos a hacer un análsis de memoria.


CONFIGURAR LA VM PARA VOLCADO DE MEMORIA




Luego, procedamos a hacer un volcado de memoria (dump) cuando se detecte un OutOfMemoryError:

  • Agrega el argumento en VM arguments:

    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/ruta/al/directorio/deseado

    Corre el programa. Cuando se produzca un OutofMemoryError, la VM hará un volcado de memoria para su posterior análisis.


    ANALIZANDO EL VOLCADO DE MEMORIA




    Instalar MAT (Memory Analyzer Tool) que Eclipse Founation



    Para mayor comodidad nos descargamos MAT independiente desde aquí

    Abrimos MAT y nos dirigimos al menú file y seleccionamos la opción Open Heap Dump.


    Navegas hacia la ruta donde has especificado guardar el memory dump (VM arguments en Eclipse) y seleccionas el archivo de volcado (extensión .hprof).

    Se nos mostrará una ventana para seleccionar la opción de análisis que deseamos. Seleccionamos la primera y le damos finish:


    MAT hará su trabajo y nos mostrará el análisis del memory dump.


  • A partir de ésta pestaña principal puedes ir muy profundo. Puedes ver como usar MAT a detalle en éste blog.

  • También dispones de VisualVM, una herramienta gratuita de Java para analizar el rendimiento de aplicaciones y mucho más: http://visualvm.java.net/features.html

  • Si deseas puedes establecer más RAM a tu aplicación, prueba a 2048m o si tienes 4GB de RAM 4096m. Recuerda que deben ir en VM arguments y no en Program arguments.

    Saludos.
"La vida es muy peligrosa. No por las personas que hacen el mal, si no por las que se sientan a ver lo que pasa." Albert Einstein

kondrag_X1

#5
muchísimas gracias he probado establecer max memoria pero al ser para un dispositivo android no me interesa mucho.

Te comento un poco, a ver si por fin me deshago de este pequeño marrón.

¿de la clase InputStream puedo leer byte a byte?Imagino que sí, aunque no me queda muy claro.

esta sería la mas simple.

                       do
{
i=inputStream.read();
}while(i != -1);


y con esta
int read(byte[] b, int off, int len)
b, buffer sobre el que dejaremos los bytes resultado de la lectura.
off, indica la posición del buffer en la cual se almacenarán los bytes leídos.
len, número de bytes a leer.

¿Siempre estoy leyendo los mismo bytes? Imagino que no cuando llegue al fin me devolverá -1

haciedno algo rápido y sin pensar mucho, abriendo 3 ficheros, de distintas longitudes.

    int len_file1 = input1.available();
    int len_file2 = input2.available();
    int len_file3 = input3.available();

    short byte_file1 = 0;
    short byte_file2 = 0;
    short byte_file3 = 0;
   
    int k = 0;
    int suma = 0;
    do{
           k = 0;
           suma = 0;
         
           if(byte_file1 = input1.readShort() >-1){
                k++;
                suma += fin
            }

           if(byte_file2 = input2.readShort() >-1)
           {
               k++;
               suma += byte_file2; //antes de sumarlas tendría que ordenarlo a little indian
               
            }

            if(byte_file3 = input3.readShort() >-1)
            {
              k++;
             suma += byte_file3;
             }

            //aquí para almacenarlo que seria lo mejor guardar fichero oOutputStreme?
            // sin gastar muca memoria
       }while(fin_file1 > -1 && fin_file2 > -1 && fin_file3 > -1);

Usuario Invitado

Según lo que he investigado, para leer un archivo a bytes en Android, se hace así:

Código (java) [Seleccionar]
public void readFile(InputStream inStream) throws IOException {
    byte[] buffer = new byte[8192];
    int bytesRead;
    while ((bytesRead = inStream.read(buffer)) > 0) {
        // proceso aquú
    }
}
"La vida es muy peligrosa. No por las personas que hacen el mal, si no por las que se sientan a ver lo que pasa." Albert Einstein