Android Camera Preview Stretched

136

Я работал над созданием своей пользовательской активности камеры на Android, но при повороте камеры соотношение сторон вида поверхности испортилось.

В моем режиме для действия я установил структуру кадра, которая содержит вид поверхности, отображающий параметры камеры.

//FrameLayout that will hold the camera preview
        FrameLayout previewHolder = (FrameLayout) findViewById(R.id.camerapreview);

        //Setting camera's preview size to the best preview size
        Size optimalSize = null;
        camera = getCameraInstance();
        double aspectRatio = 0;
        if(camera != null){
            //Setting the camera's aspect ratio
            Camera.Parameters parameters = camera.getParameters();
            List<Size> sizes = parameters.getSupportedPreviewSizes();
            optimalSize = CameraPreview.getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
            aspectRatio = (float)optimalSize.width/optimalSize.height;
        }

        if(optimalSize!= null){
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            previewHolder.setLayoutParams(params);
            LayoutParams surfaceParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            cameraPreview.setLayoutParams(surfaceParams);

        }

        cameraPreview.setCamera(camera);

        //Adding the preview to the holder
        previewHolder.addView(cameraPreview);

Затем в окне Surface я установил параметры камеры для отображения

public void setCamera(Camera camera) {
        if (mCamera == camera) { return; }

        mCamera = camera;

        if (mCamera != null) {
            requestLayout();

            try {
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }


            if(mCamera != null){
                //Setting the camera's aspect ratio
                Camera.Parameters parameters = mCamera.getParameters();
                List<Size> sizes = parameters.getSupportedPreviewSizes();
                Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);

                parameters.setPreviewSize(optimalSize.width, optimalSize.height);
                mCamera.setParameters(parameters);
            }

            /*
              Important: Call startPreview() to start updating the preview surface. Preview must 
              be started before you can take a picture.
              */
            mCamera.startPreview();
        }

    }

введите описание изображения здесь

Вы можете видеть, что человек LEGO становится выше и стройнее, когда телефон поворачивается:

Как я могу убедиться в правильности соотношения сторон экрана моей камеры?

scientiffic
источник
@scientific, можете ли вы помочь мне, где я должен написать данный метод, я также сталкиваюсь с той же проблемой?
user3233280
Кажется, у меня возникла похожая проблема, но то, что она исправила, вызывало следующее: mCamera.setDisplayOrientation (90) в методе SurfaceCreated () моего SurfaceView для камеры
Грег,

Ответы:

163

Я использую этот метод -> на основе API Демо, чтобы получить размер предварительного просмотра:

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio=(double)h / w;

        if (sizes == null) return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

Как вы можете видеть, вы должны ввести ширину и высоту экрана. Этот метод рассчитает соотношение экрана на основе этих значений, а затем из списка поддерживаемых размеров просмотра он выберет для вас лучшее из доступных. Получите ваш список SupportPreviewSize в месте, где объект Camera не равен NULL, используя

mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();

И затем в onMeasure вы можете получить оптимальный размер предварительного просмотра, например:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(width, height);

        if (mSupportedPreviewSizes != null) {
           mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }
    }

И затем (в моем коде в методе SurfaceChanged, как я уже говорил, я использую структуру API Demos кода CameraActivity, вы можете сгенерировать ее в Eclipse):

Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();

И один совет для вас, потому что я сделал почти то же приложение, что и вы. Хорошей практикой для Camera Activity является скрытие StatusBar. Такие приложения, как Instagram, делают это. Это уменьшает значение высоты экрана и меняет соотношение. На некоторых устройствах можно получить странные размеры предварительного просмотра (ваш SurfaceView будет немного обрезан)


И чтобы ответить на ваш вопрос, как проверить правильность вашего предварительного просмотра? Затем получите высоту и ширину параметров, которые вы установили:

mCamera.setParameters(parameters);

Ваше установленное соотношение равно высоте / ширине. Если вы хотите, чтобы камера хорошо выглядела на вашем экране, то соотношение высоты / ширины параметров, установленных вами для камеры, должно совпадать с соотношением высоты (без строки состояния) / ширины вашего экрана.

