Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I overlay one audio file over another one and save it?

What I am trying to accomplish is overlaying a vocal track over a music track to form a new song track.

Here is some code I have. I am reading the vocal.mp3 using FileInputStream and then saving it to a byte array like so...

        try {
            fis = new FileInputStream(myFile);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        bos = new ByteArrayOutputStream();
        byte[] buf = new byte[2048];
        try {
            for (int readNum; (readNum = fis.read(buf)) != -1;) {
                bos.write(buf, 0, readNum);
                System.out.println("read " + readNum + " bytes,");
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } 

        bytes = bos.toByteArray();

Then... I do the same thing for the music.mp3 and read that into a separate byte array. I'm not going to bother showing the code for that since it is the same as above.

After I have the two separate byte arrays I can combine them like so...

        outputStream = new ByteArrayOutputStream( );
        try {
            outputStream.write( bytes );
            outputStream.write( bytes2 );
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        mixData = new byte[bytes.length + bytes2.length];
        mixData = outputStream.toByteArray( );

And then write the combined byte array to a new song.mp3 file for saving like so...

        File someFile = new File(songOutPath);

        try {
            fos2 = new FileOutputStream(someFile);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            fos2.write(mixData);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            fos2.flush();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            fos2.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

This code will merge the two mp3 files into one...but they play one after another... I need to know if someone can help me find a way to get them to play simultaneously. This way the vocal and music track will play at the same time in a new song file that I'd generate.

UPDATE

Here is an update to the direction I am taking in my code.

I would like to call a method and pass it two filepaths for each seperate mp3 file, something like so:

mixSamples(String filePathOne, String filePathTwo)

Then in that method I would like to use media extractor to extract the data from each mp3 file and then decode each file. After the files have been decoded I would like to store each file in a short[] and then call the mix() method as seen below to mix the two short[]'s into one combined short[] and then encode that newly created array back into an mp3.

    public void mixSamples(String filePathOne, String filePathTwo){
        MediaCodec codec = null;

        MediaExtractor extractor = new MediaExtractor();
        try {
            extractor.setDataSource(filePathOne);
            return create(extractor);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            extractor.release();
        }

        // ... Do I create another extractor here for my second file?

        MediaFormat format = extractor.getTrackFormat(0);
        String mime = format.getString(MediaFormat.KEY_MIME);
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);

        try {
            codec = MediaCodec.createDecoderByType(mime);
            codec.configure(format, null, null, 0);
            codec.start();
            ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
            ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();

            extractor.selectTrack(0);

            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            final long timeoutUs = 5000;
            boolean sawInputEOS = false;
            boolean sawOutputEOS = false;
            int noOutputCounter = 0;

            while (!sawOutputEOS && noOutputCounter < 50) {
                noOutputCounter++;
                if (!sawInputEOS) {
                    int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
                    if (inputBufferIndex >= 0) {
                        ByteBuffer buffer = codecInputBuffers[inputBufferIndex];
                        int sampleSize = extractor.readSampleData(buffer, 0);
                        long presentationTimeUs = 0;
                        if (sampleSize < 0) {
                            sawInputEOS = true;
                            sampleSize = 0;
                        } else {
                            presentationTimeUs = extractor.getSampleTime();
                        }
                        codec.queueInputBuffer(inputBufferIndex, 0, sampleSize,
                                presentationTimeUs,
                                sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
                        if (!sawInputEOS) {
                            extractor.advance();
                        }
                    }
                }

                int outputBufferIndex = codec.dequeueOutputBuffer(info, timeoutUs);
                if (outputBufferIndex >= 0) {
                    if (info.size > 0) {
                        noOutputCounter = 0;
                    }
                    ByteBuffer buffer = codecOutputBuffers[outputBufferIndex];
                    if (info.size > 0) {

                        // Do something... Maybe create my short[] here...
                    }
                    codec.releaseOutputBuffer(outputBufferIndex, false);
                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        sawOutputEOS = true;
                    }
                } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    codecOutputBuffers = codec.getOutputBuffers();
                }
            }
        } catch (IOException e){

        }finally {
            codec.stop();
            codec.release();
        }
    }

    static short[] mix(short[] buffer, short[] mixWith, int numberOfMixSamples) {
        final int length = Math.min(buffer.length, numberOfMixSamples);
        int mixed;
        for (int i = 0; i < length; i++) {
            mixed = (int) buffer[i] + (int) mixWith[i];
            if (mixed > 32767) mixed = 32767;
            if (mixed < -32768) mixed = -32768;
            buffer[i] = (short) mixed;
        }
        return buffer;
    }
like image 622
AMG Avatar asked Sep 27 '22 13:09

AMG


1 Answers

You want to use MediaCodec with MediaExtractor to decode mp3 (or any other audio format) to samples. Each sample is presented by short not byte. Eventually you would have short[] (number of samples). Once you decode both audio files, then you could mix samples together to produce new samples. Then revert process to encode to audio format using result samples. I used PCM16 as intermediate format. One of the ways to mix audio together can be this:

static short[] mix(short[] buffer, short[] mixWith, int numberOfMixSamples) {
    final int length = Math.min(buffer.length, numberOfMixSamples);
    int mixed;
    for (int i = 0; i < length; i++) {
        mixed = (int) buffer[i] + (int) mixWith[i];
        if (mixed > 32767) mixed = 32767;
        if (mixed < -32768) mixed = -32768;
        buffer[i] = (short) mixed;
    }
    return buffer;
}

UPDATE Giving code from my heart :) I am going to write articles on it later on my blog android.vladli.com. This code is for already deprecated code, it will work, and new API is slightly cleaner, even though not much different.

MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(file.getAbsolutePath());
try {
   return create(extractor);
} finally {
   extractor.release();
}

// ...

MediaFormat format = extractor.getTrackFormat(0);
String mime = format.getString(MediaFormat.KEY_MIME);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);

