Search code examples
javaandroidfile-ioconcatenationwave

Merging two WAVE files on Android (concatenate)


I have been trying to merge two WAVE files on Android for quite some time now but really can't seem to get it working properly.

Everything looks fine, the files are read, and written to the output file which is also readable at a later stage and has the file size I would expect to see.

The problems occur right after the app has finished merging. This message will show in the log: Error occured in updateListener, recording is aborted which is a message from extAudioRecorder and appears when the OnRecordPositionUpdateListener reaches the catch clause (the Exception has the following detailMessage: write failed: EBADF (Bad file number)). This does not seem to break anything so I'm not too worried about this.

The real problem arises when I try to create a MediaPlayer and call the setDataSource(String path) on the MediaPlayer instance. Whenever I do this with a merged file the following error message will show in the log: Unable to to create media player (the IOException that is thrown contains the following detailMessage: setDataSourceFD failed.: status=0x80000000). Note that the first time the file will play perfectly fine (this first file was not made by the combineWaveFiles() method). That Error message appears to indicate that the audio file's format is incorrect and/or can't be read by the MediaPlayer.

My question is if anyone sees any real problems with the code below (I know it's sub-optimal in many ways but I prefer to get it to work first then worry about performance).

public static String MergeRecordings(String cumulativeFile, String recordFile, int sampleRate, int bpp, int bufferSize, int channels) {
    if (cumulativeFile == null) {
        return recordFile;
    } else if (recordFile == null) {
        return cumulativeFile;
    }

    String outputFile = FileUtils.getFilePath(null, MDSettings.shared().getMainActivity());
    FileUtils.combineWaveFiles(cumulativeFile, recordFile, outputFile, sampleRate, bpp, bufferSize, channels);

    //FileUtils.removeFile(cumulativeFile);
    //FileUtils.removeFile(recordFile);

    return outputFile;
}

//creates a new file containing file1 + file2 stuck together as such.
private static void combineWaveFiles(String file1, String file2, String outputFile, int sampleRate, int bpp, int bufferSize, int channels) {
    FileInputStream in1 = null, in2 = null;
    FileOutputStream out = null;
    long longSampleRate = sampleRate;
    long byteRate = sampleRate * channels * bpp / 8;

    byte[] data;

    try {
        try {
            in1 = new FileInputStream(file1);
        } catch (Exception e) { }

        try {
            in2 = new FileInputStream(file2);
        } catch (FileNotFoundException e) { }

        out = new FileOutputStream(outputFile);

        long file1Size = 0;
        long file2Size = 0;

        if (in1 != null) { file1Size = in1.getChannel().size() - 44; }
        if (in2 != null) { file2Size = in2.getChannel().size() - 44; }

        long totalAudioLen = file1Size + file2Size;
        long totalDataLen = totalAudioLen + 36;

        FileUtils.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate, bpp);

        if (in1 != null) {
            in1.skip(44);
            data = new byte[bufferSize];

            if (file1Size < bufferSize) {
                data = new byte[(int)file1Size];
            }

            while (in1.read(data) != -1) {
                out.write(data);

                file1Size -= bufferSize;

                if (file1Size <= 0) {
                    break;
                } else if (file1Size < bufferSize) {
                    data = new byte[(int)file1Size];
                }
            }
        }

        if (in2 != null) {
            in2.skip(44);
            data = new byte[bufferSize];

            if (file2Size < bufferSize) {
                data = new byte[(int)file2Size];
            }

            while (in2.read(data) != -1) {
                out.write(data);

                file2Size -= bufferSize;

                if (file2Size <= 0) {
                    break;
                } else if (file2Size < bufferSize) {
                    data = new byte[(int)file2Size];
                }
            }
        }

        out.close();
        if (in1 != null) { in1.close(); }
        if (in2 != null) { in2.close(); }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate, int bpp)
throws IOException {

    byte[] header = new byte[44];

    header[0] = 'R';
    header[1] = 'I';
    header[2] = 'F';
    header[3] = 'F';
    header[4] = (byte)(totalDataLen & 0xff);
    header[5] = (byte)((totalDataLen >> 8) & 0xff);
    header[6] = (byte)((totalDataLen >> 16) & 0xff);
    header[7] = (byte)((totalDataLen >> 24) & 0xff);
    header[8] = 'W';
    header[9] = 'A';
    header[10] = 'V';
    header[11] = 'E';
    header[12] = 'f';
    header[13] = 'm';
    header[14] = 't';
    header[15] = ' ';
    header[16] = 16;
    header[17] = 0;
    header[18] = 0;
    header[19] = 0;
    header[20] = 1;
    header[21] = 0;
    header[22] = (byte) channels;
    header[23] = 0;
    header[24] = (byte)(longSampleRate & 0xff);
    header[25] = (byte)((longSampleRate >> 8) & 0xff);
    header[26] = (byte)((longSampleRate >> 16) & 0xff);
    header[27] = (byte)((longSampleRate >> 24) & 0xff);
    header[28] = (byte)(byteRate & 0xff);
    header[29] = (byte)((byteRate >> 8) & 0xff);
    header[30] = (byte)((byteRate >> 16) & 0xff);
    header[31] = (byte)((byteRate >> 24) & 0xff);
    header[32] = (byte)(channels * bpp); //(2 * 16 / 8);
    header[33] = 0;
    header[34] = (byte)bpp;
    header[35] = 0;
    header[36] = 'd';
    header[37] = 'a';
    header[38] = 't';
    header[39] = 'a';
    header[40] = (byte)(totalAudioLen & 0xff);
    header[41] = (byte)((totalAudioLen >> 8) & 0xff);
    header[42] = (byte)((totalAudioLen >> 16) & 0xff);
    header[43] = (byte)((totalAudioLen >> 24) & 0xff);

    out.write(header, 0, 44);
}

Large parts of this code are taken from this answer.


Solution

  • i do this exactly in my android app. Instead of two, i merge multiple files based on users selection. I use AsyncTask to merge the samples in the background. Take a look at it here. Just filter the section that you need. If you are curious about my app, its called Sound Recorder + Pro, Beside merge i do mix, add echo and amplify samples:

        @Override
        protected Void doInBackground(Void... params) {
    
            isProcessingOn=true;
            try {
                DataOutputStream amplifyOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(Environment.getExternalStorageDirectory() + "/Soundrecpluspro/"  + year +"-"+ month +"-"+ date +"-"+ hour+"-" + min +"-"+ sec+"ME.wav")));
                DataInputStream[] mergeFilesStream = new DataInputStream[selection.size()];
                long[] sizes=new long[selection.size()];
                for(int i=0; i<selection.size(); i++) {
                    File file = new File(Environment.getExternalStorageDirectory() + "/Soundrecpluspro/" +selection.get(i));
                 sizes[i] = (file.length()-44)/2;
                }
                for(int i =0; i<selection.size(); i++) {
                    mergeFilesStream[i] =new DataInputStream(new BufferedInputStream(new FileInputStream(Environment.getExternalStorageDirectory() + "/Soundrecpluspro/" +selection.get(i))));
    
                    if(i == selection.size()-1) {
                        mergeFilesStream[i].skip(24);
                        byte[] sampleRt = new byte[4];
                        mergeFilesStream[i].read(sampleRt);
                        ByteBuffer bbInt = ByteBuffer.wrap(sampleRt).order(ByteOrder.LITTLE_ENDIAN);
                        RECORDER_SAMPLERATE = bbInt.getInt();
                        mergeFilesStream[i].skip(16);
                    }
                        else {
                            mergeFilesStream[i].skip(44);
                        }
    
                }
    
                for(int b=0; b<selection.size(); b++) {
                for(int i=0; i<(int)sizes[b]; i++) {
                     byte[] dataBytes = new byte[2];
                     try {
                     dataBytes[0] = mergeFilesStream[b].readByte();
                     dataBytes[1] = mergeFilesStream[b].readByte();
                     }
                     catch (EOFException e) {
                        amplifyOutputStream.close();
                     }
                     short dataInShort = ByteBuffer.wrap(dataBytes).order(ByteOrder.LITTLE_ENDIAN).getShort();
                     float dataInFloat= (float) dataInShort/37268.0f;
    
    
                    short outputSample = (short)(dataInFloat * 37268.0f);
                    byte[] dataFin = new byte[2];
                   dataFin[0] = (byte) (outputSample & 0xff);
                   dataFin[1] = (byte)((outputSample >> 8) & 0xff);        
                  amplifyOutputStream.write(dataFin, 0 , 2);
    
                }
                }
                amplifyOutputStream.close();
                for(int i=0; i<selection.size(); i++) {
                    mergeFilesStream[i].close();
                }
    
            } catch (FileNotFoundException e) {
    
                e.printStackTrace();
            } catch (IOException e) {
    
                e.printStackTrace();
            }
            long size =0;
            try {
                FileInputStream fileSize = new FileInputStream(Environment.getExternalStorageDirectory() + "/Soundrecpluspro/"+year +"-"+ month +"-"+ date +"-"+ hour+"-" + min +"-"+ sec+"ME.wav");
                size = fileSize.getChannel().size();
                fileSize.close();
            } catch (FileNotFoundException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
    
            final int RECORDER_BPP = 16;
    
            long datasize=size+36;
            long byteRate = (RECORDER_BPP * RECORDER_SAMPLERATE)/8;
            long longSampleRate = RECORDER_SAMPLERATE;
            byte[] header = new byte[44];
    
    
             header[0] = 'R';  // RIFF/WAVE header 
             header[1] = 'I';
             header[2] = 'F';
             header[3] = 'F';
             header[4] = (byte) (datasize & 0xff);
             header[5] = (byte) ((datasize >> 8) & 0xff);
             header[6] = (byte) ((datasize >> 16) & 0xff);
             header[7] = (byte) ((datasize >> 24) & 0xff);
             header[8] = 'W';
             header[9] = 'A';
             header[10] = 'V';
             header[11] = 'E';
             header[12] = 'f';  // 'fmt ' chunk
             header[13] = 'm';
             header[14] = 't';
             header[15] = ' ';
             header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
             header[17] = 0;
             header[18] = 0; 
             header[19] = 0;
             header[20] = 1;  // format = 1
             header[21] = 0;
             header[22] = (byte) 1;
             header[23] = 0;
             header[24] = (byte) (longSampleRate & 0xff);
             header[25] = (byte) ((longSampleRate >> 8) & 0xff);
             header[26] = (byte) ((longSampleRate >> 16) & 0xff);
             header[27] = (byte) ((longSampleRate >> 24) & 0xff);
             header[28] = (byte) (byteRate & 0xff);
             header[29] = (byte) ((byteRate >> 8) & 0xff);
             header[30] = (byte) ((byteRate >> 16) & 0xff);
             header[31] = (byte) ((byteRate >> 24) & 0xff);
             header[32] = (byte) ((RECORDER_BPP) / 8);  // block align
             header[33] = 0;
             header[34] = RECORDER_BPP;  // bits per sample
             header[35] = 0;
             header[36] = 'd';
             header[37] = 'a';
             header[38] = 't';
             header[39] = 'a';
             header[40] = (byte) (size & 0xff);
             header[41] = (byte) ((size >> 8) & 0xff);
             header[42] = (byte) ((size >> 16) & 0xff);
             header[43] = (byte) ((size >> 24) & 0xff);
           // out.write(header, 0, 44); 
    
            try {
                 RandomAccessFile rFile = new RandomAccessFile(Environment.getExternalStorageDirectory() + "/Soundrecpluspro/"  +year +"-"+ month +"-"+ date +"-"+ hour+"-" + min +"-"+ sec+ "ME.wav", "rw");
                rFile.seek(0);
                rFile.write(header);
                rFile.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
    
            return null;
        }