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

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

توجه: این صفحه به کلاس دوربین اشاره دارد که منسوخ شده است. توصیه می‌کنیم از CameraX یا برای موارد استفاده خاص از Camera2 استفاده کنید. ‫CameraX و Camera2 هر دو از Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر پشتیبانی می‌کنند.

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

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

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

دریافت نمونه‌ای از شیء Camera اولین گام در فرایند کنترل مستقیم دوربین است. همان‌طور که برنامه «دوربین» Android انجام می‌دهد، روش توصیه‌شده برای دسترسی به دوربین، باز کردن Camera در رشته‌ای جداگانه است که از onCreate() راه‌اندازی شده است. این رویکرد ایده خوبی است چون ممکن است مدتی طول بکشد و رشته میانای کاربر را کند کند. در پیاده‌سازی ساده‌تر، باز کردن دوربین را می‌توان به روش 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‏ ۹، چارچوب دوربین از چندین دوربین پشتیبانی می‌کند. اگر از میانای برنامه‌سازی کاربردی قدیمی استفاده می‌کنید و open() را بدون آرگومان فراخوانی می‌کنید، اولین دوربین پشت را دریافت می‌کنید.

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

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

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

برای شروع نمایش پیش‌نمایش، به کلاس پیش‌نمایش نیاز دارید. پیش‌نمایش به پیاده‌سازی میانای 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() فراخوانی شود. پیش‌نمایش باید در روش فراخوانی کلاس پیش‌نمایش 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() به شما امکان می‌دهد نحوه نمایش پیش‌نمایش را بدون تأثیر بر نحوه ضبط تصویر تغییر دهید. اما در Android قبل‌از سطح 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() نیز بود، بنابراین مقداردهی اولیه دوربین همیشه با توقف پیش‌نمایش شروع می‌شود.