Nexus 4 Camera preview aspect Ratio always requires 16x9 surfaceview? Why - android

Device Nexus 4
Android ver: 4.2.2
Hoping someone else has found this and can explain how to resolve it....
Nexus 4 supports the following preview sizes: -
W:1280 H:720 Ratio: 1.777777
W:800 H:480 Ratio: 1.6666666
W:768 H:432 Ratio: 1.7777778
W:720 H:480 Ratio: 1.5
W:640 H:480 Ratio: 1.3333334
W:576 H:432 Ratio: 1.3333334
W:480 H:320 Ratio: 1.5
W:384 H:288 Ratio: 1.3333334
W:352 H:288 Ratio: 1.2222222
W:320 H:240 Ratio: 1.3333334
W:240 H:160 Ratio: 1.5
W:176 H:144 Ratio: 1.2222222
myCamera.setPreviewSize() sets them, and when I call myCamera.getPreviewSize() I get the correct one that I set, BUT.... If I set my surface view to the same size as my camera preview then I get a stretched image. e.g.
setPreviewSize(640,480)
getPreviewSize -> I get 640,480
Surface view (640,480) -> Stretched image
Only if I set my Surface view to 16x9 (1.77777) do I get a perfect image.
Surface view (1280,720) -> Perfect image
This is the only device that I have this issue with. Please can someone advise if there is a camera setting I am missing for full screen mode or something that is stopping this from working.
In my long search I found 1 other post which relates also to this issue, but not an answer, just a bit more evidence of my problem
PictureCallback not called unless using supportedsizes[0]
Thanks

On the Nexus 4, there's an unfortunate issue where the preview aspect ratio and the still picture aspect ratio are tied together, even if you never take a picture. If they don't match, one of the two gets distorted (typically preview, since it is the lower resolution).
If you want to use a 4:3 preview, configure the still picture size to be 4:3 as well, before starting preview. For example, 640x480 preview with a full 8MP picture size (3264 x 2448) on N4 should cause no stretching for preview.
Use Camera.Parameters.setPictureSize to select the picture size; the list of available sizes can be read from Camera.Parameters.getSupportedPictureSizes.

This method calculate the best (for me) screen size for each device. But, I have the same problem like you when I tried this code in the Nexus 4. So, my solution is to have a special case in the end of this method which gets the width of the nexus 4 and calculates the best height for this device.
The last case could be used in all devices. You could delete the first part of the method.
private void setAspectResolutionCamera(Parameters camParams, int screen_width, int screen_height) {
boolean chosen_one_resolution = false;
//Init screen sizes
width_video = ConstantsCamera.VIDEO_ASPECT_WIDTH;
height_video = ConstantsCamera.VIDEO_ASPECT_HEIGHT;
float aspect_ratio = 1f;
int aspect_width = 6000, aspect_height = 6000;
List<Size> supported_sizes_list = camParams.getSupportedPreviewSizes();
for (int i = 0; i < supported_sizes_list.size(); i++) {
Size size = supported_sizes_list.get(i);
float aspect = (float) size.height / size.width;
if (ConstantsCamera.VIDEO_ASPECT_RATIO - aspect <= aspect_ratio && (aspect - ConstantsCamera.VIDEO_ASPECT_RATIO >= 0)) {
if (screen_width - size.height <= aspect_width && size.height - screen_width >= 0) {
if (screen_height - size.width < aspect_height) {
height_video = size.width;
width_video = size.height;
aspect_ratio = ConstantsCamera.VIDEO_ASPECT_RATIO - (float) size.height / size.width;
aspect_width = screen_width - size.height;
aspect_height = screen_height - size.width;
chosen_one_resolution = true;
}
}
}
}
//Special case
if (width_video != screen_width && !chosen_one_resolution) {
height_video = screen_width * height_video / width_video;
width_video = screen_width;
}
}

Try setting the size of your surfaceview based on the ratio of the camera parameters that you are using.

Related

Without additional calculation Camera or camera2 APIs return FOV angle values for 4:3 aspect ratio by default?