F1sher
источник
2
Только один вопрос: я вижу, что вы используете 2 разных вычисленных соотношения: double targetRatio = (double) h / w и double ratio = (double) size.width / size.height . Первый - это высота, деленная на ширину, а другая - наоборот, ширина, деленная на высоту. Разве они не должны быть одинаковыми вычислениями?
phyzalis
Это не имеет значения, потому что список размеров учитывает каждую возможность, например: вы найдете что-то вроде 480x680, но рано или поздно будет 680x480 (для ландшафта), поэтому вам просто нужно просмотреть этот список и найти тот, который соответствует вашим потребностям. Вы найдете это рано или поздно.
F1sher
Кроме того, если вы хотите, чтобы ваши изображения были такого же размера (как они, вероятно, должны быть), вы установили размер изображения с помощью parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height).
Мэтт Логан
2
@ F1sher, я всегда получаю параметры исключений нулевого указателя параметры.setPreviewSize (mPreviewSize.width, mPreviewSize.height); Не могу найти никакого решения.
FIXI
3
@phyzalis Я согласен с тобой. На самом деле, я должен был сделать это исправление, чтобы код работал так, как ожидалось.
Fernio
149

Решение F1Sher хорошо, но иногда не работает. В частности, когда ваш SurfaceView не покрывает весь экран. В этом случае вам необходимо переопределить метод onMeasure (). Я скопировал мой код здесь для вашей справки.

Так как я измерил SurfaceView на основе ширины, то в конце экрана у меня есть небольшой белый промежуток, который я заполнил. Вы можете решить эту проблему, если сохраните высоту и увеличите ширину, умножив ее на коэффициент. Тем не менее, он слегка раздавит SurfaceView.

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "CameraPreview";

    private Context mContext;
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private List<Camera.Size> mSupportedPreviewSizes;
    private Camera.Size mPreviewSize;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mContext = context;
        mCamera = camera;

        // supported preview sizes
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        for(Camera.Size str: mSupportedPreviewSizes)
                Log.e(TAG, str.width + "/" + str.height);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // empty. surfaceChanged will take care of stuff
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + h);
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.getSurface() == null){
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or reformatting changes here
        // start preview with new settings
        try {
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            mCamera.setParameters(parameters);
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

        if (mSupportedPreviewSizes != null) {
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }

        if (mPreviewSize!=null) {
            float ratio;
            if(mPreviewSize.height >= mPreviewSize.width)
                ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
            else
                ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;

            // One of these methods should be used, second method squishes preview slightly
            setMeasuredDimension(width, (int) (width * ratio));
  //        setMeasuredDimension((int) (width * ratio), height);
        }
    }

    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;

        if (sizes == null)
            return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.height / size.width;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;

            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }

        return optimalSize;
    }
}
Hesam
источник
11
это должен быть правильный ответ, он обрабатывает случай, когда предварительный просмотр камеры не покрывает весь экран. +1
Усина
1
когда я раздуть в моем приложении деятельности был закрыт <com.app.camera.CameraPreview андроид: идентификатор = "@ + идентификатор / camera_preview" Android: layout_width = "match_parent" андроид: layout_height = "match_parent" />
fish40
Вы должны внести некоторые изменения, но я принимаю это как решение. Up-голосование.
JaydeepW
2
Привет @ adnan9011, этот учебник может быть полезным, вероятно. airpair.com/android/android-camera-surface-view-fragment
Хесам,
4
высота и ширина должны были быть включены, getOptimalPreviewSizeчтобы это работало на меня. это потому, что ориентация дисплея была установлена ​​на 90? В противном случае, расчет коэффициента не были даже близко (цель была 1,7 , а коэффициенты камеры были меньше , чем 1)
Ono
23

ПРИМЕЧАНИЕ: МОЕ РЕШЕНИЕ - ПРОДОЛЖЕНИЕ РЕШЕНИЯ HESAM: https://stackoverflow.com/a/22758359/1718734

Что я адресую: Хесам сказал, что на некоторых телефонах может появиться небольшое пустое пространство, например:

ПРИМЕЧАНИЕ. Несмотря на правильное соотношение сторон, камера не заполняет весь экран.

Хесам предложил второе решение, но оно сводит на нет предварительный просмотр. И на некоторых устройствах это сильно искажает.

Итак, как мы можем решить эту проблему. Это просто ... путем умножения пропорций, пока он не заполнит экран. Я заметил, что несколько популярных приложений, таких как Snapchat, WhatsApp и т. Д., Работают одинаково.

Все, что вам нужно сделать, это добавить это в метод onMeasure:

