如何将一个音频文件覆盖另一个音频文件并保存?

时间:2021-12-10 20:58:49

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...

这是我的一些代码。我正在使用FileInputStream读取vocal.mp3,然后将其保存为字节数组,如此...

        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.

然后......我为music.mp3做同样的事情并将其读入一个单独的字节数组。我不打算为此显示代码,因为它与上面相同。

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...

然后将组合的字节数组写入一个新的song.mp3文件,以便像这样保存...

        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.

这段代码将两个mp3文件合并为一个......但是它们一个接一个地播放......我需要知道是否有人可以帮我找到让它们同时播放的方法。这样,人声和音乐曲目将在我生成的新歌曲文件中同时播放。

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:

我想调用一个方法并为每个单独的mp3文件传递两个文件路径,如下所示:

mixSamples(String filePathOne, String filePathTwo)

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.

然后在该方法中,我想使用媒体提取器从每个mp3文件中提取数据,然后解码每个文件。文件解码后我想将每个文件存储在short []中然后调用mix()方法,如下所示将两个short []混合成一个组合的short []然后编码新创建的阵列回到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;
    }

2 个解决方案

#1


3  

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:

您希望将MediaCodec与MediaExtractor一起使用,以将mp3(或任何其他音频格式)解码为样本。每个样本都以short而非字节表示。最终你会有短[](样本数)。解码两个音频文件后,您可以将样本混合在一起以生成新样本。然后使用结果样本将处理恢复为编码为音频格式。我用PCM16作为中间格式。将音频混合在一起的方法之一可以是:

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.

更新我心中的代码:)我稍后会在我的博客android.vladli.com上写文章。此代码适用于已弃用的代码,它可以使用,并且新API稍微更清晰,即使没有太大的不同。

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();
}

#2


0  

I've made the same few years ago, I used to play 4 music at the same time. I used threads. Each thread played a music with media player and you are able to synchronise them with Cyclbarrier.

几年前我也是这样做的,我曾经同时演奏过4首音乐。我用线程。每个线程都与媒体播放器播放音乐,您可以将它们与Cyclbarrier同步。

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html

#1


3  

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:

您希望将MediaCodec与MediaExtractor一起使用,以将mp3(或任何其他音频格式)解码为样本。每个样本都以short而非字节表示。最终你会有短[](样本数)。解码两个音频文件后,您可以将样本混合在一起以生成新样本。然后使用结果样本将处理恢复为编码为音频格式。我用PCM16作为中间格式。将音频混合在一起的方法之一可以是:

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.

更新我心中的代码:)我稍后会在我的博客android.vladli.com上写文章。此代码适用于已弃用的代码,它可以使用,并且新API稍微更清晰,即使没有太大的不同。

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();
}

#2


0  

I've made the same few years ago, I used to play 4 music at the same time. I used threads. Each thread played a music with media player and you are able to synchronise them with Cyclbarrier.

几年前我也是这样做的,我曾经同时演奏过4首音乐。我用线程。每个线程都与媒体播放器播放音乐,您可以将它们与Cyclbarrier同步。

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html