Android camera preview stretched using Grafika CameraCapture code
Asked Answered
R

1

15

I'm looking for help with an issue I'm facing using Grafika's CameraCaptureActivity code. I want to build an app that can record camera and display a preview, so this sample and code looked like exactly what I wanted and so far it's been great, this issue appart.

The issue I have is that when the camera preview size doesn't match the exact size of the GLSurfaceView I use to display the preview it shows a stretched preview.

Here's how it looks:

Preview stretched

As you can see it's stretched horizontally since the note sheet is a perfect square.

For this precise exemple on a Samsung Galaxy S4, the camera preview size is 1280x720 (extracted from available preview sizes using Camera2 API), and the GLSurfaceView is 1920x1080 to cover the entire screen.

When does it appear?

The issue appears when the SurfaceView is upscaled compared to the Camera output, even if I always make sure the ratio remains correct (1920x1080 = 1,5 * 1280x720).

What code are you using?

I use Grafika's code, including:

Some log?

Here's what SurfaceFlinger dump looks like:

Hardware Composer state (version 01030000):
  mDebugForceFakeVSync=0
  Display[0] configurations (* current):
    * 0: 1080x1920, xdpi=442.450989, ydpi=439.351013, secure=1 refresh=16666667
  numHwLayers=3, flags=00000000
    type   |  handle  | hint | flag | tr | blnd |  format     |     source crop(l,t,r,b)       |           frame        |      dirtyRect         |  name 
------------+----------+----------+----------+----+-------+----------+-----------------------------------+---------------------------+-------------------
       HWC | b3b12ba0 | 0002 | 0000 | 00 | 0100 | RGB_888     |    0.0,    0.0, 1080.0, 1920.0 |    0,    0, 1080, 1920 | [    0,    0, 1080, 1920] | SurfaceView
       HWC | b3d12740 | 0002 | 0000 | 00 | 0105 | RGBA_8888   |    0.0,    0.0, 1065.0, 1905.0 |    0,    0, 1065, 1905 | [    0,    0, 1065, 1905] | com.mybundle.myapp/com.mybundle.myapp.activity.MyActivity
 FB TARGET | b6a55c40 | 0000 | 0000 | 00 | 0105 | RGBA_8888   |    0.0,    0.0, 1080.0, 1920.0 |    0,    0, 1080, 1920 | [    0,    0,    0,    0] | HWC_FRAMEBUFFER_TARGET

Layer 0xb4094000 (SurfaceView)
  Region transparentRegion (this=0xb4094160, count=1)
    [  0,   0,   0,   0]
  Region visibleRegion (this=0xb4094008, count=1)
    [  0,   0, 1920, 1080]
      layerStack=   0, z=    21015, pos=(0,0), size=(1920,1080), crop=(   0,   0,1920,1080), isOpaque=1, invalidate=0, alpha=0xff, flags=0x00000002, tr=[1.00, 0.00][0.00, 1.00]
      client=0xb66190c0
      format= 4, activeBuffer=[1080x1920:1536,  3], queued-frames=0, mRefreshPending=0
            mTexName=116 mCurrentTexture=0
            mCurrentCrop=[0,0,0,0] mCurrentTransform=0x7
            mAbandoned=0
            -BufferQueue mMaxAcquiredBufferCount=1, mDequeueBufferCannotBlock=0, default-size=[1920x1080], default-format=4, transform-hint=04, FIFO(0)={}
            >[00:0xb1c18500] state=ACQUIRED, 0xb3b12ba0 [1080x1920:1536,  3]

Camera preview size code?

StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

// Retrieve display size
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point displaySize = new Point();
display.getSize(displaySize);

// Choose the sizes for camera preview
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                    displaySize.x, displaySize.y, captureWidth, captureHeight);

