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?
iCanHazPhonevalue cited in my question. This presumes that my assumptions are correct, which is what the question is really about. But your symptoms definitely sound like what I was seeing in my tests. - CommonsWare