I am trying (again) to create camera preview logic that actually works properly, for all scenarios:
android.hardware.Camera and android.hardware.camera2
Since my minSdkVersion is 15, and since I am not especially concerned about performance, I am trying to use a TextureView. And, following the advice of fadden in places like here and here, I am trying to use setTransform() on that TextureView with an appropriate Matrix that:
TextureView completely, at the cost of cropping where the TextureView aspect ratio does not match the preview frame aspect ratioIn my case, the TextureView fills the screen, minus the status bar and navigation bar.
Starting with the adjustAspectRatio() from Grafika's PlayMovieActivity.java, I now have this:
private void adjustAspectRatio(int videoWidth, int videoHeight,
int rotation) {
if (iCanHazPhone) {
int temp=videoWidth;
videoWidth=videoHeight;
videoHeight=temp;
}
int viewWidth=getWidth();
int viewHeight=getHeight();
double aspectRatio=(double)videoHeight/(double)videoWidth;
int newWidth, newHeight;
if (getHeight()>(int)(viewWidth*aspectRatio)) {
newWidth=(int)(viewHeight/aspectRatio);
newHeight=viewHeight;
}
else {
newWidth=viewWidth;
newHeight=(int)(viewWidth*aspectRatio);
}
int xoff=(viewWidth-newWidth)/2;
int yoff=(viewHeight-newHeight)/2;
Matrix txform=new Matrix();
getTransform(txform);
float xscale=(float)newWidth/(float)viewWidth;
float yscale=(float)newHeight/(float)viewHeight;
txform.setScale(xscale, yscale);
switch(rotation) {
case Surface.ROTATION_90:
txform.postRotate(270, newWidth/2, newHeight/2);
break;
case Surface.ROTATION_270:
txform.postRotate(90, newWidth/2, newHeight/2);
break;
}
txform.postTranslate(xoff, yoff);
setTransform(txform);
}
Here, videoWidth and videoHeight are the size of the camera preview, and the method itself is implemented on a subclass of TextureView. I am calling this method when I have established what the camera preview size is and after the TextureView itself is resized.
This appears to be close but not completely correct. In particular, the iCanHazPhone hack — flipping the video width and height — is a stab in the dark, as without this, while a SONY Tablet Z2 works well, a Nexus 5 turns out horrible (stretched preview that does not fill the screen).
With iCanHazPhone set to true, I get good results on a Nexus 5:


With iCanHazPhone set to false, I get stuff like:

Similarly, with iCanHazPhone set to false, I get good results on a SONY Tablet Z2:

But if I flip it to true, I get:

My current theory is that different devices have different default camera orientations, and depending on that default orientation I need to flip the preview width and height in my calculations.
So, the questions:
Is the camera guaranteed (as much as anything involving Android hardware) to have a default orientation that matches the default device orientation? For example, a Nexus 9 works correctly with iCanHazPhone set to true, indicating that it's not phone vs. tablet but default-portrait vs. default-landscape.
Is there a better way of dealing with this?
Answer to both of your questions is: use the sensor orientation provided by the Camera/Camera2 APIs to adjust your preview image.
To calculate relative camera rotation to screen (which can be used to transform your preview) I use:
static int getRelativeImageOrientation(int displayRotation, int sensorOrientation,
boolean isFrontFacing, boolean compensateForMirroring) {
int result;
if (isFrontFacing) {
result = (sensorOrientation + displayRotation) % 360;
if (compensateForMirroring) {
result = (360 - result) % 360;
}
} else {
result = (sensorOrientation - displayRotation + 360) % 360;
}
return result;
}
where displayRotation is the current display rotation:
static int getDisplayRotation(Context context) {
WindowManager windowManager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
int rotation = windowManager.getDefaultDisplay().getRotation();
switch (rotation) {
case Surface.ROTATION_0:
return 0;
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
}
return 0;
}
sensorOrientation for the legacy Camera:
Camera.CameraInfo.orientation
and for the Camera2:
CameraCharacteristics#get(CameraCharacteristics.SENSOR_ORIENTATION)
You should pass false for compansateForMirror when calculating camera preview orientation and pass true when calculating legacy Camera JPG orientation.
I've tested this across a number of devices - it seems to work, although I cannot guarantee that this is bulletproof ;]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With