دوربین را کنترل کنید

در این درس، ما در مورد چگونگی کنترل مستقیم سخت‌افزار دوربین با استفاده از APIهای فریم‌ورک بحث خواهیم کرد.

توجه: این صفحه به کلاس Camera اشاره دارد که منسوخ شده است. توصیه می‌کنیم از CameraX یا برای موارد استفاده خاص، Camera2 استفاده کنید. هر دو CameraX و Camera2 از اندروید ۵.۰ (سطح API ۲۱) و بالاتر پشتیبانی می‌کنند.

کنترل مستقیم دوربین یک دستگاه به کد بسیار بیشتری نسبت به درخواست عکس یا فیلم از برنامه‌های دوربین موجود نیاز دارد. با این حال، اگر می‌خواهید یک برنامه دوربین تخصصی یا چیزی کاملاً یکپارچه با رابط کاربری برنامه خود بسازید، این درس نحوه انجام آن را به شما نشان می‌دهد.

به منابع مرتبط زیر مراجعه کنید:

شیء دوربین را باز کنید

دریافت یک نمونه از شیء Camera اولین قدم در فرآیند کنترل مستقیم دوربین است. همانطور که برنامه Camera خود اندروید انجام می‌دهد، روش توصیه شده برای دسترسی به دوربین، باز کردن Camera در یک thread جداگانه است که از onCreate() اجرا می‌شود. این رویکرد ایده خوبی است زیرا می‌تواند مدتی طول بکشد و ممکن است thread رابط کاربری را با مشکل مواجه کند. در یک پیاده‌سازی ابتدایی‌تر، باز کردن دوربین را می‌توان به متد onResume() موکول کرد تا استفاده مجدد از کد تسهیل شود و جریان کنترل سرراست بماند.

فراخوانی Camera.open() ‎ در صورتی که دوربین از قبل توسط برنامه دیگری در حال استفاده باشد، یک استثنا ایجاد می‌کند، بنابراین آن را در یک بلوک try قرار می‌دهیم.

کاتلین

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        mCamera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(getString(R.string.app_name), "failed to open Camera")
        e.printStackTrace()
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    mCamera?.also { camera ->
        camera.release()
        mCamera = null
    }
}

جاوا

private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        camera = Camera.open(id);
        qOpened = (camera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    preview.setCamera(null);
    if (camera != null) {
        camera.release();
        camera = null;
    }
}

از API سطح ۹ به بعد، فریم‌ورک دوربین از چندین دوربین پشتیبانی می‌کند. اگر از API قدیمی استفاده کنید و تابع open() را بدون آرگومان فراخوانی کنید، اولین دوربین عقب را دریافت خواهید کرد.

پیش‌نمایش دوربین را ایجاد کنید

گرفتن عکس معمولاً مستلزم آن است که کاربران شما قبل از کلیک روی شاتر، پیش‌نمایشی از سوژه خود را ببینند. برای انجام این کار، می‌توانید از SurfaceView برای ترسیم پیش‌نمایش‌هایی از آنچه حسگر دوربین ثبت می‌کند، استفاده کنید.

پیش‌نمایش کلاس

برای شروع نمایش پیش‌نمایش، به کلاس preview نیاز دارید. پیش‌نمایش نیاز به پیاده‌سازی رابط android.view.SurfaceHolder.Callback دارد که برای انتقال داده‌های تصویر از سخت‌افزار دوربین به برنامه استفاده می‌شود.

کاتلین

class Preview(
        context: Context,
        val surfaceView: SurfaceView = SurfaceView(context)
) : ViewGroup(context), SurfaceHolder.Callback {

    var mHolder: SurfaceHolder = surfaceView.holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }
    ...
}

جاوا

class Preview extends ViewGroup implements SurfaceHolder.Callback {

    SurfaceView surfaceView;
    SurfaceHolder holder;