I can get horizontal and vertical angles of camera view using legacy Camera API or camera2 API (doesn't matter), same values.
But horizontal angle is correct only for 4:3 aspect ratio camera preview (for example 640x480 resolution).
If we start camera with Full HD preview size (for example 1920x1080 resolution, 16:9 aspect ratio) then such value is incorrect for this resolution.
Sometime ago I found a solution (https://stackoverflow.com/a/12118760/7767664) which does a little math to recalculate angles based on known camera preview aspect ratio and vertical angle values (original horizontal angle value is not needed) and we can get both horizontal and vertical angles for a specific aspect ratio.
So here's the full working code how to do it:
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraCharacteristics = cameraManager.getCameraCharacteristics("0") // hardcoded first back camera
val focalLength = cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)?.firstOrNull() ?: return
val sensorSize = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE) ?: return
val horizontalAngle = (2f * atan((sensorSize.width / (focalLength * 2f)).toDouble())) * 180.0 / Math.PI
val verticalAngle = (2f * atan((sensorSize.height / (focalLength * 2f)).toDouble())) * 180.0 / Math.PI
// using camera2 API we got the same data as with legacy Camera API (with it was easier):
// val parameters = camera.getParameters()
// val horizontalAngle = parameters.getHorizontalViewAngle()
// val verticalAngle = parameters.getVerticalViewAngle()
// but usually only vertical angle value is correct here
// horizontal angle value is correct only for 4:3 aspect ratio
// so we need do some math to get correct horizontal angle for example for 16:9 aspect ratio
// usually the widest one on smartphones
val streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
val resolutions = streamConfigurationMap.getOutputSizes(SurfaceTexture::class.java)
// get camera size with highest aspect value
val previewSize = resolutions.maxByOrNull { it.width.toFloat() / it.height.toFloat() }!!
val width = previewSize.width.toDouble()
val height = previewSize.height.toDouble()
val aspect = width / height // or hardcode it to "16 / 9" if you need to find out angles at specific ratio
val zoom = 100.0 // 100 == default 1.0 (no zoom), you can get zoom using camera and camera2, for camera2 you have to multiple it by 100
var verticalAngle2 = Math.toRadians(verticalAngle)
var horizontalAngle2 = 2.0 * atan(aspect * tan(verticalAngle2 / 2))
verticalAngle2 = Math.toDegrees(2.0 * atan(100.0 * tan(verticalAngle2 / 2.0) / zoom))
horizontalAngle2 = Math.toDegrees(2.0 * atan(100.0 * tan(horizontalAngle2 / 2.0) / zoom))
Timber.d("FOV: ${horizontalAngle}x${verticalAngle} (original 4:3) vs ${horizontalAngle2}x$verticalAngle2 (recalculated), width: $width, height: $height, aspect ratio: $aspect")
It seems ok, but I would like to know for sure how correct this method is
For my Samsung S10 it prints:
General rear (back) camera ("0" id):
FOV: 66.31770290433023x52.21398560685136 (original 4:3) vs
82.1243306411032x52.21398560685136 (recalculated), width: 1920.0, height: 1080.0, aspect ratio: 1.7777777777777777
Ultra-wide lens rear (back) camera ("2" id):
104.00253387238499x87.66172361901917 (original 4:3) vs 119.26472801785447x87.66172361901917 (recalculated), width: 1920.0, height: 1080.0, aspect ratio: 1.7777777777777777
For this device I found the next info on the internet:
Samsung Galaxy S10 - featuring a triple rear camera (telephoto 12mp
f/2.4, 45-degree angle of view, wide-angle 12mp dual pixel AF
f/1.5-2.4 OIS 77 degree angle of view, Ultra-wide 16mp FF, f/2.2 123
degrees angle of view)
So horizontal angle values from this info and my code (in case of 16:9 aspect ratio) don't match 100% but still are similar.
I can also start camera preview on this device and calculate FOV manually (without programming) https://stackoverflow.com/a/3261794/7767664
I will do it later and add info here (which values I got after manual calculation in case of 1920x1080 camera size preview, 16:9, no zoom 1.0, no video stabilization which can crop camera frames, infinity focus)

Full Screen Video Recording in Front Camera using camera2 api

