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

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

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