/* The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is collection of files collectively known as Open Camera. The Initial Developer of the Original Code is Almalence Inc. Portions created by Initial Developer are Copyright (C) 2013 by Almalence Inc. All Rights Reserved. */ package com.almalence.plugins.capture.panoramaaugmented; // Usage: // - create in activity onCreate // - call SetFrameParameters just before starting camera preview // - call NewData(data) from onPreviewFrame // - call close() from activity onDestroy import java.io.Closeable; import java.lang.reflect.Constructor; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.os.Message; public class VfGyroSensor implements Closeable, Handler.Callback { private static final boolean SMOOTH_MOTION = true; private static final boolean EARLY_TIMESTAMP = true; // false; // custom VF GYRO sensor type public static final int TYPE_VF_GYROSCOPE = Sensor.TYPE_GYROSCOPE | 0x01000000; public String getName() { return "ViewFinder Gyro"; } public String getVendor() { return "Almalence"; } public int getType() { return TYPE_VF_GYROSCOPE; } public int getVersion() { return 1; } public float getMaximumRange() { return 2 * (float) Math.PI; }// full circle in a second public float getResolution() { return 1e-5f; } public float getPower() { return 0; } public int getMinDelay() { return 16000; } // ~ 60 fps int getHandle() { return 0; } Constructor<SensorEvent> EventConstructor = null; SensorEvent sensorEvent, sensorEventPrev; private float[] sensorValuesPrev = new float[3]; SensorEventListener m_listener; private boolean m_justStability; private byte[] datacopy; private long timestamp; private long timestamp_initial; private VfGyroSensor mThiz; private boolean doneWithNewData = true; private int nBlankRuns; private final Handler H = new Handler(this); private static final int MSG_SMOOTHER_GYRO = 1; public VfGyroSensor(SensorEventListener listener) { mThiz = this; doneWithNewData = true; Initialize(); m_listener = listener; m_justStability = false; sensorValuesPrev[0] = sensorValuesPrev[1] = sensorValuesPrev[2] = 0; nBlankRuns = 0; // A hack to construct SensorEvent try { EventConstructor = SensorEvent.class.getDeclaredConstructor(int.class); // Parameter // is // the // type // of // params // in // the // constructor // (e.g. // String) EventConstructor.setAccessible(true); sensorEvent = EventConstructor.newInstance(3); sensorEventPrev = EventConstructor.newInstance(3); sensorEvent.accuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH; sensorEvent.sensor = (Sensor) null; sensorEventPrev.accuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH; sensorEventPrev.sensor = (Sensor) null; } catch (Exception e) { e.printStackTrace(); } } public void open() { Initialize(); } @Override public void close() // throws IOException { Release(); } @Override public void finalize() throws Throwable { this.close(); super.finalize(); } public void SetListener(SensorEventListener listener) { m_listener = listener; } public void SetStabilityOnly(boolean justStability) { m_justStability = justStability; } public boolean handleMessage(Message msg) { if (msg.what == MSG_SMOOTHER_GYRO) NewData(null); return true; } public void NewData(byte[] data) { if (EARLY_TIMESTAMP) timestamp = System.nanoTime(); synchronized (sensorEventPrev) { // For smoother GUI: if frame is still in processing - pass the // previous values, // but take this (timestamp, values) into account for the next // calculation // Simpler way: if not finished with previous frame - do not accept // the new one if (SMOOTH_MOTION && ((!doneWithNewData) || (data == null))) { if (nBlankRuns < 4) { sensorEventPrev.timestamp = System.nanoTime(); // timestamp; ++nBlankRuns; // emit sensor event if (m_listener != null) { m_listener.onSensorChanged(sensorEventPrev); H.sendEmptyMessageDelayed(MSG_SMOOTHER_GYRO, getMinDelay() / 1000); } } return; } } if ((!doneWithNewData) || (data == null)) return; if (datacopy == null) datacopy = new byte[data.length]; else if (datacopy.length != data.length) datacopy = new byte[data.length]; System.arraycopy(data, 0, datacopy, 0, data.length); doneWithNewData = false; if (EARLY_TIMESTAMP) { sensorEvent.timestamp = timestamp; } // To prevent pressure on UI part - run gyro processing in separate // thread new Thread(new Runnable() { @Override public void run() { synchronized (mThiz) { Update(datacopy, sensorEvent.timestamp, m_justStability); Get(sensorEvent.values); // clean any pending blank-run messages if (SMOOTH_MOTION) H.removeMessages(MSG_SMOOTHER_GYRO); float[] savedValues = new float[3]; savedValues[0] = sensorEvent.values[0]; savedValues[1] = sensorEvent.values[1]; savedValues[2] = sensorEvent.values[2]; synchronized (sensorEventPrev) { if (!EARLY_TIMESTAMP) sensorEvent.timestamp = System.nanoTime(); // if there were blank runs - correct for accumulated // error if (sensorEventPrev.timestamp != timestamp_initial) { long dt1 = sensorEvent.timestamp - timestamp_initial; long dt2 = sensorEventPrev.timestamp - timestamp_initial; if (dt1 != dt2) // replace with dt1 > dt2 { float norm = 1.f / (dt1 - dt2); for (int i = 0; i < 3; ++i) { float dx1 = dt1 * sensorEvent.values[i]; float dx2 = dt2 * sensorEventPrev.values[i]; sensorEvent.values[i] = (dx1 - dx2) * norm; } } } for (int i = 0; i < 3; ++i) { if (savedValues[i] * sensorValuesPrev[i] <= 0) sensorEventPrev.values[i] = 0; else if (Math.abs(savedValues[i]) < Math.abs(sensorValuesPrev[i])) sensorEventPrev.values[i] = savedValues[i]; else sensorEventPrev.values[i] = sensorValuesPrev[i]; sensorValuesPrev[i] = savedValues[i]; } sensorEventPrev.timestamp = sensorEvent.timestamp; timestamp_initial = sensorEvent.timestamp; nBlankRuns = 0; // emit sensor event if (m_listener != null) { m_listener.onSensorChanged(sensorEvent); if (SMOOTH_MOTION && (!m_justStability)) H.sendEmptyMessageDelayed(MSG_SMOOTHER_GYRO, getMinDelay() / 1000); } doneWithNewData = true; } } } }).start(); } public native void Initialize(); public native void Release(); public native void SetFrameParameters(int w, int h, float HorizontalFOV, float VerticalFOV); public native void Update(byte[] data, long timestamp, boolean justStability); public native long Get(float[] values); // return value is timestamp public static native void FixDrift(float[] values, boolean updateDrift); static { System.loadLibrary("utils-image"); System.loadLibrary("almalib"); System.loadLibrary("almashot-pano"); } }