Я читаю файлы WAV через AudioInputStream
.Следующий фрагмент из Java Sound Tutorials работает хорошо.
int totalFramesRead = 0;
File fileIn = new File(somePathName);
// somePathName is a pre-existing string whose value was
// based on a user selection.
try {
AudioInputStream audioInputStream =
AudioSystem.getAudioInputStream(fileIn);
int bytesPerFrame =
audioInputStream.getFormat().getFrameSize();
if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) {
// some audio formats may have unspecified frame size
// in that case we may read any amount of bytes
bytesPerFrame = 1;
}
// Set an arbitrary buffer size of 1024 frames.
int numBytes = 1024 * bytesPerFrame;
byte[] audioBytes = new byte[numBytes];
try {
int numBytesRead = 0;
int numFramesRead = 0;
// Try to read numBytes bytes from the file.
while ((numBytesRead =
audioInputStream.read(audioBytes)) != -1) {
// Calculate the number of frames actually read.
numFramesRead = numBytesRead / bytesPerFrame;
totalFramesRead += numFramesRead;
// Here, do something useful with the audio data that's
// now in the audioBytes array...
}
} catch (Exception ex) {
// Handle the error...
}
} catch (Exception e) {
// Handle the error...
}
Чтобы написать WAV, я обнаружил, что это довольно сложно.На первый взгляд это кажется круговой проблемой: команда, которая записывает, использует AudioInputStream
в качестве параметра.
Но как записать байты в AudioInputStream
?Разве не должно быть AudioOutputStream
?
Я обнаружил, что можно определить объект, который имеет доступ к необработанным аудиобайтовым данным для реализации TargetDataLine
.
Для этого требуетсяМногие методы могут быть реализованы, но большинство из них могут оставаться в фиктивной форме, поскольку они не требуются для записи данных в файл.Ключевой метод для реализации - read(byte[] buffer, int bufferoffset, int numberofbytestoread)
.
Поскольку этот метод, вероятно, будет вызываться несколько раз, должна также существовать переменная экземпляра, которая указывает, как далеко продвигаются данные, и обновлять ее как часть вышеуказанного метода read
.
Когда вы реализовали этот метод, ваш объект можно использовать для создания нового AudioInputStream
, который, в свою очередь, можно использовать с:
AudioSystem.write(yourAudioInputStream, AudioFileFormat.WAV, yourFileDestination)
В качестве напоминания, AudioInputStream
может быть создан с TargetDataLine
в качестве источника.
Что касается прямого манипулирования данными, я добился большого успеха, воздействуя на данные в буфере в самом внутреннем цикле фрагментапример выше, audioBytes
.
Пока вы находитесь в этом внутреннем цикле, вы можете преобразовать байты в целые или с плавающей точкой и умножить значение volume
(в диапазоне от 0.0
до 1.0
), а затемпреобразовать их обратно в байты с прямым порядком байтов.
Я полагаю, поскольку у вас есть доступ к серии семплов в этом буфере, вы также можете задействовать различные формы алгоритмов фильтрации DSP на этом этапе.По своему опыту я обнаружил, что изменения объема лучше выполнять непосредственно в данных в этом буфере, потому что тогда вы можете сделать наименьшее возможное приращение: одна дельта на выборку, сводя к минимуму вероятность щелчков из-за разрывов, вызванных объемом.
Я нахожу, что «контрольные линии» для тома, предоставляемые Java, склонны к ситуациям, когда скачки громкости вызывают щелчки, и я считаю, что это связано с тем, что дельты реализуются только при гранулярности чтения из одного буфера (часто в пределах одного изменения на 1024 образца), а не делят изменение на более мелкие части и добавляют их по одному на образец.Но я не знаком с тем, как были реализованы средства управления громкостью, поэтому, пожалуйста, примите эту гипотезу с недоверием.
В общем, Java.Sound был настоящей головной болью, чтобы понять.Я виноват в том, что Учебное пособие не содержит явного примера записи файла непосредственно из байтов.Я виноват в том, что Учебник хоронит лучший пример кодирования Play a File в разделе «Как конвертировать ...».Тем не менее, в этом уроке очень много БЕСПЛАТНОЙ информации.
РЕДАКТИРОВАТЬ: 12/13/17
С тех пор я использовал следующий код для записи аудио из файла PCM в моих собственных проектах.Вместо реализации TargetDataLine
можно расширить InputStream
и использовать это в качестве параметра для метода AudioInputStream.write
.
public class StereoPcmInputStream extends InputStream
{
private float[] dataFrames;
private int framesCounter;
private int cursor;
private int[] pcmOut = new int[2];
private int[] frameBytes = new int[4];
private int idx;
private int framesToRead;
public void setDataFrames(float[] dataFrames)
{
this.dataFrames = dataFrames;
framesToRead = dataFrames.length / 2;
}
@Override
public int read() throws IOException
{
while(available() > 0)
{
idx &= 3;
if (idx == 0) // set up next frame's worth of data
{
framesCounter++; // count elapsing frames
// scale to 16 bits
pcmOut[0] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);
pcmOut[1] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);
// output as unsigned bytes, in range [0..255]
frameBytes[0] = (char)pcmOut[0];
frameBytes[1] = (char)(pcmOut[0] >> 8);
frameBytes[2] = (char)pcmOut[1];
frameBytes[3] = (char)(pcmOut[1] >> 8);
}
return frameBytes[idx++];
}
return -1;
}
@Override
public int available()
{
// NOTE: not concurrency safe.
// 1st half of sum: there are 4 reads available per frame to be read
// 2nd half of sum: the # of bytes of the current frame that remain to be read
return 4 * ((framesToRead - 1) - framesCounter)
+ (4 - (idx % 4));
}
@Override
public void reset()
{
cursor = 0;
framesCounter = 0;
idx = 0;
}
@Override
public void close()
{
System.out.println(
"StereoPcmInputStream stopped after reading frames:"
+ framesCounter);
}
}
Исходные данные, которые будут экспортированы здесь, представлены в виде поплавков стерео, варьирующихся отОт -1 до 1. Формат результирующего потока - 16-битный, стереофонический, с прямым порядком байтов.
Я опустил методы skip
и markSupported
для моего конкретного приложения.Но добавить их, если они нужны, не составит труда.