שליטה במצלמה

בשיעור הזה נסביר איך לשלוט בחומרת המצלמה ישירות באמצעות ממשקי ה-API של ה-framework.

הערה: הדף הזה מתייחס למחלקה Camera, שהוצאה משימוש. מומלץ להשתמש ב-CameraX או ב-Camera2 במקרים ספציפיים. גם CameraX וגם Camera2 תומכים ב-Android מגרסה 5.0 (רמת API‏ 21) ואילך.

כדי ללמוד איך לעבור מ-Camera1 ל-CameraX, אפשר לעיין במאמר מעבר מ-Camera1 ל-CameraX. לחלופין, אתם יכולים להשתמש במיומנות מבוססת-סוכן להעברה מ-Camera1 ל-CameraX.

כדי לשלוט ישירות במצלמה של המכשיר נדרש הרבה יותר קוד מאשר כדי לבקש תמונות או סרטונים מאפליקציות מצלמה קיימות. עם זאת, אם אתם רוצים ליצור אפליקציית מצלמה מיוחדת או משהו שמשולב באופן מלא בממשק המשתמש של האפליקציה, השיעור הזה מראה איך לעשות את זה.

מקורות מידע שקשורים לנושא:

פתיחת אובייקט המצלמה

השלב הראשון בתהליך השליטה הישירה במצלמה הוא קבלת מופע של האובייקט Camera. כמו באפליקציית המצלמה של Android, הדרך המומלצת לגשת למצלמה היא לפתוח את Camera בשרשור נפרד שמופעל מ-onCreate(). הגישה הזו מומלצת כי התהליך יכול להימשך זמן מה ולהאט את שרשור ה-UI. בהטמעה בסיסית יותר, אפשר לדחות את פתיחת המצלמה ל-method‏ 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;
    }
}

החל מרמת API‏ 9, מסגרת המצלמה תומכת בכמה מצלמות. אם משתמשים ב-API הקודם וקוראים ל-open() בלי ארגומנט, מקבלים את המצלמה האחורית הראשונה.

יצירת התצוגה המקדימה של המצלמה

כדי לצלם תמונה, בדרך כלל המשתמשים צריכים לראות תצוגה מקדימה של הנושא לפני שהם לוחצים על הצמצם. כדי לעשות את זה, אתם יכולים להשתמש ב-SurfaceView כדי להציג תצוגות מקדימות של מה שחיישן המצלמה קולט.

כיתת תצוגה מקדימה

כדי להציג תצוגה מקדימה, צריך להשתמש במחלקה Preview. כדי להציג את התצוגה המקדימה, צריך להטמיע את הממשק 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() נקרא על ידי ה-method‏ setCamera(), בכל פעם שהמשתמש מבצע פעולה כלשהי כדי לשנות את המצלמה. צריך גם להפעיל מחדש את התצוגה המקדימה בשיטת הקריאה החוזרת (callback) של המחלקה 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 לפני רמת API 14, צריך לעצור את התצוגה המקדימה לפני שמשנים את הכיוון, ואז להפעיל אותה מחדש.

צילום תמונה

משתמשים ב-Camera.takePicture() method כדי לצלם תמונה אחרי שהתצוגה המקדימה מתחילה. אפשר ליצור אובייקטים מסוג Camera.PictureCallback ו-Camera.ShutterCallback ולהעביר אותם אל Camera.takePicture().

אם רוצים לצלם תמונות ברצף, אפשר ליצור Camera.PreviewCallback שמטמיע את onPreviewFrame(). כדי להשיג תוצאה באמצע, אפשר לצלם רק פריימים נבחרים של תצוגה מקדימה, או להגדיר פעולה מושהית לקריאה ל-takePicture().

הפעלה מחדש של גרסת טרום-ההשקה (Preview)

אחרי שצילמתם תמונה, אתם צריכים להפעיל מחדש את התצוגה המקדימה כדי שהמשתמש יוכל לצלם תמונה נוספת. בדוגמה הזו, ההפעלה מחדש מתבצעת על ידי עומס יתר על כפתור הצילום.

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

מוקדם יותר בשיעור, ההליך הזה היה גם חלק מה-method‏ setCamera(), כך שהפעלת מצלמה תמיד מתחילה בעצירת התצוגה המקדימה.