/*
Copyright (C) 2008-2011, Dirk Trossen, airs@dirk-trossen.de
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation as version 2.1 of the License.
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 Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.airs.handlers;
import com.airs.helper.SerialPortLogger;
import com.airs.helper.Waker;
import com.airs.platform.HandlerManager;
import com.airs.platform.SensorRepository;
import com.airs.platform.History;
import com.airs.*;
import android.content.Context;
import android.media.*;
/**
* Class to read audio-related sensors, specifically the AS and AF sensor
* @see Handler
*/
public class AudioHandler implements Handler
{
private final int SIZE_SHORT = Short.SIZE/8;
private Context airs;
// beacon data
private byte [] AS_reading = new byte[6];
private byte [] AF_reading = new byte[6];
// configuration data
private int polltime = 5000;
private final int CENTRE_POINT = 32768;
// audio data
private long level = -1;
private int sample_rate = 8000;
private int bufferSize;
private int AA_adjust = 3;
// availability of player
private boolean available = false;
private boolean havePlayer = false;
private boolean shutdown = false;
// Player for audio capture
private AudioRecord p;
private short[] output = null;
private void debug(String msg)
{
SerialPortLogger.debug(msg);
}
/**
* Sleep function
* @param millis
*/
private void sleep(long millis)
{
Waker.sleep(millis);
}
/**
* Method to acquire sensor data
* @param sensor String of the sensor symbol
* @param query String of the query to be fulfilled - not used here
* @see com.airs.handlers.Handler#Acquire(java.lang.String, java.lang.String)
*/
public synchronized byte[] Acquire(String sensor, String query)
{
// are we shutting down?
if (shutdown == true)
return null;
// acquire data and send out
try
{
switch(sensor.charAt(1))
{
case 'S' :
if (Amplitude(sensor) == true)
return AS_reading;
case 'F' :
if (Frequency(sensor) == true)
return AF_reading;
default:
return null;
}
}
catch (Exception e)
{
debug("AudioHandler::Acquire: Exception: " + e.toString());
return null;
}
}
/**
* Method to share the last value of the given sensor
* @param sensor String of the sensor symbol to be shared
* @return human-readable string of the last sensor value
* @see com.airs.handlers.Handler#Share(java.lang.String)
*/
public String Share(String sensor)
{
// acquire data and send out
try
{
switch(sensor.charAt(1))
{
case 'S' :
return "My current ambient sound level is at "+String.valueOf((double)level / 100) + " dB";
default:
return null;
}
}
catch (Exception e)
{
debug("AudioHandler::Acquire: Exception: " + e.toString());
return null;
}
}
/**
* Method to view historical chart of the given sensor symbol
* @param sensor String of the symbol for which the history is being requested
* @see com.airs.handlers.Handler#History(java.lang.String)
*/
public void History(String sensor)
{
switch(sensor.charAt(1))
{
case 'S' :
History.timelineView(airs, "Sound Pressure Level [dB]", "AS");
break;
default:
History.timelineView(airs, "Frequency [Hz]", "AF");
break;
}
}
/**
* Method to discover the sensor symbols support by this handler
* As the result of the discovery, appropriate {@link com.airs.platform.Sensor} entries will be added to the {@link com.airs.platform.SensorRepository}
* @see com.airs.handlers.Handler#Discover()
* @see com.airs.platform.Sensor
* @see com.airs.platform.SensorRepository
*/
public void Discover()
{
// return right away if no player could be created in constructor!
if (available == false)
return;
// here some midlet property check as to whether or not audio capture is supported
SensorRepository.insertSensor(new String("AF"), new String("Hz"), airs.getString(R.string.AF_d), airs.getString(R.string.AF_e), new String("int"), 0, 0, 15000, true, polltime, this);
SensorRepository.insertSensor(new String("AS"), new String("dB"), airs.getString(R.string.AS_d), airs.getString(R.string.AS_e), new String("int"), -2, 0, 1200, true, polltime, this);
}
/**
* Constructor, allocating all necessary resources for the handler
* Here, reading the various RMS values of the preferences
* Then, determining the minimal buffer size for the recording
* Then, creating an AudioPlayer just to see if it works (tear it down again right after creation)
* @param airs Reference to the calling {@link android.content.Context}
*/
public AudioHandler(Context airs)
{
boolean buffer_error = false;
// store for later
this.airs = airs;
// now read polltime for audio sampling
polltime = HandlerManager.readRMS_i("AudioHandler::samplingpoll", 5) * 1000;
// now read frequency for sampling
sample_rate = HandlerManager.readRMS_i("AudioHandler::SamplingRate", 8000);
// now read adjustment for AA reading
AA_adjust = HandlerManager.readRMS_i("AudioHandler::AA_adjust", 3);
// determine required buffer size
bufferSize = AudioRecord.getMinBufferSize(sample_rate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (bufferSize<sample_rate * SIZE_SHORT)
bufferSize = sample_rate * SIZE_SHORT;
// start player and see if it's there!
do
{
try
{
p = new AudioRecord(MediaRecorder.AudioSource.MIC, sample_rate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
if (p != null)
if (p.getState() == AudioRecord.STATE_INITIALIZED)
{
available = true;
// reserve memory for recorded output
output = new short[sample_rate];
// release player again until needed
p.release();
p = null;
buffer_error = false;
}
}
catch(Exception e)
{
buffer_error = true;
// increase buffer size by 10%
bufferSize += bufferSize/10;
}
}while(buffer_error == true);
}
/**
* Method to release all handler resources
* Here, we stop any playing AudioPlayer and release its resources
* @see com.airs.handlers.Handler#destroyHandler()
*/
public void destroyHandler()
{
// we are shutting down!
shutdown = true;
try
{
if (p != null)
{
if (p.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING)
p.stop();
p.release();
p = null;
}
}
catch(Exception e)
{
}
}
// do level determination of sample
synchronized private boolean Amplitude(String sensor)
{
long level_new = 0, ampl;
double level_d;
int i, offset = 0, recorded, read;
boolean returnValue = false;
// if somebody else is having the player resource, wait until finished
while (havePlayer == true)
sleep(100);
// now we have the player!
havePlayer = true;
// now record
try
{
// get player resources
p = new AudioRecord(MediaRecorder.AudioSource.MIC, sample_rate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
if (p != null)
if (p.getState() == AudioRecord.STATE_INITIALIZED)
{
// are we shutting down?
if (shutdown == true)
return false;
// start recording
p.startRecording();
// try to read from AudioRecord player until sample_rate buffer is full, i.e., one second of data!
level_new = 0;
// try to always read the buffer size we gave originally
read = sample_rate;
// read until nothing is left to be read
while (read>0)
{
// now read
recorded = p.read(output, offset, read);
// error in reading?
if (recorded == AudioRecord.ERROR_INVALID_OPERATION)
{
debug("AudioHandler:invalid operation!");
havePlayer = false;
p.stop();
p.release();
p = null;
return false;
}
else
{
// now substract the already recorded bytes
read -= recorded;
// compute offset for next batch
offset += recorded;
}
}
// stop and release player again
p.stop();
p.release();
p = null;
// count changes in sign
for (i=0;i<sample_rate;i++)
{
// given that the PCM encoding delivers unsigned SHORTS, we need to convert the amplitude around the 32768 centre point!
// ampl = (long)(CENTRE_POINT-Math.abs(output[i]));
ampl = (long)Math.abs(output[i]);
// now ampl*ampl for field force
level_new += ampl * ampl;
}
// compute decibel through 10*log10 (A1^2/A0^2)
level_d = 10 * Math.log10((double)level_new / (double)output.length) + (double)AA_adjust;
level_new = (long)(level_d*100);
// take positioning info and place in reading field
AS_reading[0] = (byte)sensor.charAt(0);
AS_reading[1] = (byte)sensor.charAt(1);
AS_reading[2] = (byte)((level_new>>24) & 0xff);
AS_reading[3] = (byte)((level_new>>16) & 0xff);
AS_reading[4] = (byte)((level_new>>8) & 0xff);
AS_reading[5] = (byte)(level_new & 0xff);
returnValue = true;
}
}
catch(Exception e)
{
debug("AudioHandler:Exception when requesting AudioRecord!");
}
// don't need player anymore
havePlayer = false;
// nothing happened when we got here
return returnValue;
}
// do frequency determination of sample
synchronized private boolean Frequency(String sensor)
{
long frequency_new = 0;
int i, recorded, read, offset = 0, level=0;
boolean sign = false;
boolean returnValue = false;
// if somebody else is having the player resource, wait until finished
while (havePlayer == true)
sleep(100);
havePlayer = true;
// now record
try
{
// get player resources
p = new AudioRecord(MediaRecorder.AudioSource.MIC, sample_rate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, sample_rate * 4);
if (p != null)
if (p.getState() == AudioRecord.STATE_INITIALIZED)
{
// start recording
p.startRecording();
// are we shutting down?
if (shutdown == true)
return false;
// try to always read the buffer size we gave originally
read = sample_rate;
// read until nothing is left to be read
while (read>0)
{
// now read
recorded = p.read(output, offset, read);
// error in reading?
if (recorded == AudioRecord.ERROR_INVALID_OPERATION)
{
debug("AudioHandler:invalid operation!");
havePlayer = false;
return false;
}
else
{
// now substract the already recorded bytes
read -= recorded;
// compute offset for next batch
offset += recorded;
}
}
// stop and release player again
p.stop();
p.release();
p = null;
// count changes in sign
for (i=0;i<sample_rate;i++)
{
if (output[i]<0 && sign == true)
{
frequency_new++;
sign = false;
}
else
if (output[i]>=0 && sign == false)
{
frequency_new++;
sign = true;
}
// count amplitude level
level += CENTRE_POINT-(long)(Math.abs((int)output[i]));
}
// now determine level
level /= sample_rate;
// volume too low?
if (level<10)
frequency_new = 0;
else
// now determine frequency by half of all counted spikes
frequency_new /= 2;
// take frequency info and place in reading field
AF_reading = new byte[4 + 2];
AF_reading[0] = (byte)sensor.charAt(0);
AF_reading[1] = (byte)sensor.charAt(1);
AF_reading[2] = (byte)((frequency_new>>24) & 0xff);
AF_reading[3] = (byte)((frequency_new>>16) & 0xff);
AF_reading[4] = (byte)((frequency_new>>8) & 0xff);
AF_reading[5] = (byte)(frequency_new & 0xff);
returnValue = true;
}
}
catch(Exception e)
{
debug("AudioHandler:Exception when requesting AudioRecord!");
}
// don't need player anymore
havePlayer = false;
return returnValue;
}
}