【问题标题】:InputStream audio mixing (MODE_STREAM)InputStream 音频混合 (MODE_STREAM)
【发布时间】:2015-07-29 15:01:22
【问题描述】:

我正在 Android 中制作鼓音序器...

我正在写信给MODE_STREAM 中的AudioTrack,这样我就可以实现与所有InputStreams 的同步音频播放(可通过下面代码中的“活动”输入流列表activeStreams 获得)

音频始终为:PCM (WAV),16 位立体声 44100 Hz。

显然,我无法在 UI 线程上实时合成音频,因此我使用 AsyncTask 将所有音频缓冲排队。

我的缓冲播放工作正常,但在合并两个(或更多)InputStream 的缓冲区时,互联网似乎在讨论下一步该做什么。 “将 byte[] 转换为 short[]!”,“不,即时进行位混合!”,“但是如果你不使用 short,字节字节序将被忽略!”,“它无论如何都会被忽略!” - 我什至不知道了。

如何混合两个或多个 InputStream 的缓冲区?我不明白为什么我当前的实现失败了

我已经尝试过 4 种不同的 StackOverflow 解决方案来将 byte[] 转换为 short[],这样我就可以将示例添加在一起,但是转换总是会立即使 Java 崩溃,并带有一些我无法获取的神秘错误消息转头。所以现在我放弃了。这是我实现one such StackOverflow solution的代码...

protected Long doInBackground(Object ... Object) {

  int bytesWritten = 0;
  InputStream inputStream;
  int si = 0, i = 0;

  //The combined buffers. The 'composition'
  short[] cBuffer = new short[Synth.AUDIO_BUFFER_SIZE];

  //The 'current buffer', the segment of inputStream audio.
  byte[] bBuffer = new byte[Synth.AUDIO_BUFFER_SIZE];

  //The 'current buffer', converted to short?
  short[] sBuffer = new short[Synth.AUDIO_BUFFER_SIZE];

  int curStreamNum;
  int numStreams = activeStreams.size();
  short mix;

  //Start with an empty 'composition'
  cBuffer = new short[Synth.AUDIO_BUFFER_SIZE];

  boolean bufferEmpty = false;
  try {
    while(true) { // keep going forever, until stopped or paused.
      for(curStreamNum = 0;curStreamNum < numStreams;curStreamNum++){
        inputStream = activeStreams.get(curStreamNum);
        i = inputStream.read(bBuffer);
        bufferEmpty = i<=-1;
        if(bufferEmpty){
          //Input stream buffer was empty. It's out of audio. Close and remove the stream.
          inputStream.close();
          activeStreams.remove(curStreamNum);
          curStreamNum--; numStreams--; continue; // hard continue.
        }else{
          //Take the now-read buffer, and convert to shorts.
          ByteBuffer.wrap(bBuffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(sBuffer);

          //Take the short buffer, merge into composition buffer.
          //TODO: Optimize by making the 'first layer' of the composition the first buffer, on its own.
          for(si=0;si<Synth.AUDIO_BUFFER_SIZE;si++){
            mix = (short) (sBuffer[si] + cBuffer[si]);
            //This part is probably completely wrong too. I'm not up to here yet to evaluate whats needed...
            if(mix >= 32767){
              mix = 32767;
            }else if (mix <= -32768){
              mix = -32768;
            }
            cBuffer[si] = mix;
          }
        }
      }
      track.write(sBuffer, 0, i);

      //It's always full; full buffer of silence, or of composited audio.
      totalBytesWritten += Synth.AUDIO_BUFFER_SIZE;

      //.. queueNewInputStreams ..
      publishProgress(totalBytesWritten);
      if (isCancelled()) break;
    }
  } catch (IOException e) {e.printStackTrace();}
  return Long.valueOf(totalBytesWritten);
}

我目前在这条线上收到BufferUnderflowExceptionByteBuffer.wrap(bBuffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(sBuffer);

如何可能导致缓冲区不足?我只是将 byte[] 转换为 short[]。

请帮忙!

我已经发布了我的整个函数,希望这个更完整的代码示例和相当灵活的用法可以帮助其他人。

(P.S. byte[] 到 short[] 的转换之后是一些脆弱的硬剪辑,我什至还没有调试到,但建议也将不胜感激)

【问题讨论】:

  • 你检查我的答案了吗?有什么意见吗?
  • 抱歉耽搁了。很快就会测试你的答案。

标签: java android audio buffer inputstream


【解决方案1】:

您的解决方案似乎几乎不错,我发现了两个问题和一个潜在的问题:

  1. 短数组的长度:它必须是字节数组的一半,否则会出现下溢
  2. 短线的总和必须是短线的平均值,而不仅仅是总和,否则您只会得到噪音
  3. (潜在问题)您通过 InputStream 读取的数组的长度不能完全免费,因为您必须为每个 InputStream 求和 2 个字节(那么它必须是一个偶数数组)并且您应该注意单声道与立体声音频文件(如果是立体声,则左声道有 2 个字节,右声道有 2 个字节)

在这里你可以找到一个 sn-p,我会用它来对两个 WAV 数组(16 位,单声道)求和

    Random random = new Random();

    int bufferLength = 20;

    byte[] is1 = new byte[bufferLength];
    byte[] is2 = new byte[bufferLength];
    byte[] average = new byte[bufferLength];

    random.nextBytes(is1);
    random.nextBytes(is2);

    short[] shorts1 = new short[bufferLength/2];
    ByteBuffer.wrap(is1).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts1);

    short[] shorts2 = new short[bufferLength/2];
    ByteBuffer.wrap(is2).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts2);

    short[] result = new short[bufferLength/2];

    for (int i=0; i<result.length; i++) {
        result[i] = (short) ((shorts1[i] + shorts2[i])/2);
    }

    ByteBuffer.wrap(average).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(result);