    Preview(Context context) {
        super(context);

        surfaceView = new SurfaceView(context);
        addView(surfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = surfaceView.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
...
}

کلاس پیش‌نمایش باید قبل از شروع پیش‌نمایش تصویر زنده، همانطور که در بخش بعدی نشان داده شده است، به شیء Camera ) ارسال شود.

تنظیم و شروع پیش‌نمایش

یک نمونه دوربین و پیش‌نمایش مربوط به آن باید به ترتیب خاصی ایجاد شوند، به طوری که شیء دوربین در ابتدا قرار گیرد. در قطعه کد زیر، فرآیند مقداردهی اولیه دوربین به گونه‌ای کپسوله‌سازی شده است که هر زمان کاربر کاری برای تغییر دوربین انجام دهد، Camera.startPreview() توسط متد setCamera() فراخوانی می‌شود. پیش‌نمایش همچنین باید در متد فراخوانی کلاس preview به نام surfaceChanged() مجدداً راه‌اندازی شود.

کاتلین

fun setCamera(camera: Camera?) {
    if (mCamera == camera) {
        return
    }

    stopPreviewAndFreeCamera()

    mCamera = camera

    mCamera?.apply {
        mSupportedPreviewSizes = parameters.supportedPreviewSizes
        requestLayout()

        try {
            setPreviewDisplay(holder)
        } catch (e: IOException) {
            e.printStackTrace()
        }

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

جاوا

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

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
        supportedPreviewSizes = localSizes;
        requestLayout();

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

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

تنظیمات دوربین را تغییر دهید

تنظیمات دوربین، نحوه‌ی عکس‌برداری دوربین را تغییر می‌دهد، از سطح زوم گرفته تا جبران نوردهی. این مثال فقط اندازه‌ی پیش‌نمایش را تغییر می‌دهد؛ برای اطلاعات بیشتر به کد منبع برنامه‌ی دوربین مراجعه کنید.

کاتلین

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    mCamera?.apply {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        parameters?.also { params ->
            params.setPreviewSize(previewSize.width, previewSize.height)
            requestLayout()
            parameters = params
        }

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

جاوا

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(previewSize.width, previewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

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

تنظیم جهت پیش‌نمایش

اکثر برنامه‌های دوربین، صفحه نمایش را در حالت افقی قفل می‌کنند، زیرا این جهت طبیعی حسگر دوربین است. این تنظیم مانع از گرفتن عکس‌های پرتره نمی‌شود، زیرا جهت دستگاه در هدر EXIF ​​ثبت می‌شود. متد setCameraDisplayOrientation() به شما امکان می‌دهد نحوه نمایش پیش‌نمایش را بدون تأثیر بر نحوه ضبط تصویر تغییر دهید. با این حال، در اندروید قبل از API سطح ۱۴، باید پیش‌نمایش خود را قبل از تغییر جهت متوقف کرده و سپس آن را مجدداً راه‌اندازی کنید.

عکس بگیر

از متد Camera.takePicture() برای گرفتن عکس پس از شروع پیش‌نمایش استفاده کنید. می‌توانید اشیاء Camera.PictureCallback و Camera.ShutterCallback را ایجاد کرده و آنها را به Camera.takePicture() ارسال کنید.

اگر می‌خواهید تصاویر را به طور مداوم ثبت کنید، می‌توانید یک Camera.PreviewCallback ایجاد کنید که onPreviewFrame() را پیاده‌سازی می‌کند. برای چیزی بین این دو، می‌توانید فقط فریم‌های پیش‌نمایش انتخاب شده را ثبت کنید، یا یک اقدام با تأخیر برای فراخوانی takePicture() تنظیم کنید.

پیش‌نمایش را دوباره شروع کنید

پس از گرفتن عکس، قبل از اینکه کاربر بتواند عکس دیگری بگیرد، باید پیش‌نمایش را مجدداً راه‌اندازی کنید. در این مثال، راه‌اندازی مجدد با بارگذاری بیش از حد دکمه شاتر انجام می‌شود.

کاتلین

fun onClick(v: View) {
    previewState = if (previewState == K_STATE_FROZEN) {
        camera?.startPreview()
        K_STATE_PREVIEW
    } else {
        camera?.takePicture(null, rawCallback, null)
        K_STATE_BUSY
    }
    shutterBtnConfig()
}

جاوا

@Override
public void onClick(View v) {
    switch(previewState) {
    case K_STATE_FROZEN:
        camera.startPreview();
        previewState = K_STATE_PREVIEW;
        break;

    default:
        camera.takePicture( null, rawCallback, null);
        previewState = K_STATE_BUSY;
    } // switch
    shutterBtnConfig();
}

پیش‌نمایش را متوقف کنید و دوربین را رها کنید

وقتی برنامه شما با استفاده از دوربین کار خود را تمام کرد، وقت آن است که آن را پاکسازی کنید. به طور خاص، باید شیء Camera را رها کنید، در غیر این صورت خطر از کار افتادن برنامه‌های دیگر، از جمله نمونه‌های جدید برنامه خودتان، را خواهید داشت.

چه زمانی باید پیش‌نمایش را متوقف کرده و دوربین را رها کنید؟ خب، خراب شدن سطح پیش‌نمایش، همانطور که در این متدها از کلاس Preview نشان داده شده است، نشانه‌ی خوبی است که زمان توقف پیش‌نمایش و رها کردن دوربین فرا رسیده است.

کاتلین

override fun surfaceDestroyed(holder: SurfaceHolder) {
    // Surface will be destroyed when we return, so stop the preview.
    // Call stopPreview() to stop updating the preview surface.
    mCamera?.stopPreview()
}

/**
 * When this function returns, mCamera will be null.
 */
private fun stopPreviewAndFreeCamera() {
    mCamera?.apply {
        // Call stopPreview() to stop updating the preview surface.
        stopPreview()

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        release()

        mCamera = null
    }
}

جاوا

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();
    }
}

/**
 * When this function returns, mCamera will be null.
 */
private void stopPreviewAndFreeCamera() {

    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        mCamera.release();

        mCamera = null;
    }
}

پیش از این در درس، این رویه بخشی از متد setCamera() نیز بود، بنابراین مقداردهی اولیه یک دوربین همیشه با توقف پیش‌نمایش آغاز می‌شود.