Android camera affects sensors (Accelerometer & Magnetic Field) while phone faces user

970 views Asked by At

For an application I'm making, I need to have a camera and a compass. The application is set to be at landscape mode in the manifest.
First I've implemented the compass. As suggested in Android Developers, I used two sensors - Accelerometer and Magnetic Field. This is how I've done it:
I have my activity implement SensorEventListener. In onCreate() I initialize my sensorManager using:

sManager = (SensorManager) getSystemService(SENSOR_SERVICE);

I register my listeners in onResume() like so:

sManager.registerListener(this, sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager.SENSOR_DELAY_NORMAL);
sManager.registerListener(this, sManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),SensorManager.SENSOR_DELAY_NORMAL);

and of course unregister them in onPause(). I don't use onAccuracyChanged(). this is what I do in onSensorChanged():

@Override
public void onSensorChanged(SensorEvent event) {
    switch (event.sensor.getType()) {
        case Sensor.TYPE_MAGNETIC_FIELD:
            mags = event.values.clone();
            break;
        case Sensor.TYPE_ACCELEROMETER:
            accels = event.values.clone();
            break;
    }

    if (mags != null && accels != null) {
        gravity = new float[9];
        magnetic = new float[9];
        SensorManager.getRotationMatrix(gravity, magnetic, accels, mags);
        float[] outGravity = new float[9];

        float inclination = (float) Math.acos(gravity[8]);
        if (inclination < Math.toRadians(25)
                || inclination > Math.toRadians(155)) {
            // device is close to flat. Remap for landscape.
            SensorManager.remapCoordinateSystem(gravity, SensorManager.AXIS_Y,SensorManager.AXIS_MINUS_X, outGravity);
            SensorManager.getOrientation(outGravity, values);
        } else {
            // device is not flat. Remap for landscape and perpendicular
            SensorManager.remapCoordinateSystem(gravity, SensorManager.AXIS_X,SensorManager.AXIS_Z, outGravity);
            SensorManager.getOrientation(outGravity, values);
        }
        azimuth =  Math.round(Math.toDegrees(values[0]));
    }
}

As you can see, I differentiate between when the phone is lying flat on the table, and when the user holds it (as you would when taking a picture). When I use this code alone, everything works great more or less. I'm getting correct azimuth values both when phone is lying on the table and when holding it perpendicular to the table (about 5-10 degrees difference, but I can live with that).

The problem starts when adding the camera preview to the application. I have my activity implement SurfaceHolder.Callback. I initialize my camera in onCreate():

SurfaceView cameraView = (SurfaceView)findViewById(R.id.camera_view);
surfaceHolder = cameraView.getHolder();
surfaceHolder.addCallback(this);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
    surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

This is how I implement the interface:

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    camera = Camera.open();
    camera.setDisplayOrientation(0);
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
    if (isCameraOn) {
        camera.stopPreview();
        isCameraOn = false;
    }

    if (camera != null) {
        try {
            camera.setPreviewDisplay(surfaceHolder);
            camera.startPreview();
            isCameraOn = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    camera.stopPreview();
    camera.release();
    camera = null;
}

When I add the camera code to my project, and show the camera on the phone's screen, my sensors dont work properly when phone is perpendicular suddenly. If phone is lying flat on the table, the azimuth values I'm getting are correct. When phone is being held perpendicular to the table, my azimuth values are off by about 40 degrees (though stable).

I've tried looking for a solution (both by myself and online), but so far my efforts were in vain. I would love to get some direction on how to tackle this problem.
Thanks!

1

There are 1 answers

0
Dharmendra On

First TYPE_MAGNETIC_FIELD sensor will not available in all devices. You can use TYPE_ACCELEROMETER sensor alone to accomplish your requirement.

Retrieve accelerometer sensor

Sensor accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

Just compare and copy values when sensor change event call

@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
    mGravity = event.values;
}

Then you can use below function to get sensor values of all axis.

public int[] getDeviceAngles() {
  float[] g = mGravity.clone();
  double normOfG = Math.sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]);

  // Normalize the accelerometer vector
  g[0] = (float) (g[0] / normOfG);
  g[1] = (float) (g[1] / normOfG);
  g[2] = (float) (g[2] / normOfG);

  int x = (int) Math.round(Math.toDegrees(Math.atan2(g[1], g[0])));
  int pitch = (int) Math.round(Math.toDegrees(Math.atan2(g[1], g[2])));
  int rollValue = (int) Math.round(Math.toDegrees(Math.atan2(g[2], g[0])));
  int pitchValue = pitch * -1;
  int[] values = new int[3];
  values[0] = x;
  values[1] = pitchValue;
  values[2] = rollValue;
  //values contains: azimut, pitch and roll
  return values;
}