float camHeight = (int) (width * ratio);
    float newCamHeight;
    float newHeightRatio;

    if (camHeight < height) {
        newHeightRatio = (float) height / (float) mPreviewSize.height;
        newCamHeight = (newHeightRatio * camHeight);
        Log.e(TAG, camHeight + " " + height + " " + mPreviewSize.height + " " + newHeightRatio + " " + newCamHeight);
        setMeasuredDimension((int) (width * newHeightRatio), (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | H_ratio - " + newHeightRatio + " | A_width - " + (width * newHeightRatio) + " | A_height - " + newCamHeight);
    } else {
        newCamHeight = camHeight;
        setMeasuredDimension(width, (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | A_width - " + (width) + " | A_height - " + newCamHeight);
    }

Это вычислит высоту экрана и получит соотношение высоты экрана и высоты mPreviewSize. Затем он умножает ширину и высоту камеры на новое соотношение высоты и соответственно устанавливает измеренный размер.

введите описание изображения здесь

И следующее, что вы знаете, вы в конечном итоге с этим: D

введите описание изображения здесь

Это также хорошо работает с фронтальной камерой. Я считаю, что это лучший способ сделать это. Теперь единственное, что осталось для моего приложения, - это сохранить сам предварительный просмотр после нажатия «Захват». Но да, это оно.

Yoosuf
источник
Это работает очень хорошо! но это не работает должным образом в Пейзаж: /
Матан Дахан
Ваш код решил мою проблему с пустым черным пространством за 2 месяца :)
kartik kolanji
1
Это работает, но в моем случае ... У меня есть панель действий вверху, поэтому белая полоса была меньше, однако после добавления вашего кода камера выглядела увеличенной на 40-50%. Когда я переключаюсь на фронтальную камеру, из-за этого большого зума я не вижу, как выглядит мое лицо (только волосы).
Makalele
@ Да, у меня белый экран, когда я следую твоему коду. В onMeasure(int widthMeasureSpec, int heightMeasureSpec)методе я всегда получаю width = 0 и height = 0;
Куш Патель
10

Хорошо, так что я думаю, что нет достаточного ответа для общей проблемы растяжения предварительного просмотра камеры. Или, по крайней мере, я не нашел. Мое приложение также страдало этим синдромом растяжения, и мне потребовалось некоторое время, чтобы найти решение из всех ответов пользователей на этом портале и в Интернете.

Я попробовал решение @ Hesam но оно не сработало, и мой предварительный просмотр камеры был сильно искажен.

Сначала я показываю код моего решения (важные части кода), а затем объясняю, почему я предпринял эти шаги. Есть место для модификаций производительности.

Основная деятельность xml layout:

<RelativeLayout 
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
</RelativeLayout>

Предварительный просмотр камеры:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

private SurfaceHolder prHolder;
private Camera prCamera;
public List<Camera.Size> prSupportedPreviewSizes;
private Camera.Size prPreviewSize;

@SuppressWarnings("deprecation")
public YoCameraPreview(Context context, Camera camera) {
    super(context);
    prCamera = camera;

    prSupportedPreviewSizes = prCamera.getParameters().getSupportedPreviewSizes();

    prHolder = getHolder();
    prHolder.addCallback(this);
    prHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public void surfaceCreated(SurfaceHolder holder) {
    try {
        prCamera.setPreviewDisplay(holder);
        prCamera.startPreview();
    } catch (IOException e) {
        Log.d("Yologram", "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceDestroyed(SurfaceHolder holder) {
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    if (prHolder.getSurface() == null){
      return;
    }

    try {
        prCamera.stopPreview();
    } catch (Exception e){
    }

    try {
        Camera.Parameters parameters = prCamera.getParameters();
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        parameters.setPreviewSize(prPreviewSize.width, prPreviewSize.height);

        prCamera.setParameters(parameters);
        prCamera.setPreviewDisplay(prHolder);
        prCamera.startPreview();

    } catch (Exception e){
        Log.d("Yologram", "Error starting camera preview: " + e.getMessage());
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    setMeasuredDimension(width, height);

    if (prSupportedPreviewSizes != null) {
        prPreviewSize = 
            getOptimalPreviewSize(prSupportedPreviewSizes, width, height);
    }    
}

public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio = (double) h / w;

    if (sizes == null)
        return null;

    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;

    int targetHeight = h;

    for (Camera.Size size : sizes) {
        double ratio = (double) size.width / size.height;
        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
            continue;

        if (Math.abs(size.height - targetHeight) < minDiff) {
            optimalSize = size;
            minDiff = Math.abs(size.height - targetHeight);
        }
    }

    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
    }

    return optimalSize;
}
}

Основное занятие:

public class MainActivity extends Activity {

...

@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);     
    setContentView(R.layout.activity_main);

        maCamera = getCameraInstance();

        maLayoutPreview = (FrameLayout) findViewById(R.id.camera_preview);

        maPreview = new CameraPreview(this, maCamera);

        Point displayDim = getDisplayWH();
        Point layoutPreviewDim = calcCamPrevDimensions(displayDim, 
                maPreview.getOptimalPreviewSize(maPreview.prSupportedPreviewSizes, 
                    displayDim.x, displayDim.y));
        if (layoutPreviewDim != null) {
            RelativeLayout.LayoutParams layoutPreviewParams = 
                (RelativeLayout.LayoutParams) maLayoutPreview.getLayoutParams();
            layoutPreviewParams.width = layoutPreviewDim.x;
            layoutPreviewParams.height = layoutPreviewDim.y;
            layoutPreviewParams.addRule(RelativeLayout.CENTER_IN_PARENT);
            maLayoutPreview.setLayoutParams(layoutPreviewParams);
        }
        maLayoutPreview.addView(maPreview);
}

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
private Point getDisplayWH() {

    Display display = this.getWindowManager().getDefaultDisplay();
    Point displayWH = new Point();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
        display.getSize(displayWH);
        return displayWH;
    }
    displayWH.set(display.getWidth(), display.getHeight());
    return displayWH;
}

private Point calcCamPrevDimensions(Point disDim, Camera.Size camDim) {

    Point displayDim = disDim;
    Camera.Size cameraDim = camDim;

    double widthRatio = (double) displayDim.x / cameraDim.width;
    double heightRatio = (double) displayDim.y / cameraDim.height;

    // use ">" to zoom preview full screen
    if (widthRatio < heightRatio) {
        Point calcDimensions = new Point();
        calcDimensions.x = displayDim.x;
        calcDimensions.y = (displayDim.x * cameraDim.height) / cameraDim.width;
        return calcDimensions;
    }
    // use "<" to zoom preview full screen
    if (widthRatio > heightRatio) { 
        Point calcDimensions = new Point();
        calcDimensions.x = (displayDim.y * cameraDim.width) / cameraDim.height;
        calcDimensions.y = displayDim.y;
        return calcDimensions;
    }
    return null;
}   
}

Мой комментарий:

Смысл всего этого в том, что , хотя вы вычислить размер оптимальной камеры в getOptimalPreviewSize()вас только выбрать ближайший отношение , чтобы соответствовать экрану. Поэтому, если соотношение не будет таким же, предварительный просмотр будет растягиваться.

Почему это будет растягиваться? Потому что для предварительного просмотра камеры FrameLayout в layout.xml задано значение match_parent по ширине и высоте. Вот почему предварительный просмотр растянется на весь экран.

Что нужно сделать, это установить ширину и высоту макета предварительного просмотра камеры в соответствии с выбранным соотношением размеров камеры , чтобы предварительный просмотр сохранял соотношение сторон и не искажал.

Я пытался использовать CameraPreviewкласс для выполнения всех вычислений и изменений макета, но я не мог понять это. Я пытался применить это решение , но SurfaceViewне распознает getChildCount ()или getChildAt (int index). Я думаю, что со временем все получилось, со ссылкой на maLayoutPreview, но это было нехорошо, и я применил установленный коэффициент ко всему моему приложению, и он сделал это после того, как была сделана первая фотография. Так что я отпустил его и перенес изменения макета вMainActivity .

В CameraPreviewя изменился prSupportedPreviewSizesи getOptimalPreviewSize()на публику, чтобы я мог использовать его в MainActivity. Затем мне понадобились размеры дисплея (без навигационной панели / строки состояния, если она есть) и выбор оптимального размера камеры . Я пытался получить размер RelativeLayout (или FrameLayout) вместо размера дисплея, но он возвращал нулевое значение. Это решение не сработало для меня. Макет получил свое значение после onWindowFocusChanged(проверено в журнале).

Таким образом, у меня есть свои методы для расчета размеров макета, чтобы соответствовать соотношению сторон выбранного размера камеры. Теперь вам просто нужно настроить LayoutParamsмакет предварительного просмотра камеры. Измените ширину, высоту и отцентрируйте его в родительском.

Есть два варианта расчета размеров предварительного просмотра. Либо вы хотите, чтобы он соответствовал экрану с черными полосами (если для windowBackground установлено значение null) по бокам или сверху / снизу. Или вы хотите, чтобы предварительный просмотр увеличен до полного экрана . Я оставил комментарий с дополнительной информацией в calcCamPrevDimensions().

Уилл Нейтан
источник
6

Здравствуйте, getOptimalPreview (), который здесь не работает, поэтому я хочу поделиться своей версией:

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

    if (sizes==null) return null;

    Camera.Size optimalSize = null;
    double ratio = (double)h/w;
    double minDiff = Double.MAX_VALUE;
    double newDiff;
    for (Camera.Size size : sizes) {
        newDiff = Math.abs((double)size.width/size.height - ratio);
        if (newDiff < minDiff) {
            optimalSize = size;
            minDiff = newDiff;
        }
    }
    return optimalSize;
}
EstebanA
источник
он конвертируется в квадратную камеру, учитывая вход (1920x1080) после оптимизации заданной ширины и высоты (1088x1088), мое тестирующее устройство - Samsung S6. Могу ли я знать, в чем причина. Пожалуйста, поделитесь, если у вас есть идеи по этому вопросу.
МоханРадж С
2