I have been stuck in this issue for days.
I followed this Android's official camera-sample in Kotlin:
android's camera-sample
I raised an issue on github issue on 11 Feb 2020 but haven't received any feedback.
My problem is:
I used the sample as it is and only changed val cameraId = manager.cameraIdList[0] to val cameraId = manager.cameraIdList[1] for front camera.
NOTE: It does not happen in rear camera.
The front camera does not work and shows black bar on
devices tested:
Emulator: Pixel C API 29
Device: Galaxy Tab S2
Mode: Portrait
I wanted a full screen view, so when I don't set the aspect ratio of AutoTextureView in the commented line below, the video takes full screen but is now stretched.
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
//I only have portrait mode
} else {
//textureView.setAspectRatio(previewSize.height, previewSize.width)
}
Is there a way to set full screen mode without any stretching or in a correct aspect ratio?
I have been through following solutions in slack and none worked for me:
Camera 2 : Unable to record video in full screen?
Camera2 API Make Preview Fill Entire View
Android Camera2 API stretching the preview
After working for days. Camera2 full screen preview and image capture helped me solve the problem.
Setting onMeasure in AutoFitTextureView as:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = View.MeasureSpec.getSize(widthMeasureSpec)
val height = View.MeasureSpec.getSize(heightMeasureSpec)
if (ratioWidth == 0 || ratioHeight == 0) {
setMeasuredDimension(width, height)
} else {
if (width > ((height * ratioWidth) / ratioHeight)) {
setMeasuredDimension(width, (width * ratioHeight) / ratioWidth)
} else {
setMeasuredDimension((height * ratioWidth) / ratioHeight, height)
}
}
}
Above code makes the screen full size but had problem of preview not
being at the centre
So I translated as follows in configureTransform(viewWidth: Int, viewHeight: Int)
// adjust the x and y to centre the preview
val screenWidth = resources.displayMetrics.widthPixels
val xShift = (viewWidth - screenWidth)/2
val screenHeight = resources.displayMetrics.heightPixels
val yShift = (viewHeight - screenHeight)/2
matrix.setTranslate(-xShift.toFloat(), -yShift.toFloat())
textureView.setTransform(matrix)

Android - Google Sample HdrViewFinder in Portrait Mode

