feat: record accelerometer to file
This commit is contained in:
@@ -76,6 +76,7 @@ dependencies {
|
||||
implementation libs.oboe
|
||||
implementation libs.slf4j.api
|
||||
implementation libs.logback.android
|
||||
implementation libs.gson
|
||||
|
||||
implementation libs.androidx.core.ktx
|
||||
implementation libs.androidx.lifecycle.runtime.ktx
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
|
||||
@@ -11,17 +11,18 @@ import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -98,7 +99,7 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
Toast.makeText(this, "Could not open music file contentUri", Toast.LENGTH_LONG).show();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
startCollection();
|
||||
startCollection(contentUri);
|
||||
} else if (ACTION_STOP.equals(action)) {
|
||||
stopCollectionAndSelf();
|
||||
}
|
||||
@@ -126,7 +127,7 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
return fd;
|
||||
}
|
||||
|
||||
private void startCollection() {
|
||||
private void startCollection(String meta) {
|
||||
if (isCollecting) {
|
||||
return;
|
||||
}
|
||||
@@ -147,6 +148,7 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
SensorManager.SENSOR_DELAY_GAME
|
||||
);
|
||||
isCollecting = true;
|
||||
onStartRecording(meta);
|
||||
} else {
|
||||
stopCollectionAndSelf();
|
||||
}
|
||||
@@ -156,6 +158,7 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
if (isCollecting && sensorManager != null) {
|
||||
sensorManager.unregisterListener(this);
|
||||
isCollecting = false;
|
||||
onStopRecording();
|
||||
}
|
||||
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
@@ -190,15 +193,66 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
LstForegroundService getService() { return LstForegroundService.this; }
|
||||
}
|
||||
private final LocalBinder binder = new LocalBinder();
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
return binder;
|
||||
}
|
||||
|
||||
public interface OnResultListener {
|
||||
void onResult(SensorDataArray recording);
|
||||
}
|
||||
private OnResultListener listener;
|
||||
public void setOnResultListener(OnResultListener listener) { this.listener = listener; }
|
||||
|
||||
/** single sensor sample */
|
||||
public static class SensorData {
|
||||
private long timestamp;
|
||||
private float[] values;
|
||||
public SensorData(SensorEvent event) {
|
||||
timestamp = event.timestamp;
|
||||
values = Arrays.copyOf(event.values, event.values.length);
|
||||
}
|
||||
public SensorData(long timestamp, float[] values) {
|
||||
this.timestamp = timestamp;
|
||||
this.values = values;
|
||||
}
|
||||
}
|
||||
|
||||
/** array of sensor samples */
|
||||
public static class SensorDataArray {
|
||||
private ArrayList<SensorData> data = new ArrayList<SensorData>();
|
||||
private String meta;
|
||||
public void add(SensorEvent event) { data.add(new SensorData(event)); }
|
||||
public void add(SensorData d) { data.add(d); }
|
||||
public void clear() { data.clear(); }
|
||||
public void setMeta(String meta) { this.meta = meta; }
|
||||
}
|
||||
|
||||
private final SensorDataArray recording = new SensorDataArray();
|
||||
private long recordingStartTime = 0;
|
||||
|
||||
private void onStartRecording(String meta) {
|
||||
recordingStartTime = SystemClock.elapsedRealtimeNanos();
|
||||
recording.setMeta(meta);
|
||||
}
|
||||
private void onStopRecording() {
|
||||
if(listener != null) {
|
||||
listener.onResult(recording);
|
||||
}
|
||||
recording.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
// pass on to C++ filter bank
|
||||
stepDetector.filter(event.timestamp, event.values);
|
||||
// collect accelerometer recording - adjust timebase to 0.0 sec beginning
|
||||
recording.add(new SensorData(event.timestamp - recordingStartTime, event.values));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package at.lockstep.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -16,7 +21,15 @@ import at.lockstep.saf.SafPickerActivity;
|
||||
import at.lockstep.ui.SongPickerActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements LstForegroundService.OnResultListener {
|
||||
private Button btnStart;
|
||||
private Button btnStop;
|
||||
private Button btnMediaStoreBenchmark;
|
||||
@@ -80,4 +93,74 @@ public class MainActivity extends AppCompatActivity {
|
||||
launcher.launch(intent);
|
||||
});
|
||||
}
|
||||
|
||||
private ServiceConnection conn = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
LstForegroundService service = ((LstForegroundService.LocalBinder) iBinder).getService();
|
||||
service.setOnResultListener(MainActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
// attach ServiceConnection (so we can attach a listener). incidentally, it seems to also create the service. (will currently create a PlaybackEngine, etc.)
|
||||
// TODO: check if this delays starting the application
|
||||
bindService(new Intent(this, LstForegroundService.class), conn, BIND_AUTO_CREATE);
|
||||
}
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
unbindService(conn);
|
||||
}
|
||||
|
||||
private boolean isForeground = false;
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
isForeground = false;
|
||||
// TODO: since the Service keeps running, we must signal oboe to stop playing
|
||||
// TODO: signal the pause to the C++ lib
|
||||
//
|
||||
// telltale signs: logcat: "PlaybackEngine - Buffer overrun on output for channel (0.000000)" or (1.000000)
|
||||
}
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
isForeground = true;
|
||||
}
|
||||
|
||||
LstForegroundService.SensorDataArray recording;
|
||||
|
||||
@Override
|
||||
public void onResult(LstForegroundService.SensorDataArray recording) {
|
||||
if(!isForeground) {
|
||||
Log.i("MainActivity", "ignore onResult() from LstForegroundService due to backgrounded MainActivity");
|
||||
return;
|
||||
}
|
||||
this.recording = recording;
|
||||
|
||||
//
|
||||
// write accelero recording to file
|
||||
//
|
||||
File f = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
|
||||
String dir = f != null ? f.toString() : "/"; // make compiler happy
|
||||
long unixTime = System.currentTimeMillis() / 1000L;
|
||||
String fileName = dir + "/acc_" + unixTime + ".json";
|
||||
Log.i("MainActivity", "written acc rec to " + fileName);
|
||||
try (Writer writer = new FileWriter(fileName)) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
gson.toJson(recording, writer);
|
||||
} catch (IOException e) {
|
||||
// TODO error handling
|
||||
Log.e("MainActivity", "IOException writing recording: " + e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
app/src/test/java/at/lockstep/GsonUnitTest.java
Normal file
46
app/src/test/java/at/lockstep/GsonUnitTest.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package at.lockstep;
|
||||
|
||||
import android.hardware.SensorEvent;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public class GsonUnitTest {
|
||||
|
||||
/** single sensor sample */
|
||||
static class SensorData {
|
||||
private long timestamp;
|
||||
private float[] values;
|
||||
public SensorData(SensorEvent event) {
|
||||
timestamp = event.timestamp;
|
||||
values = Arrays.copyOf(event.values, event.values.length);
|
||||
}
|
||||
public SensorData(long timestamp, float[] values) {
|
||||
this.timestamp = timestamp;
|
||||
this.values = values;
|
||||
}
|
||||
}
|
||||
|
||||
/** array of sensor samples */
|
||||
public static class SensorDataArray {
|
||||
private ArrayList<SensorData> data = new ArrayList<SensorData>();
|
||||
public void add(long timestamp, float[] values) { data.add(new SensorData(timestamp, values)); }
|
||||
public void add(SensorEvent event) { data.add(new SensorData(event)); }
|
||||
public void clear() { data.clear(); }
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGson() {
|
||||
SensorDataArray recording = new SensorDataArray();
|
||||
recording.add(0, new float[]{1, 2, 3});
|
||||
recording.add(1, new float[]{10, 20, 30});
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(recording);
|
||||
System.out.println(json);
|
||||
// {"data":[{"timestamp":0,"values":[1.0,2.0,3.0]},{"timestamp":1,"values":[10.0,20.0,30.0]}]}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ oboe = "1.10.0"
|
||||
slf4jApi = "1.7.30"
|
||||
recyclerview = "1.3.1"
|
||||
appcompat = "1.7.1"
|
||||
gson = "2.11.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -34,6 +35,7 @@ oboe = { module = "com.google.oboe:oboe", version.ref = "oboe" }
|
||||
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4jApi" }
|
||||
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
gson = { group = "com.google.code.gson", name="gson", version.ref = "gson" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user