Просто чтобы сделать эту тему более полной, я добавляю свою версию ответа:

Чего я хотел добиться: вид поверхности не должен быть растянут, и он должен охватывать весь экран. Более того, в моем приложении был только ландшафтный режим.

Решение:

Решение является чрезвычайно небольшим расширением решения F1sher:

=> Первый шаг - интегрировать решение F1sher.

=> Теперь в решении F1sher может возникнуть сценарий, когда вид поверхности не покрывает весь экран. Решение состоит в том, чтобы сделать вид поверхности больше размеров экрана, чтобы он охватывал весь экран, для этого:

    size = getOptimalPreviewSize(mCamera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight);

    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(size.width, size.height);


    mCamera.setParameters(parameters);      

    double screenRatio = (double) screenHeight / screenWidth;
    double previewRatio = (double) size.height / size.width;

    if (previewRatio > screenRatio)     /*if preview ratio is greater than screen ratio then we will have to recalculate the surface height while keeping the surface width equal to the screen width*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);

        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);        
    }
    else     /*if preview ratio is smaller than screen ratio then we will have to recalculate the surface width while keeping the surface height equal to the screen height*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);
        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);

    }       

    flPreview.addView(mPreview); 

  /*  The TopMost layout used is the RelativeLayout, flPreview is the FrameLayout in which Surface View is added, mPreview is an instance of a class which extends SurfaceView  */
