Камера Android влияет на датчики (акселерометр и магнитное поле), когда телефон смотрит на пользователя

Для приложения, которое я делаю, мне нужны камера и компас. В манифесте приложение настроено на ландшафтный режим.
Сначала я реализовал компас. Как было предложено разработчиками Android, я использовал два датчика — Акселерометр и Магнитное поле. Вот как я это сделал:
Моя активность реализована SensorEventListener. В onCreate() я инициализирую свой sensorManager, используя:

sManager = (SensorManager) getSystemService(SENSOR_SERVICE);

Я регистрирую своих слушателей в onResume() вот так:

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);

и, конечно же, отменить их регистрацию в onPause(). Я не использую onAccuracyChanged(). вот что я делаю в 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]));
    }
}

Как видите, я различаю, когда телефон лежит на столе, и когда пользователь держит его (как если бы вы делали снимок). Когда я использую только этот код, все более или менее работает отлично. Я получаю правильные значения азимута как когда телефон лежит на столе, так и когда держу его перпендикулярно столу (разница примерно в 5-10 градусов, но я могу с этим смириться).

Проблема начинается при добавлении предварительный просмотр камеры в приложение. У меня есть реализация моей деятельности SurfaceHolder.Callback. Я инициализирую свою камеру в 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);
}

Вот как я реализую интерфейс:

@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;
}

Когда я добавляю код камеры в свой проект и показываю камеру на экране телефона, мои датчики не работают должным образом, когда телефон внезапно оказывается перпендикулярным. Если телефон лежит ровно на столе, значения азимута, которые я получаю, верны. Когда телефон держится перпендикулярно столу, мои значения азимута отклоняются примерно на 40 градусов (хотя и остаются стабильными).

Я пытался найти решение (как самостоятельно, так и в Интернете), но до сих пор усилия были напрасны. Я хотел бы получить указания о том, как решить эту проблему.
Спасибо!


person Ungoliant    schedule 21.12.2016    source источник
comment
Это одно и то же устройство?   -  person Alex Cohn    schedule 22.12.2016
comment
Ага. Проверка на том же устройстве. Тем временем я также отправил свое приложение нескольким друзьям и попросил их проверить его (с включенным компасом и камерой). Похоже, что большую часть времени разница составляла около 5-10 (с чем я могу жить), однако все еще было несколько проверок, которые имели разницу ~ 30 градусов (около 10% проверок). Когда проверяли без камеры, максимальная разница была где-то 5-10. Так что это не так плохо, как в моем телефоне, но все же немного ненадежно. Если я не получу решение, я, вероятно, оставлю его сейчас и посмотрю, нужна ли мне более высокая точность в будущем.   -  person Ungoliant    schedule 22.12.2016
comment
Проблема может заключаться в том, что обратные вызовы камеры и компаса конкурируют за ЦП в основном (UI) потоке. Вы должны открыть камеру на вторичном HandlerThread.   -  person Alex Cohn    schedule 22.12.2016


Ответы (1)


Первый датчик TYPE_MAGNETIC_FIELD будет доступен не на всех устройствах. Вы можете использовать только датчик TYPE_ACCELEROMETER для выполнения вашего требования.

Получить датчик акселерометра

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

Просто сравните и скопируйте значения при вызове события изменения датчика

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

Затем вы можете использовать функцию ниже, чтобы получить значения датчиков для всех осей.

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;
}
person Dharmendra    schedule 21.12.2016