Android center CameraPreview on SurfaceView - android

I am working on the face detection application for Android (my device is Nexus S with Android 4.1.2). My SurfaceView size is automatically set to 800x480 but my maximal camera resolution is 720x480. I have tried to change the size of SurfaceView in its onLayout() method which worked but then I was missing 80px in the preview. Is it possible to stretch or at least center the CameraPreview?
Thanks

CameraPreview size can be changed using Camera.Parameters.
But I recommend that put the surfaceview on the center of screen.
Here's the code.
I didn't execute the code, but it may works.
// Center the child SurfaceView within the parent.
final int width = r - l;
final int height = b - t;
if (width * previewHeight > height * previewWidth) {
final int surfaceViewWidth = previewWidth * height / previewHeight;
surfaceView.layout((int)((width - surfaceViewWidth)*0.5), 0, (int)((width + surfaceViewWidth)*0.5), height);
} else {
final int surfaceViewHeight = previewHeight * width / previewWidth;
surfaceView.layout(0, (int)((height - surfaceViewHeight)*0.5), width, (int)((height + surfaceViewHeight)*0.5));
}

If anybody else runs into this issue - the way to center the camera preview is with a FrameLayout:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/surfacecontainer">
<android.view.SurfaceView
android:id="#+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
</FrameLayout>
More details can be found here.

Related

Calculating view height using screen width and aspect ratio not working on 1440p screen

I am programatically changing the height of a ConstraintLayout and its contained ImageView in a RecyclerView's onBindViewHolder() method:
fun bind(model: CommunityOffer) {
itemView.communityImageCl.requestLayout()
itemView.communityImageIv.requestLayout()
val aspectRatioSplit = model.mediaAspectRatio.split(":")
val widthRatio = Integer.parseInt(aspectRatioSplit[0])
val heightRatio = Integer.parseInt(aspectRatioSplit[1])
if (model.mediaAspectRatio =="9:16" || model.mediaAspectRatio == "16:9"){
communityImageIv.scaleType = ImageView.ScaleType.FIT_XY
communityImageCl.layoutParams.height = (communityImageCl.width / widthRatio) * heightRatio
communityImageIv.layoutParams.height = (communityImageIv.width / widthRatio) * heightRatio
} else if (widthRatio == heightRatio){
communityImageIv.scaleType = ImageView.ScaleType.FIT_XY
communityImageCl.layoutParams.height = communityImageCl.width
communityImageIv.layoutParams.height = communityImageIv.width
} else{
communityImageIv.scaleType = ImageView.ScaleType.FIT_XY
communityImageCl.layoutParams.height = (communityImageCl.width / widthRatio) * heightRatio
communityImageIv.layoutParams.height = (communityImageIv.width / widthRatio) * heightRatio
}
}
View layout:
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/communityImageCv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="16dp"
app:cardCornerRadius="10dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/communityImageCl"
android:layout_width="match_parent"
android:layout_height="0dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/communityImageIv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#color/transparent"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#color/alpha_grey" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
When the aspect ratio of an image, sent as metadata as a result of an API call, is conventional (16:9, 9:16, or 1:1), the image appears appropriately. However, a view whose aspect ratio is simply its dimensions (847:1280), gets scrunched into a square on 1440p phones, distorting the image
Is there something wrong with my formula for calculating height? The debugger lists the height of the image view and constraint layout as zero, I assume because that's how they are set in xml. The width of the view must take up the whole screen and the aspect ration must be kept, even if it stretches the photo.
First of all, I checked your screenshot, and can tell, that this distorted proportion is not exactly 1:1, which you can see here (I drew a red square over it):
I think it might be that as you divide the width value by a big integer, you get very small integer, then you multiply it (as int it would be missing precision) so the result might be unexpected.
Simply cast it to float as you calculate it, the cast the result back to int, and it should work.
... ((communityImageCl.width.toFloat() / widthRatio.toFloat()) * heightRatio).toInt()
P.S. I have never used Kotlin, so please someone edit above code if necessary.

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

View doesn't fill the whole screen

I'm having everything the same as in this sample in https://github.com/googlesamples/android-vision/tree/master/visionSamples/multi-tracker except my activity layout is this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/topLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
android:weightSum="100"
android:orientation="horizontal">
<be.citylife.communitypurchaseapp.view.camera.CameraSourcePreview
android:id="#+id/preview"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="60">
<be.citylife.communitypurchaseapp.view.camera.GraphicOverlay
android:id="#+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</be.citylife.communitypurchaseapp.view.camera.CameraSourcePreview>
<FrameLayout
android:id="#+id/sideContainer"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="40"/>
</LinearLayout>
My tablet is in landscape and I want that the cameraPreviewSource is always left and fills the whole screen in the height and then right off it I'm having a fragment that fills the rest.
This layout works except my previewsource doesn't fill the whole height. It has a black banner on it. Even my width is actually smaller than I want you can see this on the screenshot:
http://i61.tinypic.com/vctmw0.png
I played with the CameraSourcePreview with the width and height in the onLayout function but it doesn't help. I know on the preview that it does fill the screen to the bottom of the screen but on the tablet it isn't.
lp.
Anyone an idea how to solve this?
EDIT:
I think it has something to do with this:
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int width = 320;
int height = 240;
if (mCameraSource != null) {
Size size = mCameraSource.getPreviewSize();
if (size != null) {
width = size.getWidth();
height = size.getHeight();
}
}
// Swap width and height sizes when in portrait, since it will be rotated 90 degrees
if (isPortraitMode()) {
int tmp = width;
width = height;
height = tmp;
}
final int layoutWidth = right - left;
final int layoutHeight = bottom - top;
// Computes height and width for potentially doing fit width.
int childWidth = layoutWidth;
int childHeight = (int)(((float) layoutWidth / (float) width) * height);
// If height is too tall using fit width, does fit height instead.
if (childHeight > layoutHeight) {
childHeight = layoutHeight;
childWidth = (int)(((float) layoutHeight / (float) height) * width);
}
for (int i = 0; i < getChildCount(); ++i) {
getChildAt(i).layout(0, 0, childWidth, childHeight);
}
try {
startIfReady();
} catch (IOException e) {
Log.e(TAG, "Could not start camera source.", e);
}
}
That's the onlayout method off the CameraSourcePreview.
Comment or remove below lines from CameraSourcePreview and it should be fine. I was having same issue like you and it is solved now.
if (childHeight > layoutHeight) {
childHeight = layoutHeight;
childWidth = (int)(((float) layoutHeight / (float) height) * width);
}
that should put it into fullscreen mode :D there are a bunch of other modes you can select from. if this doesnt work, remove the automatically generated
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
/** gets called when a Menu.onClick happens
*
* #param item the ID of the clicked Item
*/
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if (id == R.id.action_settings) {
//TODO
}
return super.onOptionsItemSelected(item);
}
}
Likely has something to do with the aspect ratio of the camera and how it draws into its container.
If the camera preview you're using maintains aspect ratio and fits the preview to the container, then you will definitely get black bars. This is because most cameras' sensors produce images that are designed to fit within a space relative to 1920x1080px (or a 16:9 aspect ratio box).
What you need, is for the extra space on the sides to be hidden and for the preview to fill based on height. That is, if you don't mind some of your image to be hidden from the user when previewing. It might not be possible to do this directly with the view you're using, but it should be relatively simple if you place your object into another layout container.
Hope this helps!

Stretching camera preview to full screen in android