对于 32 位立体声,解决方案可能是

    Random random = new Random();

    int bufferLength = 8 * 50;

    byte[] is1 = new byte[bufferLength];
    byte[] is2 = new byte[bufferLength];
    byte[] average = new byte[bufferLength];

    random.nextBytes(is1);
    random.nextBytes(is2);

    System.out.println(bytesToHex(is1));
    System.out.println(bytesToHex(is2));

    int[] ints1 = new int[bufferLength/4];
    ByteBuffer.wrap(is1).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get(ints1);

    int[] ints2 = new int[bufferLength/4];
    ByteBuffer.wrap(is2).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get(ints2);

    int[] result = new int[bufferLength/4];

    for (int i=0; i<result.length; i++) {
        result[i] = ((ints1[i] + ints2[i])/2);
    }

    ByteBuffer.wrap(average).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().put(result);

【讨论】:

  • 看看这个,我很有信心它会起作用。我已经准备好把赏金交给你了。谢谢你的帮助!您能否发布答案的 32 位立体声变体?我认为它会将/2 的所有实例更改为/8... 对吗?或者我需要使用/4,但产生两个版本的byte[] average(一个用于左,一个用于右)
  • 这很简单:如果你有一个 32 位的样本,那么你需要对 int 值求和(Java 中的 int 是 4bytes->32bit)。所以你只需要将 2 修改为 4 并将 'asShortBuffer' 修改为 'asIntBuffer'。关于左右:你有一个立体声WAV文件,左边样本与右边样本交错;这意味着你有 L R L R L R ok?
  • 如果您有 32 位立体声 PCM,则样本(左+右)的字节数为 64 位,或者左+右样本为 8 字节。
  • 我最终没有测试任何这些,但我很确定这应该足以让我获得解决方案的工作,所以我只想给你赏金。再次感谢您的帮助!
猜你喜欢
  • 2012-02-06
  • 1970-01-01
  • 1970-01-01
  • 2018-07-10
  • 1970-01-01
  • 1970-01-01
  • 2014-02-01
  • 1970-01-01
  • 2014-09-22
相关资源
最近更新 更多