AudioTrack – 短arrays到字节数组使用jlayer(java mp3解码器)

我正在使用jLayer来解码MP3数据,这个调用:

SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream); 

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

当我用这个方法调用AudioTrack的write()时,它在我遍历文件的时候播放的很好:

 at.write(output.getBuffer(), 0, output.getBuffer().length); 

但是,当我使用这个答案中的任何方法将short []数组转换为byte []数组时:声音变得扭曲和不安:

 at.write(output.getBuffer(), 0, output.getBuffer().length); 

变为:

 byte[] array = ShortToByte_Twiddle_Method(output.getBuffer()); at.write(array, 0, array.length); 

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

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

非常感谢你提前。

编辑:这是我现在正在实例化AudioTrackvariables。 因此,对于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); 

这是解码方法:

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

这是听起来的(等几秒钟): https : //vimeo.com/60951237 (这是实际的文件: http : //www.tonycuffe.com/mp3/tail%20toddle.mp3 )

编辑:我会喜欢分裂的赏金,而是我已经给了比尔和尼尔公认的答案的赏金。 两者都是巨大的帮助。 对于那些想知道的,我最终重写了Sonic本机代码,这帮助我沿着这个过程前进。

Related of "AudioTrack – 短arrays到字节数组使用jlayer(java mp3解码器)"

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

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

@Bill Pringlemeir的方法相当于把所有的短路分成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(); } 

这将起作用,但可能会给你非常安静,前卫的音调。 如果你能负担得起的处理时间,双通道的方法可能会带来更好的结果:

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

再次请注意签署/未签名的问题。 上述工作签署 – >签名和无符号 – >无符号; 但不在两者之间。 这可能是你正在读签名短裤(-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(); } 

问题是你的shortbyte转换。 字节转换链接保留包括高byte和低byte部分的所有信息。 当你从16位转换到8位PCM采样时,你必须丢弃低位字节。 我的Java技能很弱,所以以下可能无法正常工作。 另请参见: 短到字节转换。

 ByteBuffer byteBuf = ByteBuffer.allocate(N); while (N >= i) { /* byte b = (byte)((buffer[i]>>8)&0xff); convert to byte. native endian */ byte b = (byte)(buffer[i]&0xff); /*convert to byte; swapped endian. */ byteBuf.put(b); i++; } 

这是以下转换,

  AAAA AAAA SBBB BBBB -> AAAA AAAA, +1 if S==1 and positive else -1 if S==1 

A有点被保留。 B是丢弃位, S是你可能希望用于舍入的位。 四舍五入是不必要的,但听起来好一点。 基本上,16位PCM比8位PCM的分辨率更高。 转换完成后,您将丢失这些位。 short byte例程尝试保留所有信息。

当然,你必须告诉声音库你正在使用8-bit PCM 。 我猜,

 at = new AudioTrack(AudioManager.STREAM_MUSIC, 22050, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_8BIT, 10000 /* 10 second buffer */, AudioTrack.MODE_STREAM); 

如果只能使用16bit PCM播放audio,则必须进行反转,将8bit PCM从库中转换为16bit PCM进行播放。 还要注意的是,通常情况下, 8bit采样通常不是直接的PCM,而是u律或a律编码的。 如果第三方库使用这些格式,转换是不同的,但你应该能够从维基百科链接编码。

注:我没有包含舍入代码overflowsign处理将复杂的答案。 你必须检查overflow (即,0x8f + 1给出0xff或255 + 1给-1)。 不过,我怀疑这个库不是直8bit PCM

另请参阅: Alsa PCM概述 , PCM上 的多媒体wiki条目 – 最终,Android使用ALSA来创build声音。

对于PCM原始缓冲区必须正确的其他因素是采样率,通道数(立体声/单声道),PCM格式(包括比特), 压缩扩展 ,小/大端和样本交织。

编辑:经过一番调查,JLayer解码器通常返回big endian 16位值。 声波滤波器 ,占用一个byte但威胁他们为16bit的little endian在下面。 最后, AudioTrack类预计在下面的16位little endian 。 我相信,由于某种原因, JLayer mp3解码器将返回16bit的little endian值。 问题中的decode()方法执行16位值的字节交换。 此外,发布的audio听起来好像字节交换。

 public byte[] decode(InputStream inputStream, int startMs, int maxMs, bool swap) throws IOException { ... short[] pcm = output.getBuffer(); for (short s : pcm) { if(swap) { outStream.write(s & 0xff); outStream.write((s >> 8) & 0xff); } else { outStream.write((s >> 8) & 0xff); outStream.write(s & 0xff); } } ... 

对于44k的mp3,你可以调用swap = true;的例程swap = true; 。 对于22k mp3 swap = false 。 这解释了所有报道的现象。 我不知道为什么JLayer mp3解码器有时会输出big endian而其他的时候会输出little endian 。 我想这取决于源mp3而不是采样率。