Запись PCM записанных данных в WAV файл (java android)
Я использую AudioRecord для записи 16-битных данных PCM в android. После записи данных и сохранения их в файл я прочитал его, чтобы сохранить его как .wav файл.
Проблема в том, что WAV файлы распознаются медиаплеерами, но не играют ничего, кроме чистого шума. Мое лучшее предположение на данный момент состоит в том, что мои заголовки wav файлов неверны, но я не мог понять, в чем проблема. (Я думаю, это потому, что я могу воспроизвести необработанные данные PCM, записанные мной в Audacity)
Здесь мой код для чтения исходного файла PCM и сохранения его как .wav:
private void properWAV(File fileToConvert, float newRecordingID){
try {
long mySubChunk1Size = 16;
int myBitsPerSample= 16;
int myFormat = 1;
long myChannels = 1;
long mySampleRate = 22100;
long myByteRate = mySampleRate * myChannels * myBitsPerSample/8;
int myBlockAlign = (int) (myChannels * myBitsPerSample/8);
byte[] clipData = getBytesFromFile(fileToConvert);
long myDataSize = clipData.length;
long myChunk2Size = myDataSize * myChannels * myBitsPerSample/8;
long myChunkSize = 36 + myChunk2Size;
OutputStream os;
os = new FileOutputStream(new File("/sdcard/onefile/assessor/OneFile_Audio_"+ newRecordingID+".wav"));
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream outFile = new DataOutputStream(bos);
outFile.writeBytes("RIFF"); // 00 - RIFF
outFile.write(intToByteArray((int)myChunkSize), 0, 4); // 04 - how big is the rest of this file?
outFile.writeBytes("WAVE"); // 08 - WAVE
outFile.writeBytes("fmt "); // 12 - fmt
outFile.write(intToByteArray((int)mySubChunk1Size), 0, 4); // 16 - size of this chunk
outFile.write(shortToByteArray((short)myFormat), 0, 2); // 20 - what is the audio format? 1 for PCM = Pulse Code Modulation
outFile.write(shortToByteArray((short)myChannels), 0, 2); // 22 - mono or stereo? 1 or 2? (or 5 or ???)
outFile.write(intToByteArray((int)mySampleRate), 0, 4); // 24 - samples per second (numbers per second)
outFile.write(intToByteArray((int)myByteRate), 0, 4); // 28 - bytes per second
outFile.write(shortToByteArray((short)myBlockAlign), 0, 2); // 32 - # of bytes in one sample, for all channels
outFile.write(shortToByteArray((short)myBitsPerSample), 0, 2); // 34 - how many bits in a sample(number)? usually 16 or 24
outFile.writeBytes("data"); // 36 - data
outFile.write(intToByteArray((int)myDataSize), 0, 4); // 40 - how big is this data chunk
outFile.write(clipData); // 44 - the actual data itself - just a long string of numbers
outFile.flush();
outFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static byte[] intToByteArray(int i)
{
byte[] b = new byte[4];
b[0] = (byte) (i & 0x00FF);
b[1] = (byte) ((i >> 8) & 0x000000FF);
b[2] = (byte) ((i >> 16) & 0x000000FF);
b[3] = (byte) ((i >> 24) & 0x000000FF);
return b;
}
// convert a short to a byte array
public static byte[] shortToByteArray(short data)
{
/*
* NB have also tried:
* return new byte[]{(byte)(data & 0xff),(byte)((data >> 8) & 0xff)};
*
*/
return new byte[]{(byte)(data & 0xff),(byte)((data >>> 8) & 0xff)};
}
Я не включил getBytesFromFile(), поскольку он занимает слишком много места и его проверенный и проверенный метод.
В любом случае, здесь код, который выполняет фактическую запись:
public void run() {
Log.i("ONEFILE", "Starting main audio capture loop...");
int frequency = 22100;
int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
final int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);
audioRecord.startRecording();
ByteArrayOutputStream recData = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(recData);
short[] buffer = new short[bufferSize];
audioRecord.startRecording();
while (!stopped) {
int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
for(int i = 0; i < bufferReadResult;i++) {
try {
dos.writeShort(buffer[i]);
} catch (IOException e) {
e.printStackTrace();
}
}
}
audioRecord.stop();
try {
dos.flush();
dos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
audioRecord.stop();
byte[] clipData = recData.toByteArray();
File file = new File(audioOutputPath);
if(file.exists())
file.delete();
file = new File(audioOutputPath);
OutputStream os;
try {
os = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream outFile = new DataOutputStream(bos);
outFile.write(clipData);
outFile.flush();
outFile.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Пожалуйста, подскажите, что может быть неправильным.
Ответы
Ответ 1
Я боролся с этим тем же вопросом в течение нескольких часов, и моя проблема в основном заключалась в том, что при записи в 16 бит вы должны быть очень осторожны с тем, что вы пишете на выходе. WAV файл ожидает данные в формате Little Endian, но с помощью writeShort записывает его на выходе в виде Big Endian. У меня также были интересные результаты при использовании других функций, поэтому я вернулся к написанию байтов в правильном порядке и работает.
Во время отладки я использовал редактор Hex. Я могу порекомендовать вам сделать то же самое. Кроме того, заголовок в ответе выше работает, я использовал его для проверки по сравнению с моим собственным кодом, и этот заголовок довольно надежный.
Ответ 2
В соответствии с заголовком проблема связана с этим кодом (если это поможет вам каким-то образом).
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = RECORDER_BPP; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
Ответ 3
Вы уверены в порядке байтов? "RIFF", "WAV", "fmt" и "data" выглядят отлично, но числа в заголовке могут потребоваться для другого порядка (маленький конец и большой endian). Вам также не нужно вручную преобразовывать в байты, используя метод intToByteArray
. Вы можете использовать методы writeInt
и writeShort
DataOutputStream
. Для первого это выглядело бы примерно так:
outFile.writeInt(Integer.reverseBytes((int)myChunkSize));
Для шорт это будет выглядеть так:
outFile.writeShort(Short.reverseBytes((short)myFormat))
Таким образом, вам также не нужно предоставлять номера смещения и длины (0, 4)
. Это приятно.
Ответ 4
Как правильно утверждает Рональд Куненборг, проблема заключается в преобразовании Litte Endian/Big Endian.
Самый простой способ - написать короткий помощник, подобный этому:
public static void writeShortLE(DataOutputStream out, short value) {
out.writeByte(value & 0xFF);
out.writeByte((value >> 8) & 0xFF);
}
Это очень полезно, если вы записываете звук в волновой файл с Android, и вам также нужен короткий массив.
(Кредиты: fooobar.com/info/169189/...)