I am trying (again) to create camera preview logic that actually works properly, for all scenarios:
- any device: phone, tablet, toaster, whatever
- any camera: front-facing, rear-facing, side-facing, dog-facing, whatever
android.hardware.Cameraandandroid.hardware.camera2- portrait and landscape device orientations
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:
- orients the preview properly, taking device orientation into account
- fills the
TextureViewcompletely, at the cost of cropping where theTextureViewaspect ratio does not match the preview frame aspect ratio - does not stretch the image, so that a preview of a square item (e.g., a 3" square Post-It Note®) shows up square in the preview
In 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
iCanHazPhoneset totrue, indicating that it's not phone vs. tablet but default-portrait vs. default-landscape.Is there a better way of dealing with this?