MediaCodec codec = MediaCodec.createDecoderByType(mime);
codec.configure(format, null, null, 0);
codec.start();

try {
    ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
    ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();

    extractor.selectTrack(0);

    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    final long timeoutUs = 5000;
    boolean sawInputEOS = false;
    boolean sawOutputEOS = false;
    int noOutputCounter = 0;

    while (!sawOutputEOS && noOutputCounter < 50) {
        noOutputCounter++;
        if (!sawInputEOS) {
            int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
            if (inputBufferIndex >= 0) {
                ByteBuffer buffer = codecInputBuffers[inputBufferIndex];
                int sampleSize = extractor.readSampleData(buffer, 0);
                long presentationTimeUs = 0;
                if (sampleSize < 0) {
                    sawInputEOS = true;
                    sampleSize = 0;
                } else {
                    presentationTimeUs = extractor.getSampleTime();
                }
                codec.queueInputBuffer(inputBufferIndex, 0, sampleSize,
                        presentationTimeUs,
                        sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
                if (!sawInputEOS) {
                    extractor.advance();
                }
            }
        }

        int outputBufferIndex = codec.dequeueOutputBuffer(info, timeoutUs);
        if (outputBufferIndex >= 0) {
            if (info.size > 0) {
                noOutputCounter = 0;
            }
            ByteBuffer buffer = codecOutputBuffers[outputBufferIndex];
            if (info.size > 0) {
                // data.writePcm16(buffer, info.offset, info.size);
                // data here is my class to gather buffer (samples) in a queue for further playback. In your case can write them down into disk or do something else
            }
            codec.releaseOutputBuffer(outputBufferIndex, false);
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                sawOutputEOS = true;
            }
        } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            codecOutputBuffers = codec.getOutputBuffers();
        }
    }
} finally {
    codec.stop();
    codec.release();
}
like image 101
Volodymyr Lykhonis Avatar answered Oct 07 '22 21:10

Volodymyr Lykhonis