/* * TODO pr�fen, ob wir diese Datei auf GPLv3 upgraden k�nnen * * org.hermit.android.io: Android utilities for accessing peripherals. * * These classes provide some basic utilities for accessing the audio * interface, at present. * * <br>Copyright 2009 Ian Cameron Smith * * <p>This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation (see COPYING). * * <p>This program 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. */ //package org.hermit.android.io; package org.cbase.blinkendroid.audio; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.util.Log; /** * A class which reads audio input from the mic in a background thread and * passes it to the caller when ready. * * <p> * To use this class, your application must have permission RECORD_AUDIO. */ public class AudioReader implements Runnable { // ******************************************************************** // // Class Data. // ******************************************************************** // // Debugging tag. @SuppressWarnings("unused") private static final String TAG = "WindMeter"; // ******************************************************************** // // Private Data. // ******************************************************************** // /** Our audio input device. */ private AudioRecord audioInput; /** Our audio input buffer. */ private short[][] inputBuffer = null; /** The index of the current item in the buffer.*/ private int inputBufferWhich = 0; /** The index of the next item to go in.*/ private int inputBufferIndex = 0; /** Size of the block to read each time. */ private int inputBlockSize = 0; /** Time in ms to sleep between blocks, to meter the supply rate.*/ private long sleepTime = 0; /** Listener for input. */ private Listener inputListener = null; /** Flag whether the thread should be running. */ private boolean running = false; /** The thread, if any, which is currently reading. Null if not running. */ private Thread readerThread = null; // ******************************************************************** // // Public Classes. // ******************************************************************** // /** * Listener for audio reads. */ public static abstract class Listener { /** * An audio read has completed. * * @param buffer * Buffer containing the data. */ public abstract void onReadComplete(short[] buffer); } // ******************************************************************** // // Constructor. // ******************************************************************** // /** * Create an AudioReader instance. */ public AudioReader() { // audioManager = (AudioManager) // app.getSystemService(Context.AUDIO_SERVICE); } // ******************************************************************** // // Run Control. // ******************************************************************** // /** * Start this reader. * * @param rate * The audio sampling rate, in samples / sec. * @param block * Number of samples of input to read at a time. This is * different from the system audio buffer size. * @param listener * Listener to be notified on each completed read. */ public void startReader(int rate, int block, Listener listener) { Log.i(TAG, "Reader: Start Thread"); synchronized (this) { // Calculate the required I/O buffer size. int audioBuf = AudioRecord.getMinBufferSize(rate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT) * 2; // Set up the audio input. audioInput = new AudioRecord(MediaRecorder.AudioSource.MIC, rate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, audioBuf); inputBlockSize = block; sleepTime = (long) (1000f / ((float) rate / (float) block)); inputBuffer = new short[2][inputBlockSize]; inputBufferWhich = 0; inputBufferIndex = 0; inputListener = listener; running = true; readerThread = new Thread(this, "Audio Reader"); readerThread.start(); } } /** * Start this reader. */ public void stopReader() { Log.i(TAG, "Reader: Signal Stop"); synchronized (this) { running = false; } try { if (readerThread != null) readerThread.join(); } catch (InterruptedException e) { ; } readerThread = null; // Kill the audio input. synchronized (this) { if (audioInput != null) { audioInput.release(); audioInput = null; } } Log.i(TAG, "Reader: Thread Stopped"); } // ******************************************************************** // // Main Loop. // ******************************************************************** // /** * Main loop of the audio reader. This runs in our own thread. * * <p> * <b>Note:</b> this method is public because the Thread API requires it. * Outside classes must not call this. */ public void run() { short[] buffer; int index, readSize; try { Log.i(TAG, "Reader: Get state"); int astate = audioInput.getState(); Log.i(TAG, "Reader: Start Recording in " + astate); audioInput.startRecording(); Log.i(TAG, "Reader: Start Recording (" + astate + " -> " + audioInput.getState() + ")"); while (running) { long stime = System.currentTimeMillis(); if (!running) break; readSize = inputBlockSize; int space = inputBlockSize - inputBufferIndex; if (readSize > space) readSize = space; buffer = inputBuffer[inputBufferWhich]; index = inputBufferIndex; synchronized (buffer) { int nread = audioInput.read(buffer, index, readSize); boolean done = false; if (!running) break; if (nread < 0) { Log.e(TAG, "Audio read failed: error " + nread); running = false; break; } int end = inputBufferIndex + nread; if (end >= inputBlockSize) { inputBufferWhich = (inputBufferWhich + 1) % 2; inputBufferIndex = 0; done = true; } else inputBufferIndex = end; if (done) { readDone(buffer); // Because our block size is way smaller than the audio // buffer, we get blocks in bursts, which messes up // the audio analyzer. We don't want to be forced to // wait until the analysis is done, because if // the analysis is slow, lag will build up. Instead // wait, but with a timeout which lets us keep the // input serviced. long etime = System.currentTimeMillis(); long sleep = sleepTime - (etime - stime); if (sleep < 5) sleep = 5; try { buffer.wait(sleep); } catch (InterruptedException e) { } } } } } finally { Log.i(TAG, "Reader: Stop Recording"); if (audioInput.getState() == AudioRecord.RECORDSTATE_RECORDING) audioInput.stop(); } } /** * Notify the client that a read has completed. * * @param buffer * Buffer containing the data. */ private void readDone(short[] buffer) { inputListener.onReadComplete(buffer); } }