fix wav format

This commit is contained in:
Alexey Kuznetsov 2016-03-11 17:08:57 +03:00
commit c65b1277a0
4 changed files with 97 additions and 151 deletions

View file

@ -347,14 +347,26 @@ public class RecordingActivity extends AppCompatActivity {
AudioRecord recorder = null;
try {
File tmp = storage.getTempRecording();
final long s = getSamples(tmp);
handle.post(new Runnable() {
@Override
public void run() {
samples = 0;
addSamples(s);
{
long ss = tmp.length();
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
ss = ss / 2;
}
});
if (channelConfig == AudioFormat.CHANNEL_IN_STEREO) {
ss = ss / 2;
}
final long s = ss;
handle.post(new Runnable() {
@Override
public void run() {
samples = 0;
addSamples(s);
}
});
}
os = new DataOutputStream(storage.open(tmp));
int min = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
@ -524,18 +536,6 @@ public class RecordingActivity extends AppCompatActivity {
}
}
long getSamples(File in) {
long samples = in.length();
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
samples = samples / 2;
}
if (channelConfig == AudioFormat.CHANNEL_IN_STEREO) {
samples = samples / 2;
}
return samples;
}
EncoderInfo getInfo() {
final int channels = channelConfig == AudioFormat.CHANNEL_IN_STEREO ? 2 : 1;
final int bps = audioFormat == AudioFormat.ENCODING_PCM_16BIT ? 16 : 8;

View file

@ -3,5 +3,7 @@ package com.github.axet.audiorecorder.encoders;
public interface Encoder {
public EncoderInfo getInfo();
public void encode(byte[] buf, int offset, int len);
public void encode(short[] buf);
public void close();
}

View file

@ -9,10 +9,9 @@ import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Created by axet on 11/03/16.
*/
public class FileEncoder {
Context context;
Handler handler;
@ -40,8 +39,7 @@ public class FileEncoder {
thread = new Thread(new Runnable() {
@Override
public void run() {
samples = in.length();
samples = getSamples(samples);
samples = getSamples(in.length());
cur = 0;
@ -50,7 +48,7 @@ public class FileEncoder {
is = new FileInputStream(in);
while (!Thread.currentThread().isInterrupted()) {
byte[] buf = new byte[info.channels * (info.bps / 8) * 100];
byte[] buf = new byte[info.channels * info.bps / 8 * 100];
int len = is.read(buf);
if (len <= 0) {
@ -58,7 +56,9 @@ public class FileEncoder {
return;
}
if (len > 0) {
encoder.encode(buf, 0, len);
short[] shorts = new short[len / 2];
ByteBuffer.wrap(buf, 0, len).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(shorts);
encoder.encode(shorts);
handler.post(progress);
synchronized (thread) {
cur += getSamples(len);
@ -69,6 +69,7 @@ public class FileEncoder {
t = e;
handler.post(error);
} finally {
encoder.close();
if (is != null) {
try {
is.close();

View file

@ -1,101 +1,21 @@
package com.github.axet.audiorecorder.encoders;
// Based on "wav IO based on code by Evan Merz"
// based on http://soundfile.sapp.org/doc/WaveFormat/
// Wav file format parsed by Evan X. Merz
// www.thisisnotalabel.com
// Example Wav file input and output
// this was written for educational purposes, but feel free to use it for anything you like
// as long as you credit me appropriately ("wav IO based on code by Evan Merz")
// if you catch any bugs in this, or improve upon it significantly, send me the changes
// at evan at thisisnotalabel dot com, so we can share your changes with the world
// http://computermusicblog.com/blog/2008/08/29/reading-and-writing-wav-files-in-java/
import android.util.Log;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Wav implements Encoder {
/*
WAV File Specification
FROM http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
The canonical WAVE format starts with the RIFF header:
0 4 ChunkID Contains the letters "RIFF" in ASCII form
(0x52494646 big-endian form).
4 4 ChunkSize 36 + SubChunk2Size, or more precisely:
4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
This is the size of the rest of the chunk
following this number. This is the size of the
entire file in bytes minus 8 bytes for the
two fields not included in this count:
ChunkID and ChunkSize.
8 4 Format Contains the letters "WAVE"
(0x57415645 big-endian form).
The "WAVE" format consists of two subchunks: "fmt " and "data":
The "fmt " subchunk describes the sound data's format:
12 4 Subchunk1ID Contains the letters "fmt "
(0x666d7420 big-endian form).
16 4 Subchunk1Size 16 for PCM. This is the size of the
rest of the Subchunk which follows this number.
20 2 AudioFormat PCM = 1 (i.e. Linear quantization)
Values other than 1 indicate some
form of compression.
22 2 NumChannels Mono = 1, Stereo = 2, etc.
24 4 SampleRate 8000, 44100, etc.
28 4 ByteRate == SampleRate * NumChannels * BitsPerSample/8
32 2 BlockAlign == NumChannels * BitsPerSample/8
The number of bytes for one sample including
all channels. I wonder what happens when
this number isn't an integer?
34 2 BitsPerSample 8 bits = 8, 16 bits = 16, etc.
The "data" subchunk contains the size of the data and the actual sound:
36 4 Subchunk2ID Contains the letters "data"
(0x64617461 big-endian form).
40 4 Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8
This is the number of bytes in the data.
You can also think of this as the size
of the read of the subchunk following this
number.
44 * Data The actual sound data.
NOTE TO READERS:
The thing that makes reading wav files tricky is that java has no unsigned types. This means that the
binary data can't just be read and cast appropriately. Also, we have to use larger types
than are normally necessary.
In many languages including java, an integer is represented by 4 bytes. The issue here is
that in most languages, integers can be signed or unsigned, and in wav files the integers
are unsigned. So, to make sure that we can store the proper values, we have to use longs
to hold integers, and integers to hold shorts.
Then, we have to convert back when we want to save our wav data.
It's complicated, but ultimately, it just results in a few extra functions at the bottom of
this file. Once you understand the issue, there is no reason to pay any more attention
to it.
ALSO:
This code won't read ALL wav files. This does not use to full specification. It just uses
a trimmed down version that most wav files adhere to.
*/
long NumSamples;
int NumSamples;
EncoderInfo info;
int BytesPerSample;
RandomAccessFile outFile;
// I made this public so that you can toss whatever you want in here
// maybe a recorded buffer, maybe just whatever you want
public byte[] myData;
ByteOrder order = ByteOrder.LITTLE_ENDIAN;
// empty constructor
public Wav() {
}
@ -115,42 +35,80 @@ a trimmed down version that most wav files adhere to.
}
public void save() {
long SubChunk2Size = NumSamples * info.channels * BytesPerSample;
int SubChunk1Size = 16;
int SubChunk2Size = NumSamples * info.channels * BytesPerSample;
int ChunkSize = 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size);
long SubChunk1Size = 16;
int BlockAlign = info.bps * info.channels;
short Format = 1;
write("RIFF", ByteOrder.BIG_ENDIAN);
write(ChunkSize, order);
write("WAVE", ByteOrder.BIG_ENDIAN);
int ByteRate = info.sampleRate * info.channels * BytesPerSample;
short AudioFormat = 1; // PCM = 1 (i.e. Linear quantization)
int BlockAlign = BytesPerSample * info.channels;
long ChunkSize = 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size);
write("fmt ", ByteOrder.BIG_ENDIAN);
write(SubChunk1Size, order);
write((short)AudioFormat, order); //short
write((short) info.channels, order); // short
write(info.sampleRate, order);
write(ByteRate, order);
write((short)BlockAlign, order); // short
write((short)info.bps, order); // short
write("data", ByteOrder.BIG_ENDIAN);
write(SubChunk2Size, order);
}
void write(String str, ByteOrder order) {
try {
// write the wav file per the wav file format
outFile.writeBytes("RIFF"); // 00 - RIFF
outFile.write(intToByteArray((int) ChunkSize), 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) SubChunk1Size), 0, 4); // 16 - size of this chunk
outFile.write(shortToByteArray((short) Format), 0, 2); // 20 - what is the audio format? 1 for PCM = Pulse Code Modulation
outFile.write(shortToByteArray((short) info.channels), 0, 2); // 22 - mono or stereo? 1 or 2? (or 5 or ???)
outFile.write(intToByteArray((int) info.sampleRate), 0, 4); // 24 - samples per second (numbers per second)
outFile.write(intToByteArray((int) ByteRate), 0, 4); // 28 - bytes per second
outFile.write(shortToByteArray((short) BlockAlign), 0, 2); // 32 - # of bytes in one sample, for all channels
outFile.write(shortToByteArray((short) info.bps), 0, 2); // 34 - how many bits in a sample(number)? usually 16 or 24
outFile.writeBytes("data"); // 36 - data
outFile.write(intToByteArray((int) SubChunk2Size), 0, 4); // 40 - how big is this data chunk
byte[] cc = str.getBytes("UTF-8");
ByteBuffer bb = ByteBuffer.allocate(cc.length);
bb.order(order);
bb.put(cc);
bb.flip();
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void encode(byte[] buf, int o, int l) {
NumSamples += buf.length / info.channels / BytesPerSample;
void write(int i, ByteOrder order) {
ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE);
bb.order(order);
bb.putInt(i);
bb.flip();
try {
outFile.write(buf, o, l); // 44 - the actual data itself - just a long string of numbers
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void write(short i, ByteOrder order) {
ByteBuffer bb = ByteBuffer.allocate(Short.SIZE / Byte.SIZE);
bb.order(order);
bb.putShort(i);
bb.flip();
try {
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void encode(short[] buf) {
NumSamples += buf.length / info.channels;
try {
ByteBuffer bb = ByteBuffer.allocate(buf.length * (Short.SIZE / Byte.SIZE));
bb.order(order);
for (int i = 0; i < buf.length; i++)
bb.putShort(buf[i]);
bb.flip();
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -166,21 +124,6 @@ a trimmed down version that most wav files adhere to.
}
}
// returns a byte array of length 4
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) {
return new byte[]{(byte) (data & 0xff), (byte) ((data >>> 8) & 0xff)};
}
public EncoderInfo getInfo() {
return info;
}