can somebody help me out with the following code. I want to transform the HdrViewFinder project from google samples (https://github.com/googlesamples/android-HdrViewfinder) so that it also works in portrait mode. But the project works only in landscape mode.
In portrait mode, the camera preview gets distorted.
So, here is the method I extracted from the HdrViewfinderActivity.java class which I think is responsible for setting the whole view into landscape:
/**
* Configure the surfaceview and RS processing.
*/
private void configureSurfaces() {
// Find a good size for output - largest 16:9 aspect ratio that's less than 720p
final int MAX_WIDTH = 1280;
final float TARGET_ASPECT = 16.f / 9.f;
final float ASPECT_TOLERANCE = 0.1f;
StreamConfigurationMap configs =
mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (configs == null) {
throw new RuntimeException("Cannot get available picture/preview sizes.");
}
Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class);
Size outputSize = outputSizes[0];
float outputAspect = (float) outputSize.getWidth() / outputSize.getHeight();
for (Size candidateSize : outputSizes) {
if (candidateSize.getWidth() > MAX_WIDTH) continue;
float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight();
boolean goodCandidateAspect =
Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
boolean goodOutputAspect =
Math.abs(outputAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
if ((goodCandidateAspect && !goodOutputAspect) ||
candidateSize.getWidth() > outputSize.getWidth()) {
outputSize = candidateSize;
outputAspect = candidateAspect;
}
}
Log.i(TAG, "Resolution chosen: " + outputSize);
// Configure processing
mProcessor = new ViewfinderProcessor(mRS, outputSize);
setupProcessor();
// Configure the output view - this will fire surfaceChanged
mPreviewView.setAspectRatio(outputAspect);
mPreviewView.getHolder().setFixedSize(outputSize.getWidth(), outputSize.getHeight());
}
How could I change this method so that it works also in portrait mode? What do I need to change ?
Setting only the screenOrientation attribute in the manifest file to portrait did not help.
I have found out the 16:9 is used for full landscape mode and that 9:16 used for full portrait mode. So, I changed MAX_WIDTH to 720 and TARGET_ASPECT to 9.f/16.f . But that also did not help.
Can somebody provide a solution ?
Here is a little sketch showing the problem/what happens when I just set the screenOrientation attribute in the manifest file to portrait mode:
I have an experimental fork that achieves this, by switching to TextureView from SurfaceView.
tested on Sony G8441 a.k.a Xperia XZ1 Compact with Android Pie

preview stretches in camera2 apis

Following are the screenshots when using texture view in camera2 apis.In full screen the preview stretches,but it works when using lower resolution(second image).
How to use this preview in full screen without stretching it.
Below answer assumes you are in portrait mode only.
Your question is
How to use the preview in full-screen without stretching it
Let's break it down to 2 things:
You want the preview to fill the screen
The preview cannot be distorted
First you need to know that this is logically impossible without crop, if your device's viewport has a different aspect ratio with any available resolution the camera provides.
So I would assume you accept cropping the preview.
Step 1: Get a list of available resolutions
StreamConfigurationMap map = mCameraCharacteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
throw new IllegalStateException("Failed to get configuration map: " + mCameraId);
}
Size[] sizes = map.getOutputSizes(SurfaceTexture.class);
Now you get a list of available resolutions (Sizes) of your device's camera.
Step 2: Find the best aspect ratio
The idea is to loop the sizes and see which one best fits. You probably need to write your own implementation of "best fits".
I am not going to provide any code here since what I have is quite different from your use case. But ideally, it should be something like this:
Size findBestSize (Size[] sizes) {
//Logic goes here
}
Step 3: Tell the Camera API that you want to use this size
//...
textureView.setBufferSize(bestSize.getWidth(), bestSize.getHeight());
Surface surface = textureView.getSurface();
try {
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
mSessionCallback, null);
} catch (final Exception e) {
//...
}
Step 4: Make your preview extends beyond your viewport
This is then nothing related to the Camera2 API. We "crop" the preview by letting the SurfaceView / TextureView extends beyond device's viewport.
First place your SurfaceView or TextureView in a RelativeLayout.
Use the below to extend it beyond the screen, after you get the aspect ratio from step 2.
Note that in this case you probably need to know this aspect ratio before you even start the camera.
//Suppose this value is obtained from Step 2.
//I simply test here by hardcoding a 3:4 aspect ratio, where my phone has a thinner aspect ratio.
float cameraAspectRatio = (float) 0.75;
//Preparation
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
int finalWidth = screenWidth;
int finalHeight = screenHeight;
int widthDifference = 0;
int heightDifference = 0;
float screenAspectRatio = (float) screenWidth / screenHeight;
//Determines whether we crop width or crop height
if (screenAspectRatio > cameraAspectRatio) { //Keep width crop height
finalHeight = (int) (screenWidth / cameraAspectRatio);
heightDifference = finalHeight - screenHeight;
} else { //Keep height crop width
finalWidth = (int) (screenHeight * cameraAspectRatio);
widthDifference = finalWidth - screenWidth;
}
//Apply the result to the Preview
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) cameraView.getLayoutParams();
lp.width = finalWidth;
lp.height = finalHeight;
//Below 2 lines are to center the preview, since cropping default occurs at the right and bottom
lp.leftMargin = - (widthDifference / 2);
lp.topMargin = - (heightDifference / 2);
cameraView.setLayoutParams(lp);
If you don't care about the result of Step 2, you can actually ignore Step 1 to Step 3 and simply use a library out there, as long as you can configure its aspect ratio. (It looks like this one is the best, but I haven't tried yet)
I have tested using my forked library. Without modifying any code of my library, I managed to make the preview fullscreen just by using Step 4:
Before using Step 4:
After using Step 4:
And the preview just after taking a photo will not distort as well, because the preview is also extending beyond your screen.
But the output image will include area that you cannot see in the preview, which makes perfect sense.
The code of Step 1 to Step 3 are generally referenced from Google's CameraView.
That's a common problem on some devices. I've noticed it mostly on samsung. You may use a trick with setting transformation on your TextureView to make it centerCrop like ImageView behaviour
I also faced similar situation, but this one line solved my problem
view_finder.preferredImplementationMode = PreviewView.ImplementationMode.TEXTURE_VIEW
in your xml:
<androidx.camera.view.PreviewView
android:id="#+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
For camera implementation using cameraX you can refer
https://github.com/android/camera-samples/tree/master/CameraXBasic
I figured out what was your poroblem. You were probably trying something like this:
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int j) {
cam.startPreview(surfaceTexture, i, j);
cam.takePicture();
}
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) { }
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { return false; }
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { }
});

How to solve distorted image issue in android custom camera for galaxy S4

I am working on custom camera application for android. The problem is that the camera capture and showing preview good in other devices (example Samsung Galaxy S3) , but It shows distorted
image on Galaxy s4, Can any one help me??
My code for Picturesize() method is as follows:
Camera.Size getBestPicturSize(int width, int height, Camera.Parameters parameters) {
Camera.Size result=null;
float dr = Float.MAX_VALUE;
float ratio = (float)width/(float)height;
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
float r = (float)size.width/(float)size.height;
if( Math.abs(r - ratio) < dr && size.width <= width && size.height <= height ) {
dr = Math.abs(r - ratio);
result = size;
}
}
return result;
}
I had the same problem, if you mean that photos were taken with aspect ratio 4:3 and saved with aspect ratio 16:9 (they were outstretched). My problem was, that since I chose one of supported PictureSizes, I didn't do the same with the PreviewSizes.
Supported PictureSizes for Samsung G S4 are only with aspect ratio 16:9, however default PreviewSize was set to 1440x1080, which is 4:3. When I set both sizes with the same aspect ratio, picture was taken with no distortion.
Hope it will help.

Categories

Resources