我正在使用jLayer解码MP3数据,通过以下调用:
SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
这个返回解码数据的调用返回一个short []数组。
output.getBuffer();
当我使用该方法调用AudioTrack write()时,它在我循环遍历文件时播放正常:
at.write(output.getBuffer(), 0, output.getBuffer().length);
然而,当我使用这个答案中的任何方法将short []数组转换为byte []数组时:https://stackoverflow.com/a/12347176/1176436声音变得扭曲和抖动:
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);
提前非常感谢你。
编辑:这就是我现在实例化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);
这是解码方法:
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)
编辑:我本来喜欢将赏金分开,但我已经给了比尔以及Neil接受的答案。两者都是一个巨大的帮助。对于那些想知道的人,我最终重写了Sonic本机代码,这有助于我继续前进。
答案 0 :(得分:4)
正如@Bill Pringlemeir所说,问题是您的转换方法实际上并没有转换。短路是16位数;一个字节是一个8位数。您选择的方法不会转换短路的内容(即内容从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();
}
再次提防签名/未签名问题。上述作品已签署 - &gt;签名和未签名 - &gt; unsigned;但不是两者之间。可能是您正在读取签名的短路(-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();
}
答案 1 :(得分:3)
问题在于您的 short
到byte
转化。 byte conversion链接会保留所有信息,包括高byte
部分。当您从16位转换为8位PCM样本时,必须丢弃低位字节。我的Java技能很弱,所以以下内容可能不会逐字逐句。另见:short to byte conversion.
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
播放音频,则必须执行反向操作并将8bit PCM
从库转换为16bit PCM
以进行播放。另请注意,通常情况下,8bit
样本通常不直接PCM但u-law或a-law编码。如果3 rd 派对库使用这些格式,则转换会有所不同,但您应该可以从维基百科链接中对其进行编码。
注意:我没有将舍入代码包含在overflow
中,sign
处理会使答案复杂化。您必须检查overflow
(即,0x8f + 1给出0xff或255 + 1给-1)。但是,我怀疑图书馆不是直的8bit PCM
。
另请参阅:Alsa PCM overview,Multi-media wiki entry on PCM - 最终Android会使用 ALSA 来发声。
PCM原始缓冲区必须正确的其他因素包括采样率,通道数(立体声/单声道),PCM格式(包括位),压缩扩展,很少/大端和样本交错。
击>
编辑:经过一些调查,JLayer解码器通常返回big endian
16位值。 Sonic过滤器需要byte
,但会将其威胁为16位little endian
。最后,AudioTrack
类需要16位little endian
。我相信由于某种原因,JLayer
mp3解码器将返回16位little endian
值。问题中的decode()
方法执行16位值的字节交换。此外,发布的音频听起来好像是交换了字节。
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;
调用例程。对于22k mp3 swap = false
。这解释了所有报道的现象。我不知道为什么JLayer
mp3解码器有时输出big endian
,有时输出little endian
。我想这取决于源mp3而不是采样率。