/** Copyright 2015 Tim Engler, Rareventure LLC This file is part of Tiny Travel Tracker. Tiny Travel Tracker is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Tiny Travel Tracker is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>. */ /** * */ package com.rareventure.android; import java.io.DataOutputStream; import java.io.IOException; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.SystemClock; import android.util.Log; //PERF: ask for a handler in the constructor, then use onPeriodicMarker or whatever with it public class AudioReaderThread extends ReadThread { private boolean micOn; private Object lock = new Object(); private String tag; private AudioRecord ar = null; private AudioDataBuffer audioDataBuffer; protected boolean toStopReading; private ProcessThread processThread; private int androidInternalBufSize; private AudioProcessor audioProcessor; private boolean isNotifyShutdown; private DataOutputStream os; /** * @param accelAudioRecorder */ public AudioReaderThread(DataOutputStream os, AudioProcessor audioProcessor, AudioDataBuffer audioDataBuffer, String tag, int androidInternalBufSize) { this.audioProcessor = audioProcessor; this.audioDataBuffer = audioDataBuffer; this.tag = tag; this.androidInternalBufSize = androidInternalBufSize; this.os = os; } public void setProcessThread(ProcessThread pt) { this.processThread = pt; } public void run() { boolean readyToRead = false; int bytesRead; long timeRead = 0; while(true) { byte processingMode; //if we are planning to read if(readyToRead) { //if the audiorecord isn't set up yet if(ar == null) { Log.d(this.tag, "Creating new audio record!"); ar = new AudioRecord(MediaRecorder.AudioSource.MIC, audioDataBuffer.sampleFreq, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, androidInternalBufSize); ar.startRecording(); //this is our first read after a break, so the mode is start up processingMode = AudioDataBuffer.PROC_MODE_STARTUP; timeRead = System.currentTimeMillis(); } else processingMode = AudioDataBuffer.PROC_MODE_CONTINUOUS; bytesRead = ar.read(this.audioDataBuffer.data, this.audioDataBuffer.rawReadIndex * this.audioDataBuffer.segmentSize, this.audioDataBuffer.segmentSize); if (bytesRead <= 0) { throw new IllegalStateException("Cannot read buffer: "+bytesRead); } this.audioDataBuffer.bytesRead[this.audioDataBuffer.rawReadIndex] = bytesRead; this.audioDataBuffer.timeRead[this.audioDataBuffer.rawReadIndex] = timeRead; timeRead += bytesRead * 1000 / 2 /* bytes per sample */ / this.audioDataBuffer.sampleFreq; boolean localMicOn; synchronized(lock) { localMicOn = this.micOn; } //if we are no longer on to gather data if(!localMicOn || isNotifyShutdown) { //we are going to have to shutdown ar.release(); ar = null; if(processingMode == AudioDataBuffer.PROC_MODE_CONTINUOUS) processingMode = AudioDataBuffer.PROC_MODE_SHUTDOWN; else processingMode = AudioDataBuffer.PROC_MODE_STARTUP_AND_SHUTDOWN; readyToRead = false; } //now set the processing mode this.audioDataBuffer.processingMode[this.audioDataBuffer.rawReadIndex] = processingMode; //we're done with the block, notify the clients synchronized(this.processThread.lock) { this.audioDataBuffer.updateReadIndex(); this.processThread.lock.notify(); } } //if ready to read else { //not ready to read anything //wait around until either the system is shutdown or we should read synchronized(lock) { while(!this.micOn && !isNotifyShutdown) { try { lock.wait(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } //in this case, micOn is false, so the AudioRecord is already shutdown. Because so, there is nothing to do, //and we can stop now. if(isNotifyShutdown) break; } //now since we didn't break, we know we are starting to read audio readyToRead = true; } //if we were not reading audio }//while true synchronized(lock) { isShutdown = true; lock.notify(); } } //run public void turnOffMic() { synchronized (this.lock) { if(!micOn) return; Log.d(tag,"Mic turn off request "+(System.currentTimeMillis())); micOn = false; lock.notify(); } } public void turnOnMic() { synchronized(this.lock) { if(micOn) return; Log.d(tag,"Mic turn on request "+(System.currentTimeMillis())); micOn = true; lock.notify(); } } @Override public boolean canProcess() { synchronized(this.lock) { return audioDataBuffer.rawProcessIndex != audioDataBuffer.rawReadIndex; } } @Override public void process() { audioProcessor.processAudio(); if(os != null) writeTestData(); } public interface AudioProcessor { void processAudio(); } @Override public void notifyShutdown() { synchronized(this.lock) { this.lock.notify(); } } private void writeTestData() { synchronized(TestUtil.class) { try { TestUtil.writeMode(os, WriteConstants.MODE_WRITE_AUDIO_DATA); TestUtil.writeData("data", os, audioDataBuffer.data, audioDataBuffer.rawProcessIndex * audioDataBuffer.segmentSize, audioDataBuffer.bytesRead[audioDataBuffer.rawProcessIndex]); TestUtil.writeTime("timeRead", os, audioDataBuffer.timeRead[audioDataBuffer.rawProcessIndex]); TestUtil.writeByte("processingMode", os, (byte)audioDataBuffer.processingMode[audioDataBuffer.rawProcessIndex]); } catch(IOException e) { throw new IllegalStateException(e); //this is test code } } } }