Skip to content

Instantly share code, notes, and snippets.

@robinvanemden
Created July 2, 2015 17:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save robinvanemden/14d2972068f08c2df314 to your computer and use it in GitHub Desktop.
Save robinvanemden/14d2972068f08c2df314 to your computer and use it in GitHub Desktop.
Audio Detector and Recorder
package com.hollandhaptics.babyapp;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.Toast;
import android.content.Context;
public class BabyService extends Service {
private String TAG = "BabyService"; // A tag for log output
private static final int min_free_storagespace = 10; // min % of free storage space, required for a ftp sync
private static final double amplitude_threshold = 1000; // the amplitude threshold (getAmplitude returns unsigned 16-bit integer values (0-32767))
private static final int number_of_retries = 60; // the number of times to (re)try measuring an amplitude above the given threshold before stopping the recording and resetting the recorder
private static final long service_runnable_interval = 1000; // The interval in ms with which the runnable is called to check the volume.
BabyServiceBinder binder; // Binder for the service
private Handler mhandler; // Handlers for runnable
private Runnable mrunnable; // Runnable
private MediaRecorder _recorder; // MediaRecorder object
private String path; // A String to store the path to the folder containing the audio files
private String current_filename; // A String to store the filename of the current recording
private boolean hasRecorded; // A boolean to store if the MediaRecorder has recorded something with an amplitude higher than the threshold
private int attempt; // An int containing the number of attempts to try to measure an amplitude higher than the threshold
private int serverResponseCode = 0; // An int to store the response code returned by the server
private String upLoadServerUri; // A String to store the server's url (set as file_upload_url in strings.xml)
private File[] uploadFiles; // An array to store all audio file paths when attempting to upload the files to the server
private String imei; // An unique ID for each phone
/** @brief Entry point of the Service.
*/
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "BabyService Created");
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
imei = telephonyManager.getDeviceId();
Log.d(TAG, "IMEI: " + imei);
upLoadServerUri = getResources().getString(R.string.file_upload_url);
mhandler = new Handler();
// Get the directory for the app's audio recordings.
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "BabyApp");
if (!file.mkdirs()) {
Log.e(TAG, "Directory not created");
}
path = file.toString();
_recorder = new MediaRecorder();
}
/** @brief Start point of service
*
* @param intent
* @param flags
* @param startId
* @return Returns START_STICKY.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "BabyService onStartCommand");
// Let the LED indicator flash so the user knows the service is running.
FlashLED();
// Start a new recording.
start_new_recording();
// Updates every x sec (set above with service_runnable_interval).
mrunnable = new Runnable() {
public void run() {
// Check if we have permission to read/write files.
if(isExternalStorageWritable() == false) {
Log.d(TAG, "No permission to write to external storage");
return;
}
// Check if there is enough storage space left.
if(IsThereStorageSpace() == false) {
notifyUser("(Almost) out of storage space");
toaster("(Almost) out of storage space", true);
Log.d(TAG, "(Almost) out of storage space");
// No more storage space, so stop the MediaRecorder.
_recorder.stop();
if(!hasRecorded) {
// Nothing interesting has been recorded, thus delete the file.
delete_file(current_filename);
}
_recorder.reset();
// Start uploading all audio files in the app's folder and start a new recording.
uploadAudioFiles();
start_new_recording();
} else {
// There's enough storage space so we can continue.
// Returns the maximum absolute amplitude that was sampled since the last call to this method.
int amplitude = _recorder.getMaxAmplitude();
Log.d(TAG, "Amplitude: " + amplitude);
Log.d(TAG, "Attempt: " + attempt);
if (amplitude > amplitude_threshold) {
// The amplitude of the last measurement was higher than the threshold.
hasRecorded = true;
attempt = 0;
// Continue recording.
Log.d(TAG, "Continue recording.");
} else {
// The amplitude of the last measurement wasn't higher than the threshold.
if (attempt >= number_of_retries) {
// No more attempts left, stop the MediaRecorder.
Log.d(TAG, "Stop recording.");
_recorder.stop();
if (hasRecorded) {
// Something with a high enough amplitude has been recorded, start uploading.
uploadAudioFiles();
} else {
// Nothing interesting has been recorded, thus delete the file.
delete_file(current_filename);
}
_recorder.reset();
// Restart the MediaRecorder.
start_new_recording();
} else {
// There are some attempts left, update the counter and continue.
attempt++;
}
}
}
mhandler.postDelayed(mrunnable, service_runnable_interval);
}
};
mhandler.postDelayed(mrunnable, service_runnable_interval);
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
/** @brief Configures the MediaRecorder for a new recording.
* This method is called by start_new_recording() before starting the MediaRecorder.
*/
private void configure_recorder() {
Log.d(TAG, "Configuring the MediaRecorder");
// Sets the audio source to be used for recording.
_recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// Sets the format of the output file produced during recording.
_recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
// Sets the audio encoder to be used for recording.
_recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// Sets the audio sampling rate for recording.
_recorder.setAudioSamplingRate(44100);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
Date now = new Date();
current_filename = formatter.format(now);
// Sets the path of the output file to be produced.
_recorder.setOutputFile(path + "/" + imei + "_" + current_filename + ".3gpp");
// Prepares the recorder to begin capturing and encoding data.
try {
_recorder.prepare();
} catch(IOException e) {
Log.e(TAG, "prepare() failed");
}
}
/** @brief Configure the MediaRecorder and start a new recording.
*/
private void start_new_recording() {
Log.d(TAG, "Starting a new recording.");
configure_recorder();
hasRecorded = false;
attempt = 0;
_recorder.start();
}
/** @brief Deletes an audio file with the given filename.
*
* @param filename The filename of the file.
*/
private void delete_file(String filename) {
Log.d(TAG, "Delete file:" + filename);
File file = new File(path + "/" + filename + ".3gpp");
file.delete();
}
/** @brief Makes a notification for the user
*
* @param _message The message to show.
*/
private void notifyUser(String _message) {
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Baby App")
.setContentText(_message);
// Creates an explicit intent for an Activity in your app
Intent resultIntent = new Intent(this, MainActivity.class);
// The stack builder object will contain an artificial back stack for the
// started Activity.
// This ensures that navigating backward from the Activity Leads out of
// your application to the Home screen.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(MainActivity.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
// mId = 0
mNotificationManager.notify(0, mBuilder.build());
}
/** @brief Makes a little toast display for the user.
*
* @param _message The message to show. Default display time is set to short.
*/
private void toaster(String _message){
toaster(_message, false);
}
/** @param _message The message to show.
* @param _long True for a longer display time, false for short.
*/
private void toaster(String _message, boolean _long){
if(_long) {
Toast.makeText(this, _message, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, _message, Toast.LENGTH_SHORT).show();
}
}
/** @brief Handles when the Service stops.
*/
@Override
public void onDestroy() {
Log.d(TAG, "BabyService Destroyed");
ClearLED();
super.onDestroy();
_recorder.reset();
_recorder.release();
}
/** @brief Binds the Service.
*
* @param intent An Intent.
* @return A new BabyServiceBinder object.
*/
@Override
public IBinder onBind(Intent intent) {
binder = new BabyServiceBinder(this);
return binder;
}
/** @brief Lets the LED indicator flash.
*/
private void FlashLED() {
NotificationManager nm = ( NotificationManager ) getSystemService( NOTIFICATION_SERVICE );
Notification notif = new Notification();
notif.ledARGB = 0xFF0000ff;
notif.flags = Notification.FLAG_SHOW_LIGHTS;
notif.ledOnMS = 100;
notif.ledOffMS = 900;
nm.notify(0, notif);
}
/** @brief Clears the LED indicator.
*/
private void ClearLED() {
NotificationManager nm = ( NotificationManager ) getSystemService( NOTIFICATION_SERVICE );
nm.cancel( 0 );
}
/** @brief Searches for audio files in the app's folder and starts uploading them to the server.
*/
public void uploadAudioFiles() {
uploadFiles = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "BabyApp").listFiles();
new Thread(new Runnable() {
public void run() {
//uploadFile(uploadFilePath + "" + uploadFileName);
for(File uploadFile : uploadFiles) {
uploadFile(uploadFile);
}
}
}).start();
}
/** @brief Uploads a file to the server. Is called by uploadAudioFiles().
*
* @param sourceFile The file to upload.
* @return int The server's response code.
*/
public int uploadFile(File sourceFile) {
HttpURLConnection conn;
DataOutputStream dos;
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 1 * 1024 * 1024;
String sourceFileUri = sourceFile.getAbsolutePath();
if (!sourceFile.isFile()) {
Log.e("uploadFile", "Source File does not exist :"
+ sourceFileUri);
return 0;
} else {
try {
// open a URL connection to the Servlet
FileInputStream fileInputStream = new FileInputStream(sourceFile);
URL url = new URL(upLoadServerUri);
// Open a HTTP connection to the URL
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true); // Allow Inputs
conn.setDoOutput(true); // Allow Outputs
conn.setUseCaches(false); // Don't use a Cached Copy
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("ENCTYPE", "multipart/form-data");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
conn.setRequestProperty("fileToUpload", sourceFileUri);
dos = new DataOutputStream(conn.getOutputStream());
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"fileToUpload\";filename=\""
+ sourceFileUri + "\"" + lineEnd);
dos.writeBytes(lineEnd);
// create a buffer of maximum size
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
dos.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
// send multipart form data necesssary after file data...
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Responses from the server (code and message)
serverResponseCode = conn.getResponseCode();
String serverResponseMessage = conn.getResponseMessage();
Log.i("uploadFile", "HTTP Response is : "
+ serverResponseMessage + ": " + serverResponseCode);
if (serverResponseCode == 200) {
boolean deleted = sourceFile.delete();
} else {
//Toast.makeText(BabyService.this, getResources().getString(R.string.toast_upload_failed), Toast.LENGTH_SHORT).show();
toaster(getResources().getString(R.string.toast_upload_failed));
}
//close the streams //
fileInputStream.close();
dos.flush();
dos.close();
} catch (MalformedURLException ex) {
ex.printStackTrace();
//Toast.makeText(BabyService.this, getResources().getString(R.string.toast_malformedurlexception),
// Toast.LENGTH_SHORT).show();
toaster(getResources().getString(R.string.toast_malformedurlexception));
Log.e("Upload file to server", "error: " + ex.getMessage(), ex);
} catch (Exception e) {
e.printStackTrace();
//Toast.makeText(BabyService.this, getResources().getString(R.string.toast_exception),
// Toast.LENGTH_SHORT).show();
toaster(getResources().getString(R.string.toast_exception));
Log.e("Upload file to server", "Exception : "
+ e.getMessage(), e);
}
return serverResponseCode;
} // End else block
}
/** @brief Determines if there is enough storage space.
*
* @return true Enough.
* @return false Too little.
*/
public boolean IsThereStorageSpace() {
// Is the amount of free space <= min_free_storagespace% of the total space, return false. Otherwise return true;
long freespace = Environment.getExternalStorageDirectory().getFreeSpace() / 1048576;
long totalspace = Environment.getExternalStorageDirectory().getTotalSpace() / 1048576;
Log.d(TAG, "StorageSpace: " + String.valueOf((int)(((double)freespace / (double)totalspace) * 100)) + "% remaining.");
if (freespace <= ((totalspace / 100) * min_free_storagespace)) {
return false;
}
return true;
}
/** @brief Checks if external storage is available for read and write.
*
* @return True/false.
*/
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment