12

I'm trying to get the new Camera2 working with a simple SurfaceView and I'm having some problems with the live preview. On some devices the image is stretched out of proportions while looking fine on others.

I've setup a SurfaceView that I programatically adjust to fit the size of the preview stream size.

On Nexus 5 this looks fine but one Samsung devices its way off. Also the Samsung devices have a black border on the right part of the preview.

Is it really not possible to work with the SurfaceView or is this the time to switch to TextureView ?

fadden
  • 51,356
  • 5
  • 116
  • 166
slott
  • 3,266
  • 1
  • 35
  • 30
  • Sounds like the preview isn't the size you think it is. You can find a similar question, with some tips for using `dumpsys` to examine the problem, in this post: http://stackoverflow.com/questions/30714469/android-supported-camera-preview-size-is-wrong-aspect-ratio – fadden Jul 14 '15 at 15:37
  • have you found a way to use SurfaceView with Camera2 and without the distorted preview? – vkislicins Dec 14 '15 at 14:49
  • 1
    I moved to TextureView - this is much more flexible also. – slott Dec 15 '15 at 07:22

3 Answers3

3

Yes, it is certainly possible. Note that the SurfaceView and its associated Surface are two different things, and each can/must be assigned a size.

The Surface is the actual memory buffer which will hold the output of the camera, and thus setting its size dictates the size of the actual image you will get from each frame. For each format available from the camera, there is a small set of possible (exact) sizes you can make this buffer.

The SurfaceView is what does the displaying of this image when it is available, and can basically be any size in your layout. It will stretch its underlying associated image data to fit whatever its layout size is, but note this display size is different from the data's size- Android will resize the image data for display automatically. This is what is probably causing your stretching.

For example, you can make a SurfaceView-based autofit View similar to the camera2basic's AutoFitTextureView as follows (this is what I use):

import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceView;

public class AutoFitSurfaceView extends SurfaceView {

    private int mRatioWidth = 0;
    private int mRatioHeight = 0;

    public AutoFitSurfaceView(Context context) {
        this(context, null);
    }

    public AutoFitSurfaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoFitSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
     * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
     * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
     *
     * @param width  Relative horizontal size
     * @param height Relative vertical size
     */
    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }
}
rcsumner
  • 1,623
  • 13
  • 12
  • 4
    This is _exactly_ what I have - based on the camera2basic code from Google. Problem is that the actual image displayed on the surface is distorted on some devices like Samsung S4 and Nexus 4 - but looks fine on Nexus 5. I tried a lot of things - even hard coded surface size to match that of the supported preview sizes - still end up with a distorted view. With TextureView its all fine and dandy - except for my pride :( – slott Jul 15 '15 at 20:48
  • What is the aspect ratio of the images you are setting up the Surface for on the misbehaving devices? I am only guessing without seeing the distorted images you are talking about, but is the samsung return 16x9 content in a 4x3 image buffer with lots of unused black bar area? Maybe you should try setting the Surface for one of its 16x9 output sizes and the SurfaceView the same way and see what happens? – rcsumner Jul 15 '15 at 20:55
  • I used a 1280 x 720 view size as well as camera preview size. – slott Jul 15 '15 at 21:41
  • If you save such an image instead with an ImageReader with the same dimensions, does it look correct? – rcsumner Jul 15 '15 at 21:44
  • I need to go back and try once more - but I'm pretty sure they were looking just fine when I pulled them off the device and viewed them. – slott Jul 15 '15 at 21:47
  • 1
    slott, you may know this already. If everything in your image is distorted where your images are skinny (squeezed horizontally), you're probably trying to squeeze a 16:9 image into a 4:3 viewport. 1280X720 does indeed have the aspect ratio of 16:9. When you go portrait mode you'll want to use a 4:3 aspect ratio for your measuredDimensioned settings else your camera image preview will be tiny. Also, don't samsung's have the camera sensor and device default orientation difference/offset issue? If so, you have to take the offset, a hardware thing, into account as well. (all pretty messy stuff). – BoiseBaked Jul 29 '16 at 17:11
1

In the docs for createCaptureSession it says

For drawing to a SurfaceView: Once the SurfaceView's Surface is created, set the size of the Surface with setFixedSize(int, int) to be one of the sizes returned by getOutputSizes(SurfaceHolder.class)

paleozogt
  • 6,393
  • 11
  • 51
  • 94
  • No. This won't work. What is required is to make a "custom" sized view. A view that is not forced to be bounded by the hierarchy that the view instance is inflated and embedded in. The rcsumner post seems to be the way to go. I'm trying it out now. Will let you know. – BoiseBaked Jul 29 '16 at 15:56
  • 3
    I tried the @rcsumner post and it didn't work for me. I don't understand why it's so difficult to avoid image stretch (aspect ratio distortion) when working with the camera2 APIs and video. The Surface that will display the image is always created first. Why can't the image "simply" be scaled to the Surface, with the use of black borders if need be, without distortion? There has got to be a straight forward way to do this. – BoiseBaked Aug 31 '16 at 14:27
-1

I've never worked with Camera2 (but very interesting in the new technology) and all I can suggest you is to follow the samples. And if you check it carefully, the sample uses a Custom TextureView, you should probalby try to copy it to your project:

http://developer.android.com/samples/Camera2Basic/src/com.example.android.camera2basic/AutoFitTextureView.html

/**
 * A {@link TextureView} that can be adjusted to a specified aspect ratio.
 */
public class AutoFitTextureView extends TextureView {

also considering that different between SurfaceView and TextureView (theoretically) is just that you can use as a normal view in layout and the other "punches a whole" through the view hierarchy, and that Camera2 is only available in API21+... there should be no harm in migrating to TextureView

Budius
  • 39,391
  • 16
  • 102
  • 144
  • I did start there and migrated that to a surface view - and that's when shit started to hit the fan :( – slott Jul 14 '15 at 15:30
  • well. Considering TextureView is API 14 and Camera2 is API 21, there's no reason really. Just switch it back, no ? – Budius Jul 14 '15 at 15:34
  • Working on a cross platform lib - so minimum is 11 so it would be nice to get this working... But if not min will be 14... – slott Jul 14 '15 at 15:35
  • API11 won't be able to handle `Camera2` regardless. You can keep your minimum 11, as long you probably route through your layouts and method calls depending on `Build.VERSION.SDK_INT` – Budius Jul 14 '15 at 15:37
  • Way ahead of you there. Have a nice working support camera class with an interface to allow interaction with both new and old camera api. What Google should have made a long time ago... – slott Jul 14 '15 at 15:38
  • 1
    Something like this https://github.com/commonsguy/cwac-camera but also including camera2. Sounds cool. But my statement still true. You can have separate `layout/camera` and `layout-v21/camera` to proper handle new and old versions. – Budius Jul 14 '15 at 15:40
  • 1
    Yes it's essentially a replacement for that with a few more things such as preview view embedded – slott Jul 14 '15 at 15:42