Escribir PCM grabó datos en un archivo.wav(Java android)
audiorecord (4)
Estoy usando AudioRecord para grabar datos PCM de 16 bits en Android. Después de grabar los datos y guardarlos en un archivo, lo leí para guardarlo como archivo .wav.
El problema es que los archivos WAV son reconocidos por los reproductores multimedia, pero no son más que ruido puro. Mi mejor suposición en este momento es que mis encabezados de archivos wav son incorrectos, pero no he podido ver cuál es exactamente el problema. (Creo que esto porque puedo reproducir los datos de PCM en bruto que grabé en Audacity)
Aquí está mi código para leer el archivo PCM sin procesar y guardarlo como .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)};
}
No he incluido getBytesFromFile () ya que ocupa demasiado espacio y es un método probado y comprobado. De todos modos, aquí está el código que hace la grabación real:
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();
}
}
Por favor, sugiera qué podría estar yendo mal.
¿Estás seguro de la orden de bytes? "RIFF", "WAV", "fmt" y "data" se ven bien, pero los números en el encabezado pueden necesitar un orden diferente (little endian vs. big endian). Tampoco necesita convertir a bytes manualmente utilizando su método intToByteArray
. Puede usar los métodos writeInt
y writeShort
de DataOutputStream
. Para el primero, esto sería algo así como:
outFile.writeInt(Integer.reverseBytes((int)myChunkSize));
Para los pantalones cortos sería como:
outFile.writeShort(Short.reverseBytes((short)myFormat))
De esta forma, tampoco es necesario proporcionar los números de desplazamiento y longitud (0, 4)
. Es agradable.
Como Ronald Kunenborg afirma correctamente que el problema es la conversión de Litte Endian / Big Endian.
La forma más sencilla es escribir un pequeño ayudante como este:
public static void writeShortLE(DataOutputStream out, short value) {
out.writeByte(value & 0xFF);
out.writeByte((value >> 8) & 0xFF);
}
Esto es muy útil si graba audio en un archivo de onda con Android y también necesita la matriz corta.
(Créditos: https://.com/a/1394839/1686216 )
He estado luchando con esta misma pregunta durante horas, y mi problema principal era que cuando grabas en 16 bits tienes que tener mucho cuidado con lo que escribes en la salida. El archivo WAV espera los datos en formato Little Endian, pero el uso de writeShort lo escribe en la salida como Big Endian. También obtuve resultados interesantes al usar las otras funciones, así que volví a escribir bytes en el orden correcto y eso funciona.
Usé un editor de Hex extensamente mientras depuraba esto. Te puedo recomendar que hagas lo mismo. Además, el encabezado en la respuesta anterior funciona, lo usé para verificar contra mi propio código y este encabezado es bastante infalible.
Según el encabezado es preocupación, he seguido este código (si te ayuda de alguna manera).
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);