jjzjj

android - 如何将一个音频文件叠加在另一个文件上并保存?

coder 2023-12-14 原文

我想要完成的是将人声轨道叠加在音乐轨道上以形成新的歌曲轨道。

这是我的一些代码。我正在使用 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();

然后...我对 music.mp3 执行相同的操作并将其读入一个单独的字节数组。我不会费心展示它的代码,因为它与上面的相同。

在我有两个单独的字节数组后,我可以像这样组合它们......

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

然后将组合后的字节数组写到一个新的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();
        }

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

更新

这是对我在代码中采取的方向的更新。

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

mixSamples(字符串文件路径一,字符串文件路径二)

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

最佳答案

您想使用 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;
}

更新 发自内心地提供代码 :) 我稍后会在我的博客 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();
}

关于android - 如何将一个音频文件叠加在另一个文件上并保存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31007787/

有关android - 如何将一个音频文件叠加在另一个文件上并保存?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  5. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  6. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  7. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  8. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

随机推荐