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

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

ملاحظة: تشير هذه الصفحة إلى فئة Camera التي تم إيقافها نهائيًا. ننصح باستخدام 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 لرسم معاينات لما يلتقطه 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()، لذا يبدأ إعداد الكاميرا دائمًا بإيقاف المعاينة.