Сартак Миттал
источник
1

Я разобрался в чем проблема - именно с изменением ориентации . Если вы измените ориентацию камеры на 90 или 270 градусов, вам нужно поменять местами ширину и высоту поддерживаемых размеров, и все будет в порядке.

Также вид поверхности должен лежать в рамке и иметь центр тяжести.

Вот пример на C # (Xamarin):

public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
    _camera.StopPreview();

    // find best supported preview size

    var parameters = _camera.GetParameters();
    var supportedSizes = parameters.SupportedPreviewSizes;
    var bestPreviewSize = supportedSizes
        .Select(x => new { Width = x.Height, Height = x.Width, Original = x }) // HACK swap height and width because of changed orientation to 90 degrees
        .OrderBy(x => Math.Pow(Math.Abs(x.Width - width), 3) + Math.Pow(Math.Abs(x.Height - height), 2))
        .First();

    if (height == bestPreviewSize.Height && width == bestPreviewSize.Width)
    {
        // start preview if best supported preview size equals current surface view size

        parameters.SetPreviewSize(bestPreviewSize.Original.Width, bestPreviewSize.Original.Height);
        _camera.SetParameters(parameters);
        _camera.StartPreview();
    }
    else
    {
        // if not than change surface view size to best supported (SurfaceChanged will be called once again)

        var layoutParameters = _surfaceView.LayoutParameters;
        layoutParameters.Width = bestPreviewSize.Width;
        layoutParameters.Height = bestPreviewSize.Height;
        _surfaceView.LayoutParameters = layoutParameters;
    }
}

Обратите внимание, что параметры камеры должны быть установлены как исходный размер (не поменялся местами), а размер вида поверхности должен поменяться местами.

Александр Данилов
источник
1

Я попробовал все решения выше, но ни один из них не работает для меня. В конце концов, я решил это сам, и обнаружил, что это довольно легко. Есть два момента, которые вы должны быть осторожны.

parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);

Этот previewSize должен быть одним из поддерживаемых разрешением камеры, которое можно получить, как показано ниже:

List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes(); 

обычно один из rawSupportedSize равен разрешению устройства.

