diff --git a/app/src/main/java/com/github/axet/audiorecorder/activities/RecordingActivity.java b/app/src/main/java/com/github/axet/audiorecorder/activities/RecordingActivity.java index 69373df..0435da5 100644 --- a/app/src/main/java/com/github/axet/audiorecorder/activities/RecordingActivity.java +++ b/app/src/main/java/com/github/axet/audiorecorder/activities/RecordingActivity.java @@ -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; diff --git a/app/src/main/java/com/github/axet/audiorecorder/encoders/Encoder.java b/app/src/main/java/com/github/axet/audiorecorder/encoders/Encoder.java index 04ea7d2..38930c0 100644 --- a/app/src/main/java/com/github/axet/audiorecorder/encoders/Encoder.java +++ b/app/src/main/java/com/github/axet/audiorecorder/encoders/Encoder.java @@ -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(); } diff --git a/app/src/main/java/com/github/axet/audiorecorder/encoders/FileEncoder.java b/app/src/main/java/com/github/axet/audiorecorder/encoders/FileEncoder.java index fefbf31..886d13d 100644 --- a/app/src/main/java/com/github/axet/audiorecorder/encoders/FileEncoder.java +++ b/app/src/main/java/com/github/axet/audiorecorder/encoders/FileEncoder.java @@ -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(); diff --git a/app/src/main/java/com/github/axet/audiorecorder/encoders/Wav.java b/app/src/main/java/com/github/axet/audiorecorder/encoders/Wav.java index 7208d33..cac5efd 100755 --- a/app/src/main/java/com/github/axet/audiorecorder/encoders/Wav.java +++ b/app/src/main/java/com/github/axet/audiorecorder/encoders/Wav.java @@ -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; }