├── .gitmodules ├── settings.gradle ├── app ├── src │ ├── arengine │ │ ├── assets │ │ ├── .gitignore │ │ ├── java │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── viotester │ │ │ │ ├── ext_ar │ │ │ │ └── TrackingProvider.java │ │ │ │ └── arengine │ │ │ │ ├── DisplayRotationHelper.java │ │ │ │ ├── BackgroundRenderer.java │ │ │ │ └── AREngineActivity.java │ │ └── AndroidManifest.xml │ ├── arcoreandengine │ │ ├── assets │ │ ├── java │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── viotester │ │ │ │ ├── arcore │ │ │ │ ├── arengine │ │ │ │ └── ext_ar │ │ │ │ └── TrackingProvider.java │ │ └── AndroidManifest.xml │ ├── main │ │ ├── assets │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── icon.png │ │ │ │ └── rounded_box.xml │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── xml │ │ │ │ └── shared_file_paths.xml │ │ │ └── layout │ │ │ │ ├── share_list_item.xml │ │ │ │ ├── settings_activity.xml │ │ │ │ ├── activity_share_list.xml │ │ │ │ ├── viotester_surface_view.xml │ │ │ │ ├── viotester_map_view.xml │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── viotester │ │ │ │ ├── ext_ar │ │ │ │ ├── EmptyTrackingProvider.java │ │ │ │ ├── Renderer.java │ │ │ │ └── ShaderUtil.java │ │ │ │ ├── modules │ │ │ │ ├── TrackingActivity.java │ │ │ │ ├── DataCollectionActivity.java │ │ │ │ ├── GpuExampleActivity.java │ │ │ │ └── CalibrationActivity.java │ │ │ │ ├── FrequencyMonitor.java │ │ │ │ ├── TrackingOutput.java │ │ │ │ ├── PermissionHelper.java │ │ │ │ ├── AssetCopier.java │ │ │ │ ├── ShareListActivity.java │ │ │ │ ├── DataRecorder.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── SettingsActivity.java │ │ ├── jni │ │ │ ├── always_assert.h │ │ │ ├── opengl │ │ │ │ ├── util.hpp │ │ │ │ ├── ar_renderer.hpp │ │ │ │ ├── camera_renderer.hpp │ │ │ │ ├── gpu_camera_adapter.hpp │ │ │ │ ├── ext_ar_renderer.cpp │ │ │ │ ├── util.cpp │ │ │ │ ├── gpu_camera_adapter.cpp │ │ │ │ └── camera_renderer.cpp │ │ │ ├── native_camera_session.hpp │ │ │ ├── jniutil.hpp │ │ │ ├── camera_worker.cpp │ │ │ ├── logging.hpp │ │ │ ├── algorithm_module.cpp │ │ │ ├── algorithm_module.hpp │ │ │ ├── CMakeLists.txt │ │ │ ├── modules │ │ │ │ ├── recorder.cpp │ │ │ │ └── camera_calibrator.cpp │ │ │ ├── algorithm_module_wrappers.cpp │ │ │ └── native_camera_session.cpp │ │ └── AndroidManifest.xml │ ├── a_default │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── viotester │ │ │ └── ext_ar │ │ │ └── TrackingProvider.java │ ├── noslam │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── viotester │ │ │ └── ext_ar │ │ │ └── TrackingProvider.java │ ├── arcore │ │ ├── java │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── viotester │ │ │ │ ├── ext_ar │ │ │ │ └── TrackingProvider.java │ │ │ │ └── arcore │ │ │ │ ├── DisplayRotationHelper.java │ │ │ │ ├── BackgroundRenderer.java │ │ │ │ └── ARCoreActivity.java │ │ ├── assets │ │ │ └── shaders │ │ │ │ ├── screenquad.vert │ │ │ │ └── screenquad.frag │ │ └── AndroidManifest.xml │ └── debug │ │ └── res │ │ └── raw │ │ └── gps.csv ├── dummy-google-services.json └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── arrays.xml ├── gradle.properties ├── reset.xml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── configure.sh ├── gradlew.bat ├── README.md ├── preferences.xml ├── gradlew └── LICENSE /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app/src/arengine/assets: -------------------------------------------------------------------------------- 1 | ../arcore/assets -------------------------------------------------------------------------------- /app/src/arcoreandengine/assets: -------------------------------------------------------------------------------- 1 | ../arcore/assets -------------------------------------------------------------------------------- /app/src/main/assets: -------------------------------------------------------------------------------- 1 | ../../../custom-vio/viotester-integration/assets -------------------------------------------------------------------------------- /app/src/arengine/.gitignore: -------------------------------------------------------------------------------- 1 | # Do not add the AREngine AAR to Git 2 | libs/*.aar 3 | -------------------------------------------------------------------------------- /app/src/arcoreandengine/java/org/example/viotester/arcore: -------------------------------------------------------------------------------- 1 | ../../../../../arcore/java/org/example/viotester/arcore/ -------------------------------------------------------------------------------- /app/src/arcoreandengine/java/org/example/viotester/arengine: -------------------------------------------------------------------------------- 1 | ../../../../../arengine/java/org/example/viotester/arengine/ -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaltoML/android-viotester/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaltoML/android-viotester/HEAD/app/src/main/res/drawable/icon.png -------------------------------------------------------------------------------- /arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | placeholder 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CV tester 4 | 5 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | android.enableJetifier=true 3 | #USE_GPU_EXAMPLES=true 4 | # -- note! leave a blank line at the end of this file! 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/shared_file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/a_default/java/org/example/viotester/ext_ar/TrackingProvider.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.ext_ar; 2 | 3 | public class TrackingProvider extends EmptyTrackingProvider {} 4 | -------------------------------------------------------------------------------- /app/src/noslam/java/org/example/viotester/ext_ar/TrackingProvider.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.ext_ar; 2 | 3 | public class TrackingProvider extends EmptyTrackingProvider {} 4 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/ext_ar/EmptyTrackingProvider.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.ext_ar; 2 | 3 | public class EmptyTrackingProvider { 4 | public static Class[] ACTIVITY_CLASSES = {}; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/jni/always_assert.h: -------------------------------------------------------------------------------- 1 | #ifndef ALWAYS_ASSERT_H 2 | #define ALWAYS_ASSERT_H 3 | 4 | // CMake makes it difficult to control variables such as NDEBUG ... take this, CMake! 5 | #ifdef NDEBUG 6 | #undef NDEBUG 7 | #endif 8 | 9 | #endif -------------------------------------------------------------------------------- /app/src/arcore/java/org/example/viotester/ext_ar/TrackingProvider.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.ext_ar; 2 | 3 | import org.example.viotester.arcore.ARCoreActivity; 4 | 5 | public class TrackingProvider { 6 | public static Class[] ACTIVITY_CLASSES = { ARCoreActivity.class }; 7 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/share_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/arengine/java/org/example/viotester/ext_ar/TrackingProvider.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.ext_ar; 2 | 3 | import org.example.viotester.arengine.AREngineActivity; 4 | 5 | public class TrackingProvider { 6 | public static Class[] ACTIVITY_CLASSES = { AREngineActivity.class }; 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jun 16 15:24:22 EEST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /reset.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_box.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/arcoreandengine/java/org/example/viotester/ext_ar/TrackingProvider.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.ext_ar; 2 | 3 | import org.example.viotester.arcore.ARCoreActivity; 4 | import org.example.viotester.arengine.AREngineActivity; 5 | 6 | public class TrackingProvider { 7 | public static Class[] ACTIVITY_CLASSES = { ARCoreActivity.class, AREngineActivity.class }; 8 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/jni/opengl/util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GL_UTIL_H_ 2 | #define GL_UTIL_H_ 3 | 4 | #include 5 | #include "logging.hpp" 6 | 7 | namespace glUtil { 8 | 9 | void checkError(const char* op); 10 | GLuint createProgram(const char* vertexShaderSource, const char* fragmentShaderSource); 11 | 12 | extern const GLfloat screenQuadVertices[]; 13 | extern const GLfloat screenQuadTextureCoordinates[]; 14 | } 15 | 16 | #endif -------------------------------------------------------------------------------- /app/src/main/jni/native_camera_session.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NATIVE_CAMERA_SESSION_HPP 2 | #define NATIVE_CAMERA_SESSION_HPP 3 | 4 | #include 5 | #include 6 | 7 | struct NativeCameraSession { 8 | static std::unique_ptr create(std::string cameraId, int targetFps); 9 | virtual ~NativeCameraSession(); 10 | virtual void initCameraSurface(JNIEnv* env, jobject surface) = 0; 11 | }; 12 | 13 | #endif -------------------------------------------------------------------------------- /app/src/main/jni/jniutil.hpp: -------------------------------------------------------------------------------- 1 | #ifndef JNIUTIL_HPP 2 | #define JNIUTIL_HPP 3 | 4 | #include 5 | #include 6 | 7 | inline std::string getStringOrEmpty(JNIEnv *env, jstring s) { 8 | if (s == nullptr) { 9 | return ""; 10 | } 11 | const char *cstr = env->GetStringUTFChars(s, nullptr); 12 | std::string result(cstr); 13 | env->ReleaseStringUTFChars(s, cstr); 14 | return result; 15 | } 16 | 17 | #endif -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Android app 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: kunitoki/android-ndk-r21@sha256:ce5aed19936a1f3c99fce2f1135c055248c0faa2f5a930c68e36e81317db4550 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Configure 13 | run: ./configure.sh 14 | - name: "Build Android project (ARCore research variant)" 15 | run: "./gradlew assembleArcoreResearchDebug" 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/modules/TrackingActivity.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.modules; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.example.viotester.AlgorithmActivity; 6 | 7 | public class TrackingActivity extends AlgorithmActivity { 8 | @Override 9 | public void onCreate(Bundle savedInstanceState) { 10 | mRecordPrefix = "vio"; 11 | mNativeModule = "tracking"; 12 | mUseCameraWorker = true; 13 | super.onCreate(savedInstanceState); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/modules/DataCollectionActivity.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.modules; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.example.viotester.AlgorithmActivity; 6 | 7 | public class DataCollectionActivity extends AlgorithmActivity { 8 | @Override 9 | public void onCreate(Bundle savedInstanceState) { 10 | mDataCollectionMode = true; 11 | mRecordPrefix = "camera2"; 12 | mNativeModule = "recording"; 13 | super.onCreate(savedInstanceState); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/ext_ar/Renderer.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.ext_ar; 2 | 3 | import javax.microedition.khronos.egl.EGLConfig; 4 | import javax.microedition.khronos.opengles.GL10; 5 | 6 | public class Renderer { 7 | public native void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig); 8 | public native void onSurfaceChanged(GL10 gl10, int w, int h); 9 | public native void onDrawFrame(double t, float[] arViewMatrix, float[] arProjMatrix, int w, int h); 10 | public native void setPointCloud(float[] pointCloudBuffer, int size); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/dummy-google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "", 4 | "firebase_url": "", 5 | "project_id": "", 6 | "storage_bucket": "" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "dummy", 12 | "android_client_info": { 13 | "package_name": "org.example.viotester" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "" 25 | } 26 | ] 27 | }, 28 | ], 29 | "configuration_version": "1" 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/modules/GpuExampleActivity.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.modules; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.example.viotester.AlgorithmActivity; 6 | import org.example.viotester.AlgorithmWorker; 7 | 8 | public class GpuExampleActivity extends AlgorithmActivity { 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) { 11 | mRecordPrefix = "none"; 12 | mNativeModule = "gpu_examples"; 13 | super.onCreate(savedInstanceState); 14 | } 15 | 16 | @Override 17 | public void adjustSettings(AlgorithmWorker.Settings s) { 18 | s.recordSensors = false; 19 | s.recordGps = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/modules/CalibrationActivity.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester.modules; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.example.viotester.AlgorithmActivity; 6 | import org.example.viotester.AlgorithmWorker; 7 | 8 | public class CalibrationActivity extends AlgorithmActivity { 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) { 11 | mRecordPrefix = "camera2"; 12 | mNativeModule = "calibration"; 13 | super.onCreate(savedInstanceState); 14 | } 15 | 16 | @Override 17 | public void adjustSettings(AlgorithmWorker.Settings s) { 18 | s.recordSensors = false; 19 | s.recordGps = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Android Studio configuration. 2 | *.iml 3 | .idea/ 4 | 5 | # Gradle configuration. 6 | .gradle/ 7 | build/ 8 | release/ 9 | 10 | # Built dynamically 11 | app/src/main/res/values/arrays.xml 12 | app/src/main/res/xml/root_preferences.xml 13 | 14 | # User configuration. 15 | local.properties 16 | 17 | # Android cmake dirs 18 | .externalNativeBuild 19 | .cxx 20 | 21 | # generic data 22 | data/ 23 | 24 | app/google-services.json 25 | 26 | # the custom module to be benchmarked against ARCore/AREngine 27 | custom-vio/ 28 | custom-vio 29 | 30 | # mobile-cv-suite can be symlinked to the root dir to use it instead of the pre-built version 31 | # this allows testing local modifications to mobile-cv-suite 32 | mobile-cv-suite/ 33 | mobile-cv-suite 34 | -------------------------------------------------------------------------------- /app/src/arengine/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/jni/camera_worker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "logging.hpp" 4 | #include "jniutil.hpp" 5 | #include "native_camera_session.hpp" 6 | 7 | #define MY_JNI_FUNC(ret, x) JNIEXPORT ret JNICALL Java_org_example_viotester_CameraWorker_ ## x 8 | 9 | namespace { 10 | std::unique_ptr cameraSession; 11 | } 12 | 13 | extern "C" { 14 | MY_JNI_FUNC(void, stopCameraSession)(JNIEnv *env, jobject thiz) { 15 | (void)env; (void)thiz; 16 | cameraSession.reset(); 17 | } 18 | 19 | MY_JNI_FUNC(void, startCameraSession)(JNIEnv *env, jobject thiz, jstring cameraId, jint targetFps, jobject surface) { 20 | (void)thiz; 21 | cameraSession.reset(); 22 | cameraSession = NativeCameraSession::create(getStringOrEmpty(env, cameraId), targetFps); 23 | assert(cameraSession); 24 | cameraSession->initCameraSurface(env, surface); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/arcore/assets/shaders/screenquad.vert: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | attribute vec4 a_Position; 17 | attribute vec2 a_TexCoord; 18 | 19 | varying vec2 v_TexCoord; 20 | 21 | void main() { 22 | gl_Position = a_Position; 23 | v_TexCoord = a_TexCoord; 24 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_share_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/jni/opengl/ar_renderer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AR_RENDERER_HPP 2 | #define AR_RENDERER_HPP 3 | 4 | #include 5 | #include 6 | 7 | class ArRenderer { 8 | public: 9 | // Choose your coordinate convention... 10 | static std::unique_ptr buildWithYIsUp(); 11 | static std::unique_ptr buildWithZIsUp(); 12 | 13 | virtual ~ArRenderer() = default; 14 | 15 | // another API for the same thing 16 | virtual void setProjection(int w, int h, float focalLength) = 0; 17 | virtual void setProjectionMatrix(const float projectionMatrix[]) = 0; 18 | virtual void setPose(double timestamp, const float xyz[], const float quaternion[]) = 0; 19 | virtual void setPose(double timestamp, const float viewMatrix[]) = 0; 20 | virtual void setPointCloud(const float *flatData, std::size_t n) = 0; 21 | virtual void render() const = 0; 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /app/src/arcore/assets/shaders/screenquad.frag: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | #extension GL_OES_EGL_image_external : require 16 | 17 | precision mediump float; 18 | varying vec2 v_TexCoord; 19 | uniform samplerExternalOES sTexture; 20 | 21 | 22 | void main() { 23 | gl_FragColor = texture2D(sTexture, v_TexCoord); 24 | } -------------------------------------------------------------------------------- /app/src/arcore/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | 9 | 10 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/jni/logging.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LOGGING_HPP 2 | #define LOGGING_HPP 3 | 4 | #ifdef ANDROID 5 | 6 | #include 7 | #define ANDROID_LOG_TAG "VioTesterNative" 8 | #define log_debug(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, ANDROID_LOG_TAG, __VA_ARGS__)) 9 | #define log_info(...) ((void)__android_log_print(ANDROID_LOG_INFO, ANDROID_LOG_TAG, __VA_ARGS__)) 10 | #define log_warn(...) ((void)__android_log_print(ANDROID_LOG_WARN, ANDROID_LOG_TAG, __VA_ARGS__)) 11 | #define log_error(...) ((void)__android_log_print(ANDROID_LOG_ERROR, ANDROID_LOG_TAG, __VA_ARGS__)) 12 | 13 | #elif defined(USE_LOGURU) 14 | 15 | #include 16 | 17 | #define log_debug(fmt, ...) (LOG_F(1, fmt, ## __VA_ARGS__)) 18 | #define log_info(fmt, ...) (LOG_F(INFO, fmt, ## __VA_ARGS__)) 19 | #define log_warn(fmt, ...) (LOG_F(WARNING, fmt, ## __VA_ARGS__)) 20 | #define log_error(fmt, ...) (LOG_F(ERROR, fmt, ## __VA_ARGS__)) 21 | 22 | #else 23 | 24 | // ## is a "gcc" hack for allowing empty __VA_ARGS__ 25 | // https://stackoverflow.com/questions/5891221/variadic-macros-with-zero-arguments 26 | #define log_debug(fmt, ...) ((void)printf(fmt"\n", ## __VA_ARGS__)) 27 | #define log_info(fmt, ...) ((void)printf(fmt"\n", ## __VA_ARGS__)) 28 | #define log_warn(fmt, ...) ((void)printf(fmt"\n", ## __VA_ARGS__)) 29 | #define log_error(fmt, ...) ((void)printf(fmt"\n", ## __VA_ARGS__)) 30 | 31 | #endif 32 | 33 | #endif // define LOGGING_HPP 34 | -------------------------------------------------------------------------------- /app/src/main/jni/algorithm_module.cpp: -------------------------------------------------------------------------------- 1 | #include "algorithm_module.hpp" 2 | #include 3 | 4 | using AlgoPtr = std::unique_ptr; 5 | using json = AlgorithmModule::json; 6 | 7 | AlgoPtr buildRecorder(int textureId, int w, int h, const json &settings); 8 | AlgoPtr buildCameraCalibrator(int textureId, int w, int h); 9 | AlgoPtr buildTracking(int textureId, int w, int h, const json &settings); 10 | AlgoPtr buildGpuExample(int textureId, int w, int h, const json &settings); 11 | 12 | AlgoPtr AlgorithmModule::build(int textureId, int width, int height, const std::string &name, const json *settings) { 13 | if (name == "calibration") { 14 | #ifdef USE_CAMERA_CALIBRATOR 15 | return buildCameraCalibrator(textureId, width, height); 16 | #else 17 | assert(false && "Camera calibrator not built"); 18 | #endif 19 | } else if (name == "recording" || name == "external") { 20 | return buildRecorder(textureId, width, height, *settings); 21 | } else if (name == "tracking") { 22 | #ifdef USE_CUSTOM_VIO 23 | return buildTracking(textureId, width, height, *settings); 24 | #else 25 | assert(false && "custom VIO not built"); 26 | #endif 27 | } else if (name == "gpu_examples") { 28 | #ifdef USE_GPU_EXAMPLES 29 | return buildGpuExample(textureId, width, height, *settings); 30 | #else 31 | assert(false && "GPU examples not built"); 32 | #endif 33 | } else { 34 | assert(false && "no such module"); 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/FrequencyMonitor.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester; 2 | 3 | import android.os.SystemClock; 4 | 5 | public class FrequencyMonitor { 6 | private static final double REPORT_INTERVAL_SECONDS = 2.0; 7 | 8 | public interface Listener { 9 | void onFrequency(double freq); 10 | } 11 | 12 | private final Listener mListener; 13 | private boolean mRunning; 14 | private long mNSamples; 15 | private long mLastReportNs; 16 | private double mLatestFrequency; 17 | 18 | FrequencyMonitor(Listener listener) { 19 | mListener = listener; 20 | } 21 | 22 | public void start() { 23 | mRunning = true; 24 | mLastReportNs = SystemClock.elapsedRealtimeNanos(); 25 | } 26 | 27 | public void stop() { 28 | mRunning = false; 29 | } 30 | 31 | public void onSample() { 32 | if (mRunning) { 33 | mNSamples++; 34 | final long curNs = SystemClock.elapsedRealtimeNanos(); 35 | if (curNs > mLastReportNs + REPORT_INTERVAL_SECONDS * 1e9) { 36 | final double dt = (curNs - mLastReportNs) * 1e-9; 37 | mLatestFrequency = mNSamples / dt; 38 | mListener.onFrequency(mLatestFrequency); 39 | mLastReportNs = curNs; 40 | mNSamples = 0; 41 | } 42 | } 43 | } 44 | 45 | public double getLatestFrequency() { 46 | return mLatestFrequency; 47 | } 48 | } -------------------------------------------------------------------------------- /configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | GOOGLE_JSON=app/google-services.json 5 | if [ ! -f "$GOOGLE_JSON" ]; then 6 | echo "No $GOOGLE_JSON, copying dummy" 7 | cp app/dummy-google-services.json "$GOOGLE_JSON" 8 | fi 9 | 10 | # merge settings files 11 | INTEGRATION_PREFS="custom-vio/viotester-integration/android/preferences.xml" 12 | INTEGRATION_ARRAYS="custom-vio/viotester-integration/android/arrays.xml" 13 | ROOT_PREFS="app/src/main/res/xml/root_preferences.xml" 14 | ROOT_ARRAYS="app/src/main/res/values/arrays.xml" 15 | 16 | WARN_BANNER="" 17 | 18 | printf "$WARN_BANNER" > "$ROOT_PREFS" 19 | printf "$WARN_BANNER" > "$ROOT_ARRAYS" 20 | if [ -f "$INTEGRATION_PREFS" ]; then 21 | echo "has $INTEGRATION_PREFS, merging settings XMLs" 22 | # Every piece in Android Gradle Plugin and the XML settings mechanism seems to be 23 | # actively working to prevent doing this any other way 24 | sed "s___g" < preferences.xml >> "$ROOT_PREFS" 25 | sed "s___g" < "$INTEGRATION_PREFS" | sed "s___g" >> "$ROOT_PREFS" 26 | 27 | sed "s___g" < arrays.xml >> "$ROOT_ARRAYS" 28 | sed "s___g" < "$INTEGRATION_ARRAYS" >> "$ROOT_ARRAYS" 29 | else 30 | echo "no custom preferences using preferences as-is" 31 | sed "s___g" < preferences.xml >> "$ROOT_PREFS" 32 | cat < arrays.xml >> "$ROOT_ARRAYS" 33 | fi 34 | cat < reset.xml >> "$ROOT_PREFS" 35 | printf "$WARN_BANNER" >> "$ROOT_PREFS" 36 | printf "$WARN_BANNER" >> "$ROOT_ARRAYS" 37 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/TrackingOutput.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester; 2 | 3 | public class TrackingOutput { 4 | 5 | public static final int STATUS_INIT = 0; 6 | public static final int STATUS_TRACKING = 1; 7 | public static final int STATUS_LOST_TRACKING = 2; 8 | 9 | private double[] output; 10 | private int status; 11 | private String statsString; 12 | private boolean hasPose; 13 | 14 | TrackingOutput(double[] output, int status, String statsString) { 15 | this.hasPose = output != null; 16 | if (this.hasPose) 17 | this.output = output; 18 | else 19 | this.output = new double[] {0,0,0,0,0,0,0,0}; 20 | 21 | this.status = status; 22 | this.statsString = statsString; 23 | } 24 | 25 | public double time() { 26 | return output[0]; 27 | } 28 | 29 | public double x() { 30 | return output[1]; 31 | } 32 | 33 | public double y() { 34 | return output[2]; 35 | } 36 | 37 | public double z() { 38 | return output[3]; 39 | } 40 | 41 | public double qx() { 42 | return output[4]; 43 | } 44 | 45 | public double qy() { 46 | return output[5]; 47 | } 48 | 49 | public double qz() { 50 | return output[6]; 51 | } 52 | 53 | public double qw() { 54 | return output[7]; 55 | } 56 | 57 | public int status() { 58 | return status; 59 | } 60 | 61 | public String statsString() { 62 | return statsString; 63 | } 64 | 65 | public boolean hasPose() { 66 | return hasPose; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/jni/opengl/camera_renderer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CAMERA_RENDERER_HPP 2 | #define CAMERA_RENDERER_HPP 3 | 4 | #include 5 | 6 | class CameraRenderer { 7 | public: 8 | /** 9 | * How to deal with the situation where the aspect ratios of the screen and 10 | * camera texture do not match 11 | */ 12 | enum class AspectFixMethod { 13 | /** Fill screen with camera texture and crop */ 14 | CROP = 0, 15 | /** Letterboxing: fit camera texture inside screen and fill rest with black */ 16 | LETTERBOX = 1 17 | }; 18 | static std::unique_ptr build(int width, int height); 19 | virtual ~CameraRenderer() = default; 20 | 21 | virtual void render() = 0; 22 | 23 | // data can be set either by giving an external buffer (in RGBA/BGRA format) or an OpenGL 24 | // texture ID. Do not mix these (e.g., call with a texture ID after calling with a CPU buffer) 25 | virtual void setTextureData(int textureWidth, int textureHeight, const void *buffer, AspectFixMethod aspectFix) = 0; 26 | virtual void setExternalTexture(int textureWidth, int textureHeight, int textureId, AspectFixMethod aspectFix) = 0; 27 | 28 | virtual bool matchDimensions(int w, int h) const = 0; 29 | 30 | /** 31 | * Get the screen width of the camera texture in pixels 32 | * after transforming according to the AspectFixMethod 33 | */ 34 | virtual int getActiveWidth() const = 0; 35 | 36 | /** 37 | * Get the height of the camera texture in pixels 38 | * after transforming according to the AspectFixMethod 39 | */ 40 | virtual int getActiveHeight() const = 0; 41 | }; 42 | 43 | #endif -------------------------------------------------------------------------------- /app/src/arcoreandengine/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | 9 | 10 | 16 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/viotester_surface_view.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 16 | 17 | 29 | 30 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/jni/opengl/gpu_camera_adapter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GPU_CAMERA_ADAPTER_HPP 2 | #define GPU_CAMERA_ADAPTER_HPP 3 | 4 | #include 5 | #include 6 | namespace cv { class Mat; } 7 | 8 | struct GpuCameraAdapter { 9 | struct TextureAdapter { 10 | const int width; 11 | const int height; 12 | 13 | enum class Type { 14 | RGBA, 15 | BGRA, 16 | /** 17 | * Single-channel gray texture. Only useful if further processed on the GPU 18 | * since it's not possible to read this to CPU memory as a single-channel image 19 | * in OpenGL ES 20 | */ 21 | GRAY, 22 | /** 23 | * Grayscale image where groups of consecutive 4 pixels have been encoded 24 | * into the R,G,B,A channels of the texture whose width is 1/4 of the original. 25 | * Can be read as a single-channel gray image to the CPU, but not as useful on 26 | * the GPU 27 | */ 28 | GRAY_COMPRESSED 29 | }; 30 | 31 | virtual void render(bool toFrameBuffer = true) = 0; 32 | 33 | /** 34 | * Read cpuSize() pixels to a 4-channel image. Note that OpenGL ES, one can only copy 35 | * data to the CPU side as GL_RGBA so we always have 4 bytes per pixel. 36 | */ 37 | virtual void readPixels(uint8_t *pixels) = 0; 38 | virtual std::size_t readPixelsSize() const = 0; 39 | 40 | TextureAdapter(int w, int h); 41 | virtual ~TextureAdapter(); 42 | }; 43 | 44 | static std::unique_ptr create(int width, int height, int textureId); 45 | virtual std::unique_ptr createTextureAdapter(TextureAdapter::Type type) = 0; 46 | 47 | virtual ~GpuCameraAdapter(); 48 | 49 | static void readChecked(TextureAdapter &adapter, cv::Mat &mat); 50 | }; 51 | 52 | 53 | #endif -------------------------------------------------------------------------------- /app/src/main/jni/opengl/ext_ar_renderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include "ar_renderer.hpp" 6 | #include "util.hpp" 7 | 8 | namespace { 9 | std::unique_ptr arRenderer; 10 | } 11 | 12 | #define MY_JNI_FUNC(ret, x) JNIEXPORT ret JNICALL Java_org_example_viotester_ext_1ar_Renderer_ ## x 13 | 14 | extern "C" { 15 | MY_JNI_FUNC(void, onSurfaceCreated)(JNIEnv *env, jobject a, jobject b, jobject c) { 16 | log_debug("External AR onSurfaceCreated"); 17 | // reset everything 18 | arRenderer.reset(); 19 | } 20 | 21 | MY_JNI_FUNC(void, onSurfaceChanged)(JNIEnv*, jobject, jobject, jint width, jint height) { 22 | log_debug("External AR onSurfaceChanged %dx%d", width, height); 23 | if (!arRenderer) { 24 | arRenderer = ArRenderer::buildWithYIsUp(); 25 | } 26 | } 27 | 28 | MY_JNI_FUNC(void, onDrawFrame)(JNIEnv *env, jobject thiz, jdouble t, jfloatArray mvMatrix, jfloatArray projMatrix, jint screenWidth, jint screenHeight) { 29 | assert(arRenderer); 30 | auto *vmat = env->GetFloatArrayElements(mvMatrix, nullptr); 31 | auto *projMat = env->GetFloatArrayElements(projMatrix, nullptr); 32 | 33 | // we cannot be sure that no-one else, like the GPU texture readers have not changed 34 | // the viewport so must be set again 35 | glViewport(0, 0, screenWidth, screenHeight); 36 | 37 | arRenderer->setProjectionMatrix(projMat); 38 | arRenderer->setPose(t, vmat); 39 | 40 | env->ReleaseFloatArrayElements(mvMatrix, vmat, JNI_ABORT); 41 | env->ReleaseFloatArrayElements(projMatrix, projMat, JNI_ABORT); 42 | 43 | arRenderer->render(); 44 | } 45 | 46 | MY_JNI_FUNC(void, setPointCloud)(JNIEnv *env, jobject thiz, jfloatArray pointCloudData, jint size) { 47 | assert(arRenderer); 48 | auto *data = env->GetFloatArrayElements(pointCloudData, nullptr); 49 | arRenderer->setPointCloud(data, static_cast(size)); 50 | 51 | env->ReleaseFloatArrayElements(pointCloudData, data, JNI_ABORT); 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/PermissionHelper.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.net.Uri; 8 | import android.provider.Settings; 9 | import androidx.core.app.ActivityCompat; 10 | import androidx.core.content.ContextCompat; 11 | 12 | /** 13 | * Helper to ask camera permission. 14 | */ 15 | public class PermissionHelper { 16 | private static final String[] PERMISSIONS = { 17 | Manifest.permission.CAMERA, 18 | Manifest.permission.ACCESS_FINE_LOCATION 19 | }; 20 | private static final int PERMISSION_REQUEST_CODE = 0; 21 | 22 | /** 23 | * Check to see we have the necessary permissions for this app. 24 | */ 25 | public static boolean havePermissions(Activity activity) { 26 | for (String p : PERMISSIONS) { 27 | if (ContextCompat.checkSelfPermission(activity, p) != PackageManager.PERMISSION_GRANTED) 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | /** 34 | * Check to see we have the necessary permissions for this app, and ask for them if we don't. 35 | */ 36 | public static void requestPermissions(Activity activity) { 37 | ActivityCompat.requestPermissions( 38 | activity, PERMISSIONS, PERMISSION_REQUEST_CODE); 39 | } 40 | 41 | /** 42 | * Check to see if we need to show the rationale for this permission. 43 | */ 44 | public static boolean shouldShowRequestPermissionRationale(Activity activity) { 45 | for (String p : PERMISSIONS) { 46 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, p)) 47 | return true; 48 | } 49 | return false; 50 | } 51 | 52 | /** 53 | * Launch Application Setting to grant permission. 54 | */ 55 | public static void launchPermissionSettings(Activity activity) { 56 | Intent intent = new Intent(); 57 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 58 | intent.setData(Uri.fromParts("package", activity.getPackageName(), null)); 59 | activity.startActivity(intent); 60 | } 61 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/jni/opengl/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace glUtil { 7 | const GLfloat screenQuadVertices[] = { 8 | -1.0f, -1.0f, 0.0f, +1.0f, -1.0f, 0.0f, 9 | -1.0f, +1.0f, 0.0f, +1.0f, +1.0f, 0.0f, 10 | }; 11 | 12 | const GLfloat screenQuadTextureCoordinates[] = { 13 | 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 14 | }; 15 | 16 | void checkError(const char* op) { 17 | GLint error; 18 | bool any = false; 19 | while((error = glGetError())) { 20 | any = true; 21 | log_error("operation %s produced glError (0x%x)\n", op, error); 22 | } 23 | if (any) { 24 | abort(); 25 | } 26 | } 27 | 28 | static GLuint loadShader(GLenum shaderType, const char* shaderSource) { 29 | const GLuint shader = glCreateShader(shaderType); 30 | assert(shader); 31 | 32 | glShaderSource(shader, 1, &shaderSource, nullptr); 33 | glCompileShader(shader); 34 | GLint compiled = 0; 35 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 36 | 37 | if (!compiled) { 38 | GLint len = 0; 39 | 40 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); 41 | assert(len); 42 | 43 | std::vector buf(static_cast(len)); 44 | glGetShaderInfoLog(shader, len, nullptr, buf.data()); 45 | log_error("Error compiling shader %d:\n%s\n", shaderType, buf.data()); 46 | glDeleteShader(shader); 47 | assert(false); 48 | } 49 | 50 | return shader; 51 | } 52 | 53 | GLuint createProgram(const char* vertexSource, const char* fragmentSource) { 54 | const GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource); 55 | const GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentSource); 56 | const GLuint program = glCreateProgram(); 57 | assert(program); 58 | glAttachShader(program, vertexShader); 59 | checkError("glAttachShader"); 60 | glAttachShader(program, fragmentShader); 61 | checkError("glAttachShader"); 62 | glLinkProgram(program); 63 | GLint linkStatus = GL_FALSE; 64 | glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); 65 | if (linkStatus != GL_TRUE) { 66 | GLint bufLength = 0; 67 | glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); 68 | if (bufLength) { 69 | std::vector buf(static_cast(bufLength)); 70 | glGetProgramInfoLog(program, bufLength, nullptr, buf.data()); 71 | log_error("Could not link program:\n%s\n", buf.data()); 72 | } 73 | glDeleteProgram(program); 74 | assert(false); 75 | } 76 | return program; 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/AssetCopier.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.google.android.gms.common.util.IOUtils; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | 14 | import androidx.preference.PreferenceManager; 15 | 16 | /** 17 | * Handles copying of Android assets to files in the cache folder so that they can be accessed 18 | * directly from native code using the file names. Not sure why this is necessary in the first 19 | * place 20 | */ 21 | public class AssetCopier { 22 | // changing this string is the easy way to make things work if one changes the build 23 | // variants that use SLAM 24 | public static final String HAS_SLAM_FILES_KEY = "has_slam_files_2"; 25 | public static final String ASSET_PATH_KEY_ORB_VOCABULARY = "asset_path_orb_vocabulary"; 26 | 27 | 28 | private static final String TAG = AssetCopier.class.getSimpleName(); 29 | private final Context mContext; 30 | 31 | AssetCopier(Context context) { 32 | mContext = context; 33 | copySlamAssets(); 34 | } 35 | 36 | private void copySlamAssets() { 37 | String orbVocabPath = copyAssetToCache("orb_vocab.dbow2"); 38 | 39 | boolean hasSlamAssets = orbVocabPath != null; 40 | 41 | // hacky: store to shared preferences so that these are easily accessible from 42 | // any activity that may need them. Alternatively, one could handle all of this only 43 | // in the relevant actitivy, but then it hard to do it only on app startup and not 44 | // every time the relevant view is opened. Also at least some of this info may be 45 | // needed in multiple places, e.g., settings view + algorithm activity 46 | PreferenceManager.getDefaultSharedPreferences(mContext) 47 | .edit() 48 | .putBoolean(HAS_SLAM_FILES_KEY, hasSlamAssets) 49 | .putString(ASSET_PATH_KEY_ORB_VOCABULARY, orbVocabPath) 50 | .apply(); 51 | } 52 | 53 | private String copyAssetToCache(String assetName) { 54 | final File targetFile = new File(mContext.getCacheDir(), "asset_" + assetName); 55 | final String targetFn = targetFile.getAbsolutePath(); 56 | Log.d(TAG, "copying asset " + assetName + " to " + targetFn); 57 | try ( 58 | InputStream is = mContext.getAssets().open(assetName); 59 | OutputStream os = new FileOutputStream(targetFile)) { 60 | IOUtils.copyStream(is, os); 61 | return targetFn; 62 | } catch (IOException e) { 63 | Log.w(TAG, "Failed to copy asset " + assetName + ": " + e); 64 | return null; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/arengine/java/org/example/viotester/arengine/DisplayRotationHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | // NOTE: this file is based on the Huawei AREngine sample app source code, 16 | // which is based on the ARCore sample source code but the changes are not 17 | // clearly marked. In this file, the changes are marked with respect to the 18 | // Huawei sample code 19 | package org.example.viotester.arengine; // NOTE: changed package name 20 | import android.content.Context; 21 | import android.hardware.display.DisplayManager; 22 | import android.hardware.display.DisplayManager.DisplayListener; 23 | import android.view.Display; 24 | import android.view.WindowManager; 25 | 26 | import com.huawei.hiar.ARSession; 27 | 28 | public class DisplayRotationHelper implements DisplayListener{ 29 | 30 | private boolean mViewportChanged; 31 | private int mViewportWidth; 32 | private int mViewportHeight; 33 | private final Context mContext; 34 | private final Display mDisplay; 35 | 36 | 37 | public DisplayRotationHelper(Context context) { 38 | this.mContext = context; 39 | mDisplay = context.getSystemService(WindowManager.class).getDefaultDisplay(); 40 | } 41 | 42 | public void onResume() { 43 | mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, null); 44 | } 45 | 46 | public void onPause() { 47 | mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); 48 | } 49 | 50 | public void onSurfaceChanged(int width, int height) { 51 | mViewportWidth = width; 52 | mViewportHeight = height; 53 | mViewportChanged = true; 54 | } 55 | 56 | public void updateSessionIfNeeded(ARSession session) { 57 | if (mViewportChanged) { 58 | int displayRotation = mDisplay.getRotation(); 59 | session.setDisplayGeometry(displayRotation, mViewportWidth, mViewportHeight); 60 | mViewportChanged = false; 61 | } 62 | } 63 | 64 | public int getRotation() { 65 | return mDisplay.getRotation(); 66 | } 67 | 68 | @Override 69 | public void onDisplayAdded(int displayId) { 70 | 71 | } 72 | 73 | @Override 74 | public void onDisplayRemoved(int displayId) { 75 | 76 | } 77 | 78 | @Override 79 | public void onDisplayChanged(int displayId) { 80 | mViewportChanged = true; 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/ShareListActivity.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.text.format.Formatter; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.widget.AdapterView; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.ListView; 12 | 13 | import java.io.File; 14 | import java.util.ArrayList; 15 | 16 | import androidx.appcompat.app.AppCompatActivity; 17 | import androidx.core.content.FileProvider; 18 | 19 | public class ShareListActivity extends AppCompatActivity { 20 | private static final String TAG = ShareListActivity.class.getName(); 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_share_list); 26 | ListView listView = findViewById(R.id.share_data_list); 27 | listView.setEmptyView(findViewById(R.id.share_data_empty_list)); 28 | populateListView(listView); 29 | } 30 | 31 | private void populateListView(ListView listView) { 32 | final ArrayList listItems = new ArrayList<>(); 33 | File[] files = DataRecorder.getFolder(getExternalCacheDir()).listFiles(); 34 | if (files == null) files = new File[0]; 35 | final ArrayList fileList = new ArrayList<>(); 36 | 37 | for (File f : files) { 38 | String fn = f.getName(); 39 | if (fn.endsWith(".tar")) { 40 | String text = fn + " (" + Formatter.formatShortFileSize(this, f.length()) + ")"; 41 | listItems.add(text); 42 | fileList.add(f); 43 | } 44 | } 45 | 46 | listView.setAdapter(new ArrayAdapter<>(this, R.layout.share_list_item, listItems)); 47 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 48 | @Override 49 | public void onItemClick(AdapterView adapterView, View view, int i, long l) { 50 | Log.d(TAG, "selected item " + i + ": " + listItems.get(i)); 51 | File f = fileList.get(i); 52 | 53 | Uri contentUri = FileProvider.getUriForFile(ShareListActivity.this, "org.example.viotester.fileprovider", f); 54 | if (contentUri == null) 55 | throw new RuntimeException("failed to create contentURI for" + f.getAbsolutePath()); 56 | 57 | Intent shareIntent = new Intent(); 58 | shareIntent.setAction(Intent.ACTION_SEND); 59 | shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 60 | shareIntent.setDataAndType(contentUri, getContentResolver().getType(contentUri)); 61 | shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 62 | startActivity(Intent.createChooser(shareIntent, "Choose an app for sharing the recording")); 63 | } 64 | }); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/jni/algorithm_module.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALGORITHM_MODULE_HPP 2 | #define ALGORITHM_MODULE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include "jsonl-recorder/types.hpp" 8 | 9 | namespace cv { class Mat; } 10 | 11 | class AlgorithmModule { 12 | public: 13 | using Vector3d = recorder::Vector3d; 14 | using Pose = recorder::Pose; 15 | using json = nlohmann::json; 16 | 17 | struct Gps { 18 | double latitude; 19 | double longitude; 20 | double altitude = 0; 21 | float accuracy; 22 | }; 23 | 24 | struct CameraIntrinsics { 25 | int cameraIndex = 0; 26 | float focalLengthX, focalLengthY; 27 | float principalPointX, principalPointY; 28 | }; 29 | 30 | // these may be called from the GL thread 31 | static std::unique_ptr build(int textureId, int width, int height, const std::string &name, const json *settings = nullptr); 32 | virtual ~AlgorithmModule() = default; 33 | 34 | // these methods are guaranteed to be called from an "algorithm/sensor thread" 35 | virtual void addGyro(double t, const Vector3d &val) = 0; 36 | virtual void addAcc(double t, const Vector3d &val) = 0; 37 | virtual void addGps(double t, const Gps &gps) { (void)t; (void)gps; }; 38 | virtual void addJsonData(const json &json) { (void)json; }; 39 | virtual std::string status() const { return ""; } 40 | virtual int trackingStatus() const { return -1; }; 41 | virtual bool pose(Pose &pose) const { (void)pose; return false; }; 42 | 43 | // these methods are called from the OpenGL thread 44 | virtual void addFrame(double t, const CameraIntrinsics &cameraIntrinsics) = 0; 45 | 46 | /** 47 | * If this module supports visualizations, initialize visualizations for 48 | * given screen size 49 | * @param width screen / visualization window width 50 | * @param height screen / visualization window height 51 | */ 52 | virtual void setupRendering(int width, int height) { (void)width; (void)height; } 53 | 54 | /** 55 | * Render visualizations, if any 56 | * @param t Current time (real time) 57 | */ 58 | virtual void render(double t) { (void)t; } 59 | 60 | // This helper function will protect all calls with a single mutex 61 | static std::unique_ptr makeThreadSafe(AlgorithmModule *nonThreadSafe); 62 | }; 63 | 64 | class CpuAlgorithmModule : public AlgorithmModule { 65 | public: 66 | virtual void addFrame(double t, const cv::Mat &grayFrame, cv::Mat *colorFrame, 67 | const CameraIntrinsics &cameraIntrinsics, 68 | cv::Mat &outputColorFrame) = 0; 69 | 70 | void addFrame(double t, const CameraIntrinsics &cameraIntrinsics) final; 71 | void setupRendering(int width, int height) final; 72 | void render(double t) final; 73 | 74 | virtual ~CpuAlgorithmModule(); 75 | 76 | protected: 77 | CpuAlgorithmModule(int textureId, int width, int height); 78 | bool visualizationEnabled = true; 79 | 80 | private: 81 | struct impl; 82 | std::unique_ptr pimpl; 83 | }; 84 | 85 | #endif -------------------------------------------------------------------------------- /app/src/main/jni/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | set(CMAKE_CXX_STANDARD 14) 4 | add_compile_options("-Wextra") 5 | 6 | message(STATUS "ANDROID_ABI=${ANDROID_ABI}") 7 | 8 | # hacky 9 | include_directories(".") 10 | add_definitions(-include always_assert.h) 11 | 12 | set(target vio_main) 13 | project(${target} CXX) 14 | 15 | set(ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../..") 16 | 17 | if(EXISTS ${ROOT_DIR}/mobile-cv-suite) 18 | # if mobile-cv-suite exists in the root directory, use it 19 | set(MCS_TARGET_DIR ${ROOT_DIR}/mobile-cv-suite) 20 | else() 21 | # otherwise download a pre-built release from Github 22 | set(MCS_VERSION 1.4.4) 23 | set(MCS_TARGET_DIR ${ROOT_DIR}/app/.cxx/mobile-cv-suite-${MCS_VERSION}) 24 | if(NOT EXISTS ${MCS_TARGET_DIR}) 25 | set(MCS_ARCHIVE_FN ${CMAKE_CURRENT_BINARY_DIR}/mobile-cv-suite.tar-${MCS_VERSION}.gz) 26 | file(DOWNLOAD https://github.com/AaltoML/mobile-cv-suite/releases/download/${MCS_VERSION}/mobile-cv-suite.tar.gz ${MCS_ARCHIVE_FN} SHOW_PROGRESS) 27 | file(MAKE_DIRECTORY ${MCS_TARGET_DIR}) 28 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${MCS_ARCHIVE_FN} WORKING_DIRECTORY ${MCS_TARGET_DIR}) 29 | endif() 30 | endif() 31 | set(mobile-cv-suite_DIR "${MCS_TARGET_DIR}") 32 | #find_package(mobile-cv-suite REQUIRED PATHS "${MCS_TARGET_DIR}") 33 | 34 | set(VIO_ANDROID_SRC 35 | camera_worker.cpp 36 | algorithm_worker.cpp 37 | algorithm_module.cpp 38 | algorithm_module_wrappers.cpp 39 | native_camera_session.cpp 40 | modules/camera_calibrator.cpp 41 | modules/recorder.cpp 42 | opengl/ar_renderer.cpp 43 | opengl/camera_renderer.cpp 44 | opengl/ext_ar_renderer.cpp 45 | opengl/gpu_camera_adapter.cpp 46 | opengl/util.cpp) 47 | 48 | set(VIO_ANDROID_FLAGS "-DDUMMY_FLAG") 49 | 50 | option(USE_GPU_EXAMPLES "Compile with GPU examples" OFF) 51 | option(USE_CAMERA_CALIBRATOR "Compile with camera calibrator" ON) 52 | 53 | if (USE_GPU_EXAMPLES) 54 | list(APPEND VIO_ANDROID_FLAGS "-DUSE_GPU_EXAMPLES") 55 | list(APPEND VIO_ANDROID_SRC modules/gpu_examples.cpp) 56 | endif() 57 | 58 | if (USE_CAMERA_CALIBRATOR) 59 | list(APPEND VIO_ANDROID_FLAGS "-DUSE_CAMERA_CALIBRATOR") 60 | list(APPEND VIO_ANDROID_SRC modules/camera_calibrator.cpp) 61 | endif() 62 | 63 | add_library(${target} SHARED ${VIO_ANDROID_SRC}) 64 | 65 | set(VIO_ANDROID_LIBS 66 | "GLESv3" 67 | mobile-cv-suite::core 68 | camera2ndk 69 | mediandk 70 | android 71 | log) 72 | 73 | option(USE_CUSTOM_VIO "Compile with a custom VIO module" OFF) 74 | if (USE_CUSTOM_VIO) 75 | # assumes the VIO module also uses mobile-cv-suite 76 | message(STATUS "Custom VIO build ${target}") 77 | add_subdirectory("${ROOT_DIR}/custom-vio/viotester-integration/android" build-custom-vio) 78 | list(APPEND VIO_ANDROID_LIBS ${CUSTOM_VIO_LIBS}) 79 | list(APPEND VIO_ANDROID_FLAGS "-DUSE_CUSTOM_VIO") 80 | else() 81 | find_package(mobile-cv-suite REQUIRED) 82 | endif() 83 | target_compile_definitions(${target} PRIVATE ${VIO_ANDROID_FLAGS}) 84 | 85 | target_link_libraries(${target} ${VIO_ANDROID_LIBS}) 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visual-Inertial Odometry (VIO) benchmark app for Android 2 | 3 | ## Usage 4 | 5 | There different modes in the application are explained below 6 | 7 | ### Data collection 8 | 9 | Saves data as tarballs to the "external cache" folder on the disk. Accessible via ADB 10 | 11 | # list files 12 | adb shell ls -lha /storage/emulated/0/Android/data/org.example.viotester/cache/recordings 13 | 14 | # download data (example) 15 | adb pull /storage/emulated/0/Android/data/org.example.viotester/cache/recordings/20191031104043.tar 16 | 17 | These files can also be shared directly from the phone using the _Share recording_ button. 18 | To remove the recordings from the phone, either use ADB (`adb shell rm ...`) 19 | or just clear the cache from Android settings, e.g., 20 | Long tap icon -> App info -> Storage & Cache -> Clear cache. 21 | 22 | For data collection, the recommended settings are 23 | * Resolution: 1280x960 24 | * (Target) FPS: 30 25 | 26 | TODO: the real recording FPS will not currently be stored to the recorded video, 27 | which may produce speedups in playback. However, this does not otherwise affect the 28 | algorithm and since the true timestamps of the frames are stored in the other recording file, the 29 | problem is fixable later with, e.g, FFMpeg. 30 | 31 | ### Camera calibration 32 | 33 | Approximates camera parameters using OpenCV on phones whose Camera 2 API does provide these values 34 | 35 | 1. Print or open [this pattern](https://raw.githubusercontent.com/opencv/opencv/3.4/doc/acircles_pattern.png) on your screen. 36 | 2. Move camera so that the pattern is viewed from different angles and at different locations on the screen (center & edges) 37 | 3. The camera parameters at the current resolution are printed on screen (TODO: share to Slack etc.) 38 | 39 | ## Build 40 | 41 | First, configure using 42 | 43 | ./configure.sh 44 | 45 | Note: you will also have to do this after changing anything in `preferences.xml`. In addition to 46 | merging the available settings XML files, it creates a dummy `google-services.json` if you don't 47 | have one. 48 | 49 | ### Linux/Mac setup 50 | 51 | On Debian Stretch, these packages needed to be installed with apt-get 52 | 53 | * ninja 54 | * ccache 55 | * openjdk-8-jdk 56 | 57 | Also an Android SDK and Android NDK need to be installed. The project can be run in Android Studio. 58 | 59 | ### ARCore test mode 60 | 61 | Can be used to compare other methods to Google ARCore. 62 | Enabled by changing the build flavor to `arcore`. 63 | 64 | ### AREngine test mode 65 | 66 | Enables comparison to Huawei AREngine. Enabled by changing the build flavor to `arengine`. 67 | Requirements: 68 | * Supported Huawei phone 69 | 70 | ## Copyright 71 | 72 | The files under `app/src/arcore` that say so at the top of the file, 73 | are Licensed under Apache 2 (© Google). They have been copied from the ARCore SDK examples. 74 | To see how they have been modified from their original versions, see comments beginning with "NOTE". 75 | 76 | The original code in this repository is also licensed under Apache 2.0. 77 | 78 | The depenency [mobile-cv-suite](https://github.com/AaltoML/mobile-cv-suite) 79 | is a collection of libraries licensed under various OSS licenses, including LGPL. See the repository for more details. 80 | -------------------------------------------------------------------------------- /app/src/main/res/layout/viotester_map_view.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 18 | 23 | 24 | 28 | 29 | 41 | 42 | 57 | 58 | 73 | 74 | 88 | 89 | -------------------------------------------------------------------------------- /app/src/main/jni/modules/recorder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../algorithm_module.hpp" 3 | #include "jsonl-recorder/recorder.hpp" 4 | #include "logging.hpp" 5 | #include 6 | #include 7 | 8 | struct RecordingModule : public CpuAlgorithmModule { 9 | bool recordCamera; 10 | std::unique_ptr recorder; 11 | int w, h; 12 | bool recordSensors; 13 | std::unique_ptr recorderThread; 14 | 15 | RecordingModule(int textureId, int width, int height, const json &settings) : CpuAlgorithmModule(textureId, width, height) { 16 | w = width; 17 | h = height; 18 | recorderThread = accelerated::Processor::createThreadPool(1); 19 | recordSensors = settings.at("recordSensors").get(); 20 | recordCamera = settings.at("recordCamera").get(); 21 | visualizationEnabled = true; 22 | 23 | auto recName = settings.at("recordingFileName"); 24 | auto videoRecName = settings.at("videoRecordingFileName"); 25 | std::string outputPath = recName.is_null() ? "" : recName.get(); 26 | recorder = recorder::Recorder::build(outputPath, 27 | (videoRecName.is_null() || !recordCamera) ? "" : videoRecName.get()); 28 | 29 | recorder->setVideoRecordingFps(settings.at("targetFps").get()); 30 | 31 | log_info("Recorder started, output %s", outputPath.c_str()); 32 | } 33 | 34 | void addGyro(double t, const recorder::Vector3d &val) final { 35 | if (recordSensors) 36 | recorderThread->enqueue([this, t, val]() { 37 | recorder->addGyroscope(t, val.x, val.y, val.z); 38 | }); 39 | } 40 | 41 | void addAcc(double t, const recorder::Vector3d &val) final { 42 | if (recordSensors) 43 | recorderThread->enqueue([this, t, val]() { 44 | recorder->addAccelerometer(t, val.x, val.y, val.z); 45 | }); 46 | } 47 | 48 | void addFrame(double t, const cv::Mat &grayFrame, cv::Mat *colorFrame, 49 | const CameraIntrinsics &cam, 50 | cv::Mat &outputColorFrame) final { 51 | if (recordCamera) { 52 | assert(colorFrame != nullptr); 53 | // TODO: render GPU texture directly 54 | auto frameData = recorder::FrameData { 55 | .t = t, 56 | .cameraInd = cam.cameraIndex, 57 | .focalLengthX = cam.focalLengthX, 58 | .focalLengthY = cam.focalLengthY, 59 | .px = cam.principalPointX, 60 | .py = cam.principalPointY, 61 | .frameData = nullptr 62 | }; 63 | recorderThread->enqueue([this, frameData, colorFrame]() { 64 | auto f = frameData; 65 | f.frameData = colorFrame; 66 | recorder->addFrame(f); 67 | }); 68 | 69 | if (visualizationEnabled) { 70 | outputColorFrame = *colorFrame; 71 | } 72 | } 73 | } 74 | 75 | void addGps(double t, const AlgorithmModule::Gps &gps) final { 76 | recorderThread->enqueue([this, t, gps]() { 77 | recorder->addGps(t, gps.latitude, gps.longitude, gps.accuracy, gps.altitude); 78 | }); 79 | } 80 | 81 | void addJsonData(const json &json) final { 82 | recorderThread->enqueue([this, json]() { 83 | recorder->addJson(json); 84 | }); 85 | } 86 | 87 | std::string status() const final { 88 | return "recording..."; 89 | } 90 | }; 91 | 92 | std::unique_ptr buildRecorder(int textureId, int w, int h, const AlgorithmModule::json &settings) { 93 | return std::make_unique(textureId, w, h, settings); 94 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 28 | 32 | 39 | 46 | 53 | 60 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 80 | 83 | 84 | 85 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /app/src/main/java/org/example/viotester/DataRecorder.java: -------------------------------------------------------------------------------- 1 | package org.example.viotester; 2 | import android.os.SystemClock; 3 | import android.util.Log; 4 | 5 | import com.google.android.gms.common.util.IOUtils; 6 | 7 | import org.kamranzafar.jtar.TarEntry; 8 | import org.kamranzafar.jtar.TarOutputStream; 9 | 10 | import java.io.BufferedInputStream; 11 | import java.io.BufferedOutputStream; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.text.SimpleDateFormat; 17 | import java.util.Date; 18 | import java.util.Locale; 19 | 20 | public class DataRecorder { 21 | private static final String TAG = DataRecorder.class.getName(); 22 | private final File mFolder; 23 | private final String mVideoFileName; 24 | private final String mTarFileName; 25 | private final String mLogFileName; 26 | private final String mInfoFileName; 27 | private final String mParametersFileName; 28 | private final boolean compress; 29 | 30 | static File getFolder(File cacheDir) { 31 | return new File(cacheDir, "recordings"); 32 | } 33 | 34 | public DataRecorder(File cacheDir, String prefix, boolean compress) { 35 | Log.d(TAG, "ctor"); 36 | 37 | this.compress = compress; 38 | 39 | final File rootFolder = getFolder(cacheDir); 40 | 41 | if (!rootFolder.exists() && !rootFolder.mkdirs()) { 42 | throw new RuntimeException("failed to create root folder " + rootFolder.getAbsolutePath()); 43 | } 44 | 45 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); 46 | if (!prefix.isEmpty()) prefix = prefix + "-"; 47 | final String name = prefix + dateFormat.format(new Date()); 48 | mFolder = new File(rootFolder, name); 49 | if (!mFolder.mkdir()) { 50 | throw new RuntimeException("failed to create folder " + name); 51 | } 52 | mVideoFileName = new File(mFolder, "data.avi").getAbsolutePath(); 53 | mLogFileName = new File(mFolder, "data.jsonl").getAbsolutePath(); 54 | mInfoFileName = new File(mFolder, "info.json").getAbsolutePath(); 55 | mParametersFileName = new File(mFolder, "parameters.txt").getAbsolutePath(); 56 | mTarFileName = new File(rootFolder, name + ".tar").getAbsolutePath(); 57 | 58 | Log.i(TAG,"video file " + mVideoFileName); 59 | Log.i(TAG, "sensor log file " + mLogFileName); 60 | } 61 | 62 | public String getVideoFileName() { 63 | return mVideoFileName; 64 | } 65 | public String getLogFileName() { 66 | return mLogFileName; 67 | } 68 | public String getInfoFileName() { return mInfoFileName; } 69 | public String getParametersFileName() { return mParametersFileName; } 70 | 71 | public void flush() { 72 | Log.d(TAG, "flush"); 73 | if (this.compress) { 74 | try { 75 | writeTarball(); 76 | } catch (IOException e) { 77 | throw new RuntimeException(e); 78 | } 79 | } 80 | } 81 | 82 | private void writeTarball() throws IOException { 83 | File[] files = mFolder.listFiles(); 84 | if (files == null) 85 | throw new IOException("failed to list files in " + mFolder.getAbsolutePath()); 86 | 87 | try (TarOutputStream out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(mTarFileName)))) { 88 | for (File f : files) { 89 | Log.d(TAG, "adding " + f.getName() + " to tar " + mTarFileName); 90 | out.putNextEntry(new TarEntry(f, f.getName())); 91 | try (BufferedInputStream origin = new BufferedInputStream(new FileInputStream(f))) { 92 | IOUtils.copyStream(origin, out); 93 | } 94 | out.flush(); 95 | } 96 | } 97 | 98 | Log.i(TAG, "tarball created successfully, clearing folder " + mFolder.getAbsolutePath()); 99 | boolean success = true; 100 | for (File f : files) if (!f.delete()) success = false; 101 | if (!mFolder.delete()) success = false; 102 | if (!success) { 103 | throw new IOException("failed to clear folder " + mFolder); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 |