Во-вторых, поместите SurfaceView в FrameLayout и установите высоту и ширину макета поверхности в методе SurfaceChanged, как указано выше.

FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
layoutParams.height = cameraResolution.x;
layoutParams.width = cameraResolution.y;

Хорошо, все готово, надеюсь, это поможет вам.

SalutonMondo
источник
0

Мои требования: предварительный просмотр камеры должен быть полноэкранным и сохранять соотношение сторон. Решение Hesam и Yoosuf было великолепным, но я почему-то вижу проблему с большим зумом.

Идея та же: иметь центр контейнера предварительного просмотра в родительском элементе и увеличивать ширину или высоту в зависимости от пропорций, пока он не сможет охватить весь экран.

Следует отметить, что размер предварительного просмотра находится в альбомной ориентации, поскольку мы устанавливаем ориентацию экрана.

camera.setDisplayOrientation (90);

Контейнер, к которому мы добавим представление SurfaceView:

<RelativeLayout
    android:id="@+id/camera_preview_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"/>

Добавьте предварительный просмотр в его контейнер с центром в родительском элементе в вашей активности.

this.cameraPreview = new CameraPreview(this, camera);
cameraPreviewContainer.removeAllViews();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
cameraPreviewContainer.addView(cameraPreview, 0, params);

Внутри класса CameraPreview:

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    // If your preview can change or rotate, take care of those events here.
    // Make sure to stop the preview before resizing or reformatting it.

    if (holder.getSurface() == null) {
        // preview surface does not exist
        return;
    }

    stopPreview();

    // set preview size and make any resize, rotate or
    // reformatting changes here
    try {
        Camera.Size nativePictureSize = CameraUtils.getNativeCameraPictureSize(camera);
        Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(optimalSize.width, optimalSize.height);
        parameters.setPictureSize(nativePictureSize.width, nativePictureSize.height);
        camera.setParameters(parameters);
        camera.setDisplayOrientation(90);
        camera.setPreviewDisplay(holder);
        camera.startPreview();

    } catch (Exception e){
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    if (supportedPreviewSizes != null && optimalSize == null) {
        optimalSize = CameraUtils.getOptimalSize(supportedPreviewSizes, width, height);
        Log.i(TAG, "optimal size: " + optimalSize.width + "w, " + optimalSize.height + "h");
    }

    float previewRatio =  (float) optimalSize.height / (float) optimalSize.width;
    // previewRatio is height/width because camera preview size are in landscape.
    float measuredSizeRatio = (float) width / (float) height;

    if (previewRatio >= measuredSizeRatio) {
        measuredHeight = height;
        measuredWidth = (int) ((float)height * previewRatio);
    } else {
        measuredWidth = width;
        measuredHeight = (int) ((float)width / previewRatio);
    }
    Log.i(TAG, "Preview size: " + width + "w, " + height + "h");
    Log.i(TAG, "Preview size calculated: " + measuredWidth + "w, " + measuredHeight + "h");

    setMeasuredDimension(measuredWidth, measuredHeight);
}
Говард Лин
источник
-1

Вы должны установить cameraView.getLayoutParams (). Height и cameraView.getLayoutParams (). Width в соответствии с желаемым соотношением сторон.

user2919210
источник
это отличается от установки параметров моего вида камеры таким образом (что я сейчас и делаю): parameters.setPreviewSize (optimSize.width, optimSize.height); mCamera.setParameters (параметры);
scientiffic
-2

Я отказался от расчетов и просто получил размер вида, в котором я хочу отобразить предварительный просмотр камеры, и установил такой же размер предварительного просмотра камеры (только ширина / высота при повороте) в моей пользовательской реализации SurfaceView:

@Override // CameraPreview extends SurfaceView implements SurfaceHolder.Callback { 
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    Display display = ((WindowManager) getContext().getSystemService(
            Context.WINDOW_SERVICE)).getDefaultDisplay();

    if (display.getRotation() == Surface.ROTATION_0) {
        final Camera.Parameters params = camera.getParameters();
        // viewParams is from the view where the preview is displayed
        params.setPreviewSize(viewParams.height, viewParams.width);
        camera.setDisplayOrientation(90);
        requestLayout();
        camera.setParameters(params);
    }
    // I do not enable rotation, so this can otherwise stay as is
}
Мартин Шураб
источник
Документ для Android гласит: При настройке размера предварительного просмотра вы должны использовать значения из getSupportedPreviewSizes (). Не устанавливайте произвольные значения в методе setPreviewSize ().
Г. Штайгерт