التحكّم في الكاميرا

في هذا الدرس، سنتناول كيفية التحكّم في أجهزة الكاميرا مباشرةً باستخدام واجهات برمجة التطبيقات الخاصة بإطار العمل.

ملاحظة: تشير هذه الصفحة إلى فئة الكاميرا التي تم إيقافها نهائيًا. ننصحك باستخدام CameraX أو Camera2 في حالات استخدام معيّنة. تتوافق كلّ من CameraX وCamera2 مع الإصدار 5.0 من نظام التشغيل Android (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث.

يتطلّب التحكّم المباشر في كاميرا الجهاز كتابة رمز برمجي أكثر بكثير من طلب الصور أو الفيديوهات من تطبيقات الكاميرا الحالية. ومع ذلك، إذا كنت تريد إنشاء تطبيق كاميرا متخصص أو تطبيق مدمج بالكامل في واجهة مستخدم تطبيقك، توضّح لك هذه الدرس كيفية إجراء ذلك.

يُرجى الاطّلاع على المراجع ذات الصلة التالية:

فتح "عنصر الكاميرا"

الحصول على مثيل من الكائن Camera هو الخطوة الأولى في عملية التحكّم المباشر في الكاميرا. كما هو الحال في تطبيق "الكاميرا" الخاص بنظام التشغيل Android، فإنّ الطريقة المقترَحة للوصول إلى الكاميرا هي فتح Camera في سلسلة محادثات منفصلة يتم تشغيلها من onCreate(). هذا النهج فكرة جيدة لأنّ العملية قد تستغرق بعض الوقت وقد تؤدي إلى إبطاء سلسلة واجهة المستخدم. في عملية تنفيذ أبسط، يمكن تأجيل فتح الكاميرا إلى الطريقة onResume() لتسهيل إعادة استخدام الرمز البرمجي والحفاظ على تدفق التحكم بشكل مباشر.

يؤدي استدعاء Camera.open() إلى طرح استثناء إذا كان تطبيق آخر يستخدم الكاميرا، لذا نضعها في كتلة try.

Kotlin

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
    }
}

Java

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;
    }
}

منذ المستوى 9 من واجهة برمجة التطبيقات، يتيح إطار عمل الكاميرا استخدام كاميرات متعددة. إذا كنت تستخدم واجهة برمجة التطبيقات القديمة واستدعيت open() بدون وسيطة، ستحصل على الكاميرا الخلفية الأولى.

إنشاء معاينة الكاميرا

يتطلّب التقاط صورة عادةً أن يرى المستخدمون معاينة لما يتم تصويره قبل النقر على زر الغالق. لإجراء ذلك، يمكنك استخدام SurfaceView لرسم معاينات لما ترصده أداة استشعار الكاميرا.

معاينة الصف

لبدء عرض معاينة، يجب أن يكون لديك فئة معاينة. تتطلّب المعاينة تنفيذ واجهة android.view.SurfaceHolder.Callback، والتي تُستخدَم لنقل بيانات الصور من جهاز الكاميرا إلى التطبيق.

Kotlin

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)
    }
    ...
}

Java

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() لفئة المعاينة.

Kotlin

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()
    }
}

Java

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();
    }
}

تعديل إعدادات الكاميرا

تغيّر إعدادات الكاميرا طريقة التقاط الصور، بدءًا من مستوى التكبير/التصغير إلى تعويض درجة الإضاءة. يغيّر هذا المثال حجم المعاينة فقط، ويمكنك الاطّلاع على الرمز المصدري لتطبيق "الكاميرا" للحصول على المزيد.

Kotlin

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()
    }
}

Java

@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 التي تسبق مستوى واجهة برمجة التطبيقات 14، عليك إيقاف المعاينة قبل تغيير اتجاه الشاشة ثم إعادة تشغيلها.

التقاط صورة

استخدِم طريقة Camera.takePicture() لالتقاط صورة بعد بدء المعاينة. يمكنك إنشاء العنصرَين Camera.PictureCallback وCamera.ShutterCallback وتمريرهما إلى Camera.takePicture().

إذا أردت الحصول على الصور بشكل مستمر، يمكنك إنشاء Camera.PreviewCallback ينفّذ onPreviewFrame(). للحصول على شيء بينهما، يمكنك التقاط لقطات معاينة محددة فقط، أو إعداد إجراء مؤجّل لاستدعاء takePicture().

إعادة تشغيل "برنامج المعاينة"

بعد التقاط صورة، عليك إعادة تشغيل المعاينة قبل أن يتمكّن المستخدم من التقاط صورة أخرى. في هذا المثال، تتم إعادة التشغيل من خلال الضغط مع الاستمرار على زر الغالق.

Kotlin

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()
}

Java

@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.

Kotlin

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
    }
}

Java

@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()، لذا يبدأ تهيئة الكاميرا دائمًا بإيقاف المعاينة.