AudioTrack – преобразование коротких массивов в байтовые массивы с использованием 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 [], используя любой из методов в этом ответе: 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 находились в байтовом массиве для другой сторонней библиотеки, которую я использую. Файл имеет значение 22 кГц, если это имеет значение, и это то, как он создается:

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

Огромное спасибо заранее.

Изменить: Вот как я теперь создаю переменную AudioTrack. Таким образом, для файлов с частотой 44 кГц значение, которое отправляется, составляет 44100, а для файлов с частотой 22 кГц – 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, который помог мне двигаться по этому процессу.

Solutions Collecting From Web of "AudioTrack – преобразование коротких массивов в байтовые массивы с использованием jlayer (java mp3-декодера)"

Как говорит @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(); } 

Опять же, будьте осторожны с подписью / неподписанной проблемой. Вышеуказанные работы были подписаны-> подписаны и без знака-> без знака; Но не между ними. Возможно, вы читаете подписанные шорты (-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(); } 

Проблема заключается в том, что вы конвертируете от short 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 to 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-закона или закона закодированы. Если библиотека 3- й партии использует эти форматы, конверсия отличается, но вы должны иметь возможность кодировать ее из ссылок в wikipedia.

ПРИМЕЧАНИЕ. Я не включил код округления, поскольку overflow и обработка значков затруднят ответ. Вы должны проверить overflow (Ie, 0x8f + 1 дает 0xff или 255 + 1, давая -1). Однако я подозреваю, что библиотека не является прямым 8bit PCM .

См. Также: Обзор Alsa PCM , запись в wiki для мультимедиа на PCM. В конечном итоге Android использует ALSA для звука.

Другими факторами, которые должны быть правильными для необработанного буфера PCM, являются частота дискретизации, количество каналов (стерео / моно), формат PCM, включая биты, компандирование , малое / большое кодирование и чередование образцов.

EDIT: после некоторого исследования декодер JLayer обычно возвращает big endian 16-значные значения. Фильтр Sonic принимает byte но угрожает им как 16- byte little endian под ним. Наконец, класс AudioTrack ожидает 16-битный little endian под ним. Я считаю, что по какой-то причине MP3-декодер JLayer вернет 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 mp3s вы вызываете подпрограмму с swap = true; , Для 22k mp3 swap = false . Этим объясняются все сообщенные явления. Я не знаю, почему в MP3-декодере JLayer иногда выводятся big endian и другие времена, little endian . Я думаю, это зависит от исходного mp3, а не от частоты дискретизации.