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

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

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

للتعرّف على كيفية نقل البيانات من Camera1 إلى CameraX، يُرجى الاطّلاع على نقل البيانات من Camera1 إلى CameraX. بدلاً من ذلك، يمكنك استخدام مهارة الذكاء الاصطناعي الوكيل لنقل البيانات من Camera1 إلى CameraX.

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

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

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

يُعدّ الحصول على مثيل لكائن 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()، لذا يبدأ إعداد الكاميرا دائمًا بإيقاف المعاينة.