I am using this camera preview application for my Android app.
I want the camera preview over the full screen.Hence I used the example from Android APIs to try setting the preview to full screen. This is how I am trying to do it:
if (!cameraConfigured) {
Camera.Parameters parameters=camera.getParameters();
Camera.Size size = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
if (size != null) {
parameters.setPreviewSize(size.width, size.height);
camera.setParameters(parameters);
cameraConfigured=true;
}
I am using relative layout as my layout. My layout settings are follows:
<android.view.SurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
I am still not able to preview my camera over the entire screen. I would like to know how to preview over the entire screen.
I found the problem. I added the following setting to Android Manifest file
<supports-screens android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true" android:xlargeScreens="true"/>
I am able to view the camera over full screen.
Im assuming your code is within a class that extends SurfaceView and that you will place the surfaceView inside a FrameLayout that is as large as the display.
What you are not doing in your code is setting your SurfaceView to be the same size as the display which in the following code block is done by getting the layout, and setting the width and height.
I use the following code to stretch to the dimensions of the screen:
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
...
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
...
final DisplayMetrics dm = this.getResources().getDisplayMetrics();
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(cameraSize.width, cameraSize.height);
mCamera.setParameters(parameters);
//Here you get the SurfaceView layout and subsequently set its width and height
FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams) this.getLayoutParams();
frameParams.width = LayoutParams.MATCH_PARENT;// dm.widthPixels should also work
frameParams.height = LayoutParams.MATCH_PARENT;//dm.heightPixels should also work
this.setLayoutParams(frameParams);
//And we should have things set now...
....
}
...
}
I hope this helps. The key parts to integrate are between those inner comments
Get dimensions of the screen
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels;
use this width and height to get preview size.
First, get the display width and height:
int dw = currentDisplay.getWidth(); // dw == display width
int dh = currentDisplay.getHeight(); // dh == display height
Then loop through the supported preview sizes, and pick the largest one where, if
(size.width <= dw && size.height <= dh). When you hit one where this if statement
fails, use the previous values (you could set something like prevWidth and prevHeight
as a quick/easy way to step back one ... just remember to set each of those AFTER
you test them; after you hit a preview size that's larger than screen size, just
break out of the loop.
Later,
--jim

Android: Mediaplayer: How to use SurfaceView or mediaplayer to play video in correct size

I am playing local video file using MediaPlayer and SurfaceView. SurfaceView is the only control in activity, while my video files are QVGA or other. Problem is that video is getting stretched, How can i play video in its original size e.g. qvga with remaining area black.
From iteration,
When i am forcefully sets layout_height/width of Surfaceview in XML, video displayed fine.
surface_holder.setFixedSize(w,h) has no effect, neither mp.setdisplay().
Please guide in this.
UPDATE
XML flie
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/home_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<SurfaceView
android:id="#+id/surface"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip" />
</framelayout>
MediaPlayer usage is as per following link
http://davanum.wordpress.com/2007/12/29/android-videomusic-player-sample-from-local-disk-as-well-as-remote-urls/
Thanks in advance.
Setting your SurfaceView layout to wrap_content will not size a video to play at the proper aspect ratio.
A SurfaceView is an optimized drawing surface
A video is drawn to a SurfaceView, not contained within it
wrap_content is synonymous with fill_parent for a SurfaceView.
What you want to do is get the dimensions of your video from the MediaPlayer object. You can then set the aspect ratio of the SurfaceView to match the video.
Some Basic initialization
public class YourMovieActivity extends Activity implements SurfaceHolder.Callback {
private MediaPlayer mp = null;
//...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mp = new MediaPlayer();
mSurfaceView = (SurfaceView) findViewById(R.id.surface);
//...
}
}
Then the good stuff. I have omitted error checking here to reduce code, MediaPlayer calls should be wrapped in a try{}.
#Override
public void surfaceCreated(SurfaceHolder holder) {
mp.setDataSource("/sdcard/someVideo.mp4");
mp.prepare();
//Get the dimensions of the video
int videoWidth = mp.getVideoWidth();
int videoHeight = mp.getVideoHeight();
//Get the width of the screen
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
//Get the SurfaceView layout parameters
android.view.ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams();
//Set the width of the SurfaceView to the width of the screen
lp.width = screenWidth;
//Set the height of the SurfaceView to match the aspect ratio of the video
//be sure to cast these as floats otherwise the calculation will likely be 0
lp.height = (int) (((float)videoHeight / (float)videoWidth) * (float)screenWidth);
//Commit the layout parameters
mSurfaceView.setLayoutParams(lp);
//Start video
mp.start();
}
Note that this code makes some assumptions about the dimensions of your video. As-is, it maximizes the width and assumes that the height is not greater than the height of the screen.
You may want to fit height instead of width, also you could check the dimension calculation and ensure that it is not greater than the screen or screen - other_layout_elements.
here is the code I currently use in a project:
private MediaPlayer mMediaPlayer;
private SurfaceView mSurfaceView;
private SurfaceHolder holder;
private int mPos = 0;
...
int width = mSurfaceView.getWidth();
int height = mSurfaceView.getHeight();
float boxWidth = width;
float boxHeight = height;
float videoWidth = mMediaPlayer.getVideoWidth();
float videoHeight = mMediaPlayer.getVideoHeight();
Log.i(TAG, String.format("startVideoPlayback # %d - video %dx%d - box %dx%d", mPos, (int) videoWidth, (int) videoHeight, width, height));
float wr = boxWidth / videoWidth;
float hr = boxHeight / videoHeight;
float ar = videoWidth / videoHeight;
if (wr > hr)
width = (int) (boxHeight * ar);
else
height = (int) (boxWidth / ar);
Log.i(TAG, String.format("Scaled to %dx%d", width, height));
holder.setFixedSize(width, height);
mMediaPlayer.seekTo(mPos);
mMediaPlayer.start();
the layout I'm using (you can just ignore the progress bar)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<ProgressBar
android:id="#+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<SurfaceView
android:id="#+id/surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" >
</SurfaceView>
On your SurfaceView in XML are you using wrap_content ? This should fix your problem if not. You may need to paste a little more code for further investigation if that doesn't fix your problem.
Change your surface view width to wrap_content as well.

Categories

Resources