private static Size chooseOptimalSize(Size[] choices, int screenWidth, int screenHeight, int captureWidth, int captureHeight)
    {
        // Collect the supported resolutions that are at least as big as the screen && wanted capture size
        List<Size> bigEnough = new ArrayList<>();
        // Collect the supported resolutions that are at least as big as the wanted capture size but < to screensize
        List<Size> smallerButEnough = new ArrayList<>();

        for (Size option : choices) {
            if (option.getHeight() == option.getWidth() * captureHeight / captureWidth) {
                if( option.getWidth() >= screenWidth && option.getHeight() >= screenHeight )
                {
                    bigEnough.add(option);
                }
                else
                {
                    smallerButEnough.add(option);
                }
            }
        }

        // Pick the smallest of those, assuming we found any
        if (bigEnough.size() > 0)
        {
            return Collections.min(bigEnough, new CompareSizesByArea());
        }
        // Pick the biggest of those, assuming we found any
        else if( smallerButEnough.size() > 0 )
        {
            return Collections.max(smallerButEnough, new CompareSizesByArea());
        }
        else
        {
            Log.e(TAG, "Couldn't find any suitable preview size");
            return choices[0];
        }
    }

What have you tried?

I first thought it was a Camera2 issue, so I tried to use only the old Camera API, but result is the same.

Then I thought it was a ratio issue, but as you can see the ratio here is ok horizontally and vertically.

My bet is that there's an OpenGL transformation to apply when the Surface is resized but I'm a pure noob in OpenGL so I haven't been able to find anything that works. I try added those lines but it doesn't help:

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    Log.d(TAG, "onSurfaceChanged " + width + "x" + height);

    gl.glViewport(0, 0, width, height);
    gl.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);
}

Another example, right from Grafika:

Here's an other example of the issue, right from the Grafika app.

Phone: Galaxy S3 Neo (GT-I9301L) - Android 4.4.2

The phone has a 1280x720 screen, camera opened with the 640x480 resolution, displayed in a 573x430. Here's how it looks:

Stretching on GS3 Neo

SurfaceFlinger logs:

Hardware Composer state (version  1030000):
  mDebugForceFakeVSync=0
  Display[0] : 720x1280, xdpi=304.799011, ydpi=306.716003, refresh=16666667
  numHwLayers=4, flags=00000000
    type    |  handle  |   hints  |   flags  | tr | blend |  format  |          source crop            |           frame           name 
------------+----------+----------+----------+----+-------+----------+---------------------------------+--------------------------------
        HWC | b84a3d18 | 00000002 | 00000000 | 00 | 00100 | 00000002 | [    0.0,    0.0,  430.0,  573.0] | [   32,  353,  462,  926] SurfaceView
        HWC | b841b080 | 00000002 | 00000000 | 00 | 00105 | 00000001 | [    0.0,    0.0,  670.0, 1280.0] | [    0,    0,  670, 1280] com.android.grafika/com.android.grafika.CameraCaptureActivity
        HWC | b8423250 | 00000002 | 00000000 | 00 | 00105 | 00000001 | [    0.0,    0.0,   50.0, 1280.0] | [  670,    0,  720, 1280] StatusBar
  FB TARGET | b8412d50 | 00000000 | 00000000 | 00 | 00105 | 00000001 | [    0.0,    0.0,  720.0, 1280.0] | [    0,    0,  720, 1280] HWC_FRAMEBUFFER_TARGET


+ Layer 0xb8416080 (SurfaceView) id=112
  Region transparentRegion (this=0xb84169d0, count=1)
    [  0,   0,   0,   0]
  Region visibleRegion (this=0xb8416088, count=1)
    [353, 258, 926, 688]
      layerStack=   0, z=    21015, pos=(353,258), size=( 573, 430), crop=(   0,   0, 573, 430), isOpaque=1, invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
      client=0xb841d7c8
      format= 4, activeBuffer=[ 430x 573: 448,  2], queued-frames=0, mRefreshPending=0
            mTexName=95 mCurrentTexture=1
            mCurrentCrop=[0,0,0,0] mCurrentTransform=0x7
            mAbandoned=0
            -BufferQueue mMaxAcquiredBufferCount=1, mDequeueBufferCannotBlock=0, default-size=[573x430], default-format=4, transform-hint=04, FIFO(0)={}
             [00:0xb84439a8] state=FREE    , 0xb849a528 [ 430x 573: 448,  2]
            >[01:0xb8421128] state=ACQUIRED, 0xb84a3d18 [ 430x 573: 448,  2]
             [02:0xb84238e8] state=FREE    , 0xb84432f8 [ 430x 573: 448,  2]

Grafika's app log:

04-18 18:06:49.954 14383-14383/com.android.grafika D/Grafika: onCreate complete: com.android.grafika.CameraCaptureActivity@42adc288
04-18 18:06:49.954 14383-14383/com.android.grafika D/Grafika: onResume -- acquiring camera
04-18 18:06:50.064 14383-14383/com.android.grafika D/Grafika: Camera preferred preview size for video is 1920x1080
04-18 18:06:50.064 14383-14383/com.android.grafika D/Grafika-AFL: Setting aspect ratio to 1.3333333333333333 (was -1.0)
04-18 18:06:50.064 14383-14383/com.android.grafika D/Grafika: onResume complete: com.android.grafika.CameraCaptureActivity@42adc288
04-18 18:06:50.064 14383-32312/com.android.grafika D/Grafika: setCameraPreviewSize
04-18 18:06:50.094 14383-14383/com.android.grafika D/Grafika-AFL: onMeasure target=1.3333333333333333 width=[MeasureSpec: EXACTLY 1216] height=[MeasureSpec: EXACTLY 526]
04-18 18:06:50.094 14383-14383/com.android.grafika D/Grafika-AFL: new size=701x526 + padding 0x0
04-18 18:06:50.094 14383-14383/com.android.grafika D/Grafika-AFL: onMeasure target=1.3333333333333333 width=[MeasureSpec: EXACTLY 1216] height=[MeasureSpec: EXACTLY 430]
04-18 18:06:50.094 14383-14383/com.android.grafika D/Grafika-AFL: new size=573x430 + padding 0x0
04-18 18:06:50.124 14383-32312/com.android.grafika D/Grafika: onSurfaceCreated
04-18 18:06:50.134 14383-32312/com.android.grafika D/Grafika: Created program 3 (TEXTURE_EXT)
04-18 18:06:50.144 14383-32312/com.android.grafika D/Grafika: onSurfaceChanged 573x430
04-18 18:06:50.144 14383-32312/com.android.grafika D/Grafika: Updating filter to 0
04-18 18:06:50.154 14383-14383/com.android.grafika D/Grafika: onItemSelected: 0
04-18 18:06:50.154 14383-14383/com.android.grafika D/Grafika: CameraHandler [Handler (com.android.grafika.CameraCaptureActivity$CameraHandler) {42aeec70}]: what=0
04-18 18:06:50.514 14383-14383/com.android.grafika D/Grafika-AFL: onMeasure target=1.3333333333333333 width=[MeasureSpec: EXACTLY 1216] height=[MeasureSpec: EXACTLY 526]
04-18 18:06:50.514 14383-14383/com.android.grafika D/Grafika-AFL: new size=701x526 + padding 0x0
04-18 18:06:50.514 14383-14383/com.android.grafika D/Grafika-AFL: onMeasure target=1.3333333333333333 width=[MeasureSpec: EXACTLY 1216] height=[MeasureSpec: EXACTLY 430]
04-18 18:06:50.514 14383-14383/com.android.grafika D/Grafika-AFL: new size=573x430 + padding 0x0
04-18 18:06:50.544 14383-14383/com.android.grafika D/AbsListView: onVisibilityChanged() is called, visibility : 4
04-18 18:06:50.544 14383-14383/com.android.grafika D/AbsListView: unregisterIRListener() is called 

So...

If one of you guys have an idea of how to make it work, I would love to hear from him.

Thanks!

Reconstructionist answered 13/4, 2016 at 10:23 Comment(30)
I'm guessing something isn't using the resolution you think it's using. One approach to figuring this out is to use adb shell dumpsys SurfaceFlinger to dump the HardwareComposer layers; this will show the transformation that's being applied to the SurfaceView Surface. source.android.com/devices/graphics/… has an example (using a video player from Grafika) of the section you should look at. GLES transforms shouldn't be relevant since you're just drawing the entire frame with a viewport-sized rect. Do you see any distortion in Grafika itself?Ching
@Ching I added the log of adb shell dumpsys SurfaceFlinger on my answer: #36617782. Do you see anything weird?Reconstructionist
The HWC output shows everything at 1080x1920, so there's nothing wrong with your SurfaceView configuration. That leaves the Camera configuration. Can you show the code you're using to configure the preview size?Ching
@Ching post is edited with camera2 code for preview size. Thanks for your helpReconstructionist
What values do you get from mPreviewSize.getWidth() / mPreviewSize.getHeight()?Ching
I get width: 1280, height: 720.Reconstructionist
@Ching I'm also able to reproduce it in Grafika, simply by removing the rest of the Activity layout and putting the surfaceview in full screen (keeping the aspect layout), if that helps.Reconstructionist
So you've got a landscape-oriented preview image at 16:9, being blitted to a landscape-oriented display that is also 16:9. The image you showed would make more sense if you were getting a 9:16 720x1280 portrait image and stretching it to landscape. If you rotate the image 90 degrees while rendering to the SurfaceView (or flip the parameters to AspectFrameLayout to letter-box the View) does the note pad look square?Ching
It's either getting rotated and stretched or clipped and then stretched. Does the field of view on the stretched output match the field of view on non-stretched output? (Flip between modified and unmodified Grafika camera activities without moving the device and see if the captured image is the same.)Ching
@Ching I'll try to run the tests you mentioned on Monday and come back to you with screenshots and data. Thanks again for helping me, I really appreciate it.Reconstructionist
You can also check on what the camera service thinks you asked it for with 'adb shell dumpsys media.camera' while your app is running; if you scroll down to the open device information, it'll have what output streams are currently configured and their resolutions.Kropotkin
@Ching Inverting parameters for the AspectFrameLayout doesn't make the pad look square. And after re-running some tests with Grafika, I'm able to reproduce it without any modification. I've edited my question adding an exemple with a Galaxy S3 Neo, I opened the camera with a resolution of 640x480: openCamera(640, 480);, all the rest is unchanged. Does this other example tells us something? @EddyTalvala I'm not getting anything on the Galaxy S3 running 4.4.1: Client[0] (0xb71a8178) PID: 14383 is the only non-static log.Reconstructionist
@Reconstructionist A 640x480 (4:3) preview will look stretched if displayed at 1920x1080 (16:9), but if you're displaying it at 573x430 then it should match. Something somewhere isn't configured right... you're displaying at full screen size, so either the camera preview isn't coming in at 16:9, or somehow only the center part of the preview is being displayed. Grafika's FullFrameRect rendering is pretty simple, so if you haven't changed that around the thing to look at is the camera config. (I tried the dumpsys on my 4.4.x device and also didn't get anything interesting out... must be a newer feature.)Ching
You're logging the viewport size in onSurfaceChanged()... does that match expectations? When you try to run Grafika at 640x480, do you see an "Unable to set preview size" complaint in the log? (I'm wondering if your camera doesn't support that resolution, and Grafika is stupidly not passing the error out of choosePreviewSize().)Ching
@Ching I don't think that's the problem, I added the full log of the app in my question but it seems to be ok on the camera side. I'm really running out of ideas, it's kind of depressing..Reconstructionist
I made the same modification to Grafika (changed CameraCaptureActivity#onResume to call openCamera(640, 480)). I see the same new size=573x430 log. On my Moto X running 4.4.4, squares look square. I grabbed a screen shot for comparison (imgur.com/1oJNXYH). Our screens don't line up exactly, but that might be because my device has a visible nav bar on the right. Measuring your image with GIMP shows a 16:9 screen shot with a 4:3 camera area, so the View layout is fine. We seem to be doing the same thing, but for some reason it looks wrong on your device and fine on mine.Ching
Just to confirm: it looked correct with the unmodified Grafika (camera at 1280x720) on the Galaxy S3 Neo? It only distorts when using 640x480?Ching
@Ching again thank you so much for your time! It really depends on the device, for 90% of the device I have here it works but for some it's stretched. It seems to be linked to the size camera can handle vs the actual size of the SurfaceView (depending of the screen).Reconstructionist
@Ching and yes, it looked correct with the 720p initial resolution. It only appears when a certain camera/surface resolution is matched, but I'm not sure howReconstructionist
I'm wondering if the device is lying about its camera capabilities, and actually giving you 16:9 images. @EddyTalvala might have some ideas.Ching
Random thought: does anything change if openCamera() tries for CAMERA_FACING_BACK instead? (Wondering if the device has two cameras, and the data for one is wrong, but nobody notices because pictures are usually taken with the back-facing camera.)Ching
@Ching Actually it's taken with Back camera, not front. Forgot to mention that but I changed it too. But I have cases (when using a 16/9 resolution) where the the back camera is stretched and not the front. One thing I realized too is that the 2 devices that have the bug (on the back camera, on 16/9 resolution) are buggy using Camera2 and not with the old Camera API (because the device supports more resolution with the old API). So if it's a Camera API issue the question would be: how to detect it...Reconstructionist
And to be complete: those 2 devices are the Galaxy S4 and the One plus one. Both running Lollipop. And both works using the old camera API.. I checked on the web and didn't find anything about a bug on those precise devices but who knowsReconstructionist
Likely, you're either running into a bug with LEGACY-level camera2 devices, where the legacy support layer is doing the wrong thing given the sizes reported by the hardware, or at the device-specific code level for those devices. With both, we've seen issues with when the still picture aspect ratio doesn't match the preview aspect ratio. If you're configuring a JPEG stream, try making it the same aspect ratio if it already isn't.Kropotkin
@EddyTalvala thank you for your inputs! Right now, with my last example (with the S3 Neo), I'm using the old Camera api (since it's Android 4.4.1 and with Grafika's code), so it's a bug that is reproducible even without Camera2. I tried setting the pictureSize to the same resolution (or ratio if not available) as the preview but it didn't help. My problem is that if I detect that Camera2 is in LEGACY mode and doesn't supports my resolution but the old Camera API does, I can fallback to Camera1, but since I also reproduce the bug on Camera1, what tells me that it won't happen on some device?Reconstructionist
rereading my comment I realised I'm not clear enough: I'm having this bug on 2 devices (Galaxy S4 & One + One) using the back camera on a 16/9 resolution and Camera2 API. Also with the S3 Neo using a 4/3 resolution on the back camera with the old Camera API.Reconstructionist
@Ching I ended up using a code like that gist.github.com/benoitletondor/a8bffe56ca1b5106826ca95a0fe8fd74 to select which camera api to use. It's probably not perfect but it works on all the devices I've been able to test. A bit sad though...Reconstructionist
Did you end up with a satifactory solution? If not, can I ask: 1) How are you determining captureWidth & captureHeight? 2) Are you doing setPreviewSize() and setPictureSize to the same mPreviewSize? 3) Do you know if the problem devices might not be 16x9?Exceeding
@MiaoLiu 1) It's a fixed resolution of what I want. I'm usually using 1280x720, but for the test with S3 Neo I used 640x480. 2) No, I'm using this method: gist.github.com/benoitletondor/a204f78fc6b61ca6a97b1823363adcd6 3) It happens on devices that are 16/9 with a 16/9 camera size and also on some with a 4/3 camera size.Reconstructionist
Well it solves the problem on some devices since the camera will produce the correct ratio but it just hides the real problem on my opinion: you'll find devices where it doesn't work in full screen since the camera doesn't produce the correct ratio for the requested resolutionReconstructionist
M
1

i`m sorry for my last answer, it was stupid and didn`t resolve problem. But i founded another solution, and tested it on 5 device using different camera aspect ratio. As i understood, you`re using Grafika code. Try to remove parms.setRecordingHint(true); So now i`m using only param.setPictureSize() param.setPreviewSize() with same aspect ratio in camera params. Also i set gl view`s size with that aspect, i made it by myself.In grafika it used fixed size, afaik. Hope it`ll help you.

Monosome answered 9/12, 2016 at 5:53 Comment(3)
Hey Roman, thank you for your answer. The thing is, removing setRecordingHint can lead a lot of lag and framerate drop for videos since the camera will be in photo mode so it's probably a bad idea to do that, don't you think?Reconstructionist
Yes, i got lower frames but it resolved bug with resolution. Also you can use lower preview resolution, i didn`t noticed any framerate changes if i use 320x240 for previewSize wo flag on galaxy s3. Also, before i founded this, i just used same aspect ratio for preview & picture size, as default rate, which i got from getPreferredPreviewSizeForVideo().Monosome
Ok good to know thank you. The thing is I need at least 720p so I can't disable the setRecordingHint but it's good to know as a last resort fallback. Thank you again.Reconstructionist

© 2022 - 2024 — McMap. All rights reserved.