AudioTrack - 使用jlayer的短数组到字节数组失真(java mp3 decoder)

Sta*_*wed 8 java mp3 android pcm jlayer

我正在使用jLayer解码MP3数据,通过以下调用:

SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
Run Code Online (Sandbox Code Playgroud)

这个返回解码数据的调用返回一个short []数组. output.getBuffer();

当我使用该方法调用AudioTrack write()时,它在我循环文件时播放正常:

at.write(output.getBuffer(), 0, output.getBuffer().length);
Run Code Online (Sandbox Code Playgroud)

但是,当我使用本答案中的任何方法将short []数组转换为byte []数组时:https://stackoverflow.com/a/12347176/1176436声音变得扭曲和抖动:

at.write(output.getBuffer(), 0, output.getBuffer().length);
Run Code Online (Sandbox Code Playgroud)

变为:

byte[] array = ShortToByte_Twiddle_Method(output.getBuffer());
at.write(array,  0,  array.length);
Run Code Online (Sandbox Code Playgroud)

我做错了什么,我该怎么做才能解决它?不幸的是,我需要将pcm数据放在我正在使用的另一个第三方库的字节数组中.如果重要,该文件为22kHz,这就是实例化的方式:

at = new AudioTrack(AudioManager.STREAM_MUSIC, 22050, AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_16BIT, 10000 /* 10 second buffer */,
                AudioTrack.MODE_STREAM);   
Run Code Online (Sandbox Code Playgroud)

非常感谢你提前.

编辑:这就是我现在实例化AudioTrack变量的方式.因此对于44kHz文件,发送的值是44100,而对于22kHz文件,值是22050.

at = new AudioTrack(AudioManager.STREAM_MUSIC, decoder.getOutputFrequency(), 
                                  decoder.getOutputChannels() > 1 ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO,
                                  AudioFormat.ENCODING_PCM_16BIT, 10000 /* 10 second buffer */,
                                  AudioTrack.MODE_STREAM);
Run Code Online (Sandbox Code Playgroud)

这是解码方法:

public byte[] decode(InputStream inputStream, int startMs, int maxMs) throws IOException {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);

        float totalMs = 0;
        boolean seeking = true;

        try {
            Bitstream bitstream = new Bitstream(inputStream);
            Decoder decoder = new Decoder();

            boolean done = false;
            while (!done) {
                Header frameHeader = bitstream.readFrame();
                if (frameHeader == null) {
                    done = true;
                } else {
                    totalMs += frameHeader.ms_per_frame();

                    if (totalMs >= startMs) {
                        seeking = false;
                    }

                    if (!seeking) {
                        // logger.debug("Handling header: " + frameHeader.layer_string());
                        SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);                            

                        short[] pcm = output.getBuffer();
                        for (short s : pcm) {
                            outStream.write(s & 0xff);
                            outStream.write((s >> 8) & 0xff);
                        }
                    }

                    if (totalMs >= (startMs + maxMs)) {
                        done = true;
                    }
                }
                bitstream.closeFrame();
            }

            return outStream.toByteArray();
        } catch (BitstreamException e) {
            throw new IOException("Bitstream error: " + e);
        } catch (DecoderException e) {
            throw new IOException("Decoder error: " + e);
        }
    }
Run Code Online (Sandbox Code Playgroud)

这就是它的声音(等待几秒钟):https://vimeo.com/60951237 (这是实际文件:http://www.tonycuffe.com/mp3/tail%20toddle.mp3)

编辑:我本来喜欢将赏金分开,但我已经给了比尔和Nil接受的答案.两者都是一个巨大的帮助.对于那些想知道的人,我最终重写了Sonic本机代码,这有助于我继续前进.

Nei*_*end 4

正如@Bill Pringlemeir 所说,问题是你的转换方法实际上并没有转换。Short 是一个 16 位数字;一个字节是一个 8 位数字。您选择的方法不会转换 Shorts 的内容(即将内容从 16 位转换为 8 位),它会改变相同位集合的存储方式。正如你所说,你需要这样的东西:

SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
byte[] array = MyShortToByte(output.getBuffer());
at.write(array,  0,  array.length);
Run Code Online (Sandbox Code Playgroud)

@Bill Pringlemeir 的方法相当于将所有 Shorts 除以 256 以确保它们适合字节范围:

byte[] MyShortToByte(short[] buffer) {
    int N = buffer.length;
    ByteBuffer byteBuf = ByteBuffer.allocate(N);
    while (N >= i) {
        byte b = (byte)(buffer[i]/256);  /*convert to byte. */
        byteBuf.put(b);
        i++;
    }
    return byteBuf.array();
}
Run Code Online (Sandbox Code Playgroud)

这会起作用,但可能会给你带来非常安静、尖锐的音调。如果您有足够的处理时间,两遍方法可能会给出更好的结果:

byte[] MyShortToByte(short[] buffer) {
    int N = buffer.length;
    short min = 0;
    short max = 0;
    for (int i=0; i<N; i++) {
         if (buffer[i] > max) max = buffer[i];
         if (buffer[i] < min) min = buffer[i];
         }
    short scaling = 1+(max-min)/256; // 1+ ensures we stay within range and guarantee no divide by zero if sequence is pure silence ...

    ByteBuffer byteBuf = ByteBuffer.allocate(N);
    for (int i=0; i<N; i++) {
        byte b = (byte)(buffer[i]/scaling);  /*convert to byte. */
        byteBuf.put(b);
    }
    return byteBuf.array();
}
Run Code Online (Sandbox Code Playgroud)

再次注意签名/未签名问题。以上作品已签名->已签名和未签名->未签名;但不在两者之间。可能您正在读取有符号短裤(-32768-32767),但需要输出无符号字节(0-255),...

如果您可以承受处理时间,更精确(更平滑)的方法是通过浮点数(这也解决了签名/未签名问题):

byte[] MyShortToByte(short[] buffer) {
    int N = buffer.length;
    float f[] = new float[N];
    float min = 0.0f;
    float max = 0.0f;
    for (int i=0; i<N; i++) {
         f[i] = (float)(buffer[i]);
         if (f[i] > max) max = f[i];
         if (f[i] < min) min = f[i];
         }
    float scaling = 1.0f+(max-min)/256.0f; // +1 ensures we stay within range and guarantee no divide by zero if sequence is pure silence ...

    ByteBuffer byteBuf = ByteBuffer.allocate(N);
    for (int i=0; i<N; i++) {
        byte b = (byte)(f[i]/scaling);  /*convert to byte. */
        byteBuf.put(b);
    }
    return byteBuf.array();
}
Run Code Online (Sandbox Code Playgroud)