Как записать звук на Java в byte массив
Есть много задач, в которых может понадобиться передать звук в виде массива байтов через сеть. Java сильно отстала в этом вопросе и давно не развивала свой API для работы со звуком. Это вызывает трудности при решение простых на первый взгляд вопросов.
На сайте уже есть пример про запись звука с помощью Java. Пример простой и пишет звук только в файл.
AudioSystem.write(m_audioInputStream, m_targetType, m_outputFile);
Для упрощение задачи можно просто прочитать потом файл в byte массив, но метод «AudioSystem.write» может вызываться и с stream в качестве последнего аргумента. Это бы сразу решило проблему, но не так всё просто. При попытке вызвать метод «write» с каким-либо stream происходит ошибка: stream length not specified.
AudioSystem.write(audio, AudioFileFormat::Type::WAVE, process.getOutputStream());
Это связано с тем, что запись идёт в WAVE формате, который требует в заголовке файла размер записанных данных. Этот размер невозможно узнать до окончания записи, что и вызывает ошибку.
Проблему можно решить, если самостоятельно реализовать AudioSystem.write для WAVE формата.
Начать нужно с класса, который будет отвечать за формирование корректного заголовка WAVE формата:
package info.privateblog.sound.wave; import java.io.ByteArrayOutputStream; import java.io.IOException; public class NewWaveWriter { private ByteArrayOutputStream outStream = new ByteArrayOutputStream(); private int mSampleRate; private int mChannels; private int mSampleBits; private int mBytesWritten; public NewWaveWriter(int sampleRate) throws IOException { this.mSampleRate = sampleRate; this.mChannels = 2; this.mSampleBits = 16; this.mBytesWritten = 0; outStream.write(new byte[44]); } public void write(byte[] src, int offset, int length) throws IOException { if (offset > length) { throw new IndexOutOfBoundsException(String.format("offset %d is greater than length %d", offset, length)); } for (int i = offset; i < length; i+=4) { writeUnsignedShortLE(src[i], src[i+1]); writeUnsignedShortLE(src[i + 2], src[i + 3]); mBytesWritten += 4; } } public byte[] getByteBuffer() throws IOException { byte[]result = outStream.toByteArray(); writeWaveHeader(result); return result; } private void writeWaveHeader(byte[]file) throws IOException { int bytesPerSec = (mSampleBits + 7) / 8; int position = 0; position = setValue(file, position, "RIFF"); position = setValue(file, position, mBytesWritten + 36); position = setValue(file, position, "WAVE"); position = setValue(file, position, "fmt "); position = setValue(file, position, (int)16); position = setValue(file, position, (short) 1); position = setValue(file, position, (short) mChannels); position = setValue(file, position, mSampleRate); position = setValue(file, position, mSampleRate * mChannels * bytesPerSec); position = setValue(file, position, (short) (mChannels * bytesPerSec)); position = setValue(file, position, (short) mSampleBits); position = setValue(file, position, "data"); position = setValue(file, position, mBytesWritten); } private void writeUnsignedShortLE(byte sample1, byte sample2) throws IOException { outStream.write(sample1); outStream.write(sample2); } private static int setValue(byte[]buffer, int position, String value) { for (int i = 0; i< value.length(); i++) { buffer[position + i] = (byte)value.charAt(i); } return position + value.length(); } private static int setValue(byte[]buffer, int position, int value) { buffer[position + 3] = (byte)(value>>24); buffer[position + 2] = (byte)(value>>16); buffer[position + 1] = (byte)(value>>8); buffer[position + 0] = (byte)(value); return position + 4; } private static int setValue(byte[]buffer, int position, short value) { buffer[position + 1] = (byte)(value>>8); buffer[position] = (byte)value; return position + 2; } }
Данный класс работает только с двухканальным 16-битным звуком. Он предоставляет нам метод write и getByteBuffer и решает проблему с получением byte массива с записанным звуком без использования промежуточного файла.
Пример использования:
public void run() { try { writer = new NewWaveWriter(44100); byte[]buffer = new byte[256]; int res = 0; while((res = m_audioInputStream.read(buffer)) > 0) { writer.write(buffer, 0, res); } } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } public byte[]getResult() throws IOException { return writer.getByteBuffer(); }