Camera API

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

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

שיקולים

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

  • דרישת מצלמה – האם השימוש במצלמה כל כך חשוב לאפליקציה שלכם, שאתם לא רוצים שהיא תותקן במכשיר שאין בו מצלמה? אם כן, צריך להצהיר על דרישת המצלמה במניפסט.
  • תמונה מהירה או מצלמה בהתאמה אישית – איך האפליקציה תשתמש במצלמה? האם אתם רוצים רק לצלם תמונה או קטע וידאו קצר, או שהאפליקציה שלכם תספק דרך חדשה להשתמש במצלמות? כדי לצלם תמונה או סרטון קצר, כדאי להשתמש באפליקציות מצלמה קיימות. כדי לפתח תכונה מותאמת אישית של מצלמה, עיינו בקטע פיתוח אפליקציית מצלמה.
  • דרישה לגבי שירותים שפועלים בחזית – מתי האפליקציה מקיימת אינטראקציה עם המצלמה? ב-Android 9 (רמת API‏ 28) ואילך, אפליקציות שפועלות ברקע לא יכולות לגשת למצלמה. לכן, צריך להשתמש במצלמה כשהאפליקציה פועלת בחזית או כחלק משירות שפועל בחזית.
  • אחסון – האם התמונות או הסרטונים שהאפליקציה יוצרת אמורים להיות גלויים רק לאפליקציה שלכם, או שהם משותפים כך שאפליקציות אחרות כמו גלריה או אפליקציות מדיה ויישומים חברתיים אחרים יכולות להשתמש בהם? האם אתם רוצים שהתמונות והסרטונים יהיו זמינים גם אם האפליקציה תוסר? בקטע שמירת קובצי מדיה מוסבר איך להטמיע את האפשרויות האלה.

העקרונות הבסיסיים

מסגרת Android תומכת בצילום תמונות וסרטונים באמצעות android.hardware.camera2 API או Intent של המצלמה. אלה המחלקות הרלוונטיות:

android.hardware.camera2
החבילה הזו היא ה-API הראשי לשליטה במצלמות של המכשיר. אפשר להשתמש בו כדי לצלם תמונות או סרטונים כשמפתחים אפליקציית מצלמה.
Camera
המחלקה הזו היא ה-API הישן שהוצא משימוש לשליטה במצלמות של המכשיר.
SurfaceView
המחלקה הזו משמשת להצגת תצוגה מקדימה של מצלמה בשידור חי למשתמש.
MediaRecorder
המחלקה הזו משמשת להקלטת סרטון מהמצלמה.
Intent
אפשר להשתמש בסוג פעולת Intent של MediaStore.ACTION_IMAGE_CAPTURE או MediaStore.ACTION_VIDEO_CAPTURE כדי לצלם תמונות או סרטונים בלי להשתמש ישירות באובייקט Camera.

הצהרות במניפסט

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

  • הרשאת גישה למצלמה – האפליקציה צריכה לבקש הרשאה להשתמש במצלמת המכשיר.
    <uses-permission android:name="android.permission.CAMERA" />

    הערה: אם אתם משתמשים במצלמה באמצעות הפעלה של אפליקציית מצלמה קיימת, האפליקציה שלכם לא צריכה לבקש את ההרשאה הזו.

  • תכונות המצלמה – האפליקציה צריכה גם להצהיר על שימוש בתכונות המצלמה, לדוגמה:
    <uses-feature android:name="android.hardware.camera" />

    רשימה של תכונות המצלמה מופיעה בחומר העזר בנושא תכונות במניפסט.

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

    אם האפליקציה שלכם יכולה להשתמש במצלמה או בתכונה של המצלמה כדי לפעול כמו שצריך, אבל היא לא דורשת את זה, אתם צריכים לציין את זה במניפסט על ידי הכללת המאפיין android:required והגדרתו ל-false:

    <uses-feature android:name="android.hardware.camera" android:required="false" />
  • הרשאת אחסון – האפליקציה יכולה לשמור תמונות או סרטונים באחסון החיצוני של המכשיר (כרטיס SD) אם היא מטרגטת את Android 10 (רמת API‏ 29) או גרסה ישנה יותר, ומציינת את הפרטים הבאים במניפסט.
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • הרשאה להקלטת אודיו – כדי להקליט אודיו עם צילום וידאו, האפליקציה צריכה לבקש הרשאה לצילום אודיו.
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
  • הרשאת מיקום – אם האפליקציה מתייגת תמונות עם נתוני מיקום GPS, צריך לבקש את ההרשאה ACCESS_FINE_LOCATION. שימו לב: אם האפליקציה מטרגטת ל-Android 5.0 (רמת API ‏21) ומעלה, צריך גם להצהיר שהאפליקציה משתמשת ב-GPS של המכשיר:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
    <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
    <uses-feature android:name="android.hardware.location.gps" />

    מידע נוסף על קבלת מיקום המשתמש זמין במאמר אסטרטגיות מיקום.

שימוש באפליקציות מצלמה קיימות

דרך מהירה לאפשר צילום תמונות או סרטונים באפליקציה בלי הרבה קוד נוסף היא להשתמש ב-Intent כדי להפעיל אפליקציית מצלמה קיימת ל-Android. הפרטים מתוארים בשיעורי ההדרכה צילום תמונות פשוט וצילום סרטונים פשוט.

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

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

הערה: המדריך הבא מיועד לגרסה הישנה של Camera API, שהוצאה משימוש. לאפליקציות מצלמה חדשות או מתקדמות, מומלץ להשתמש ב-API החדש יותר, android.hardware.camera2.

אלה השלבים הכלליים ליצירת ממשק מצלמה בהתאמה אישית לאפליקציה:

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

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

זהירות: אל תשכחו לשחרר את האובייקט Camera באמצעות קריאה ל-Camera.release() כשהאפליקציה מסיימת להשתמש בו. אם האפליקציה לא משחררת את המצלמה כמו שצריך, כל הניסיונות הבאים לגשת למצלמה, כולל אלה של האפליקציה שלכם, ייכשלו ועלולים לגרום להשבתה של האפליקציה שלכם או של אפליקציות אחרות.

מתבצע זיהוי של חומרת המצלמה

אם האפליקציה לא דורשת מצלמה באופן ספציפי באמצעות הצהרת מניפסט, צריך לבדוק אם יש מצלמה זמינה בזמן הריצה. כדי לבצע את הבדיקה הזו, משתמשים ב-method‏ PackageManager.hasSystemFeature(), כמו שמוצג בקוד לדוגמה שבהמשך:

Kotlin

/** Check if this device has a camera */
private fun checkCameraHardware(context: Context): Boolean {
    if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
        // this device has a camera
        return true
    } else {
        // no camera on this device
        return false
    }
}

Java

/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}

במכשירי Android יכולות להיות כמה מצלמות, למשל מצלמה אחורית לצילום ומצלמה קדמית לשיחות וידאו. ב-Android מגרסה 2.3 (רמת API‏ 9) ואילך אפשר לבדוק את מספר המצלמות שזמינות במכשיר באמצעות השיטה Camera.getNumberOfCameras().

גישה למצלמות

אם וידאתם שבמכשיר שבו האפליקציה פועלת יש מצלמה, אתם צריכים לבקש גישה אליה על ידי קבלת מופע של Camera (אלא אם אתם משתמשים בIntent כדי לגשת למצלמה).

כדי לגשת למצלמה הראשית, משתמשים ב-method‏ Camera.open() ומוודאים שכל החריגות נתפסות, כמו שמוצג בקוד הבא:

Kotlin

/** A safe way to get an instance of the Camera object. */
fun getCameraInstance(): Camera? {
    return try {
        Camera.open() // attempt to get a Camera instance
    } catch (e: Exception) {
        // Camera is not available (in use or does not exist)
        null // returns null if camera is unavailable
    }
}

Java

/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

זהירות: כשמשתמשים ב-Camera.open(), תמיד צריך לבדוק אם יש חריגים. אם לא תבדקו אם יש חריגים כשהמצלמה בשימוש או לא קיימת, המערכת תסגור את האפליקציה.

במכשירים עם Android מגרסה 2.3 (רמת API‏ 9) ומעלה, אפשר לגשת למצלמות ספציפיות באמצעות Camera.open(int). קוד הדוגמה שלמעלה ייגש למצלמה האחורית הראשונה במכשיר עם יותר ממצלמה אחת.

בדיקת התכונות של המצלמה

אחרי שמקבלים גישה למצלמה, אפשר לקבל מידע נוסף על היכולות שלה באמצעות השיטה Camera.getParameters() ובדיקת האובייקט Camera.Parameters שמוחזר כדי לראות אילו יכולות נתמכות. כשמשתמשים ב-API ברמה 9 ומעלה, משתמשים ב-Camera.getCameraInfo() כדי לקבוע אם המצלמה נמצאת בחלק הקדמי או האחורי של המכשיר, ואת כיוון התמונה.

יצירת כיתה בגרסת טרום-השקה (Preview)

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

הקוד לדוגמה הבא מדגים איך ליצור מחלקה בסיסית של תצוגה מקדימה של מצלמה שאפשר לכלול בפריסה של View. המחלקה הזו מטמיעה את SurfaceHolder.Callback כדי לתעד את אירועי הקריאה החוזרת (callback) ליצירה ולהשמדה של התצוגה, שנדרשים להקצאת הקלט של התצוגה המקדימה של המצלמה.

Kotlin

/** A basic Camera preview class */
class CameraPreview(
        context: Context,
        private val mCamera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val mHolder: SurfaceHolder = holder.apply {
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        addCallback(this@CameraPreview)
        // deprecated setting, but required on Android versions prior to 3.0
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        mCamera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "Error setting camera preview: ${e.message}")
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.surface == null) {
            // preview surface does not exist
            return
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview()
        } catch (e: Exception) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        mCamera.apply {
            try {
                setPreviewDisplay(mHolder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "Error starting camera preview: ${e.message}")
            }
        }
    }
}

Java

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

אם רוצים להגדיר גודל ספציפי לתצוגה המקדימה של המצלמה, צריך להגדיר אותו ב-method‏ surfaceChanged(), כמו שצוין בהערות שלמעלה. כשמגדירים את גודל התצוגה המקדימה, חובה להשתמש בערכים מ-getSupportedPreviewSizes(). אל תגדירו ערכים שרירותיים ב-method‏ setPreviewSize().

הערה: עם השקת התכונה ריבוי חלונות ב-Android 7.0 (רמת API‏ 24) ומעלה, אי אפשר יותר להניח שיחס הגובה-רוחב של התצוגה המקדימה זהה לזה של הפעילות, גם אחרי קריאה ל-setDisplayOrientation(). בהתאם לגודל החלון וליחס הגובה-רוחב, יכול להיות שתצטרכו להתאים תצוגה מקדימה רחבה של המצלמה לפריסה לאורך, או להיפך, באמצעות פריסת Letterbox.

הצבת תצוגה מקדימה בפריסה

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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    />

  <Button
    android:id="@+id/button_capture"
    android:text="Capture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    />
</LinearLayout>

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

<activity android:name=".CameraActivity"
          android:label="@string/app_name"

          android:screenOrientation="landscape">
          <!-- configure this activity to use landscape orientation -->

          <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

הערה: התצוגה המקדימה של המצלמה לא חייבת להיות בפריסה לרוחב. החל מ-Android 2.2 (רמת API‏ 8), אפשר להשתמש ב-method‏ setDisplayOrientation() כדי להגדיר את סיבוב התמונה בתצוגה המקדימה. כדי לשנות את כיוון התצוגה המקדימה כשהמשתמש משנה את כיוון הטלפון, בתוך השיטה surfaceChanged() של מחלקת התצוגה המקדימה, קודם צריך לעצור את התצוגה המקדימה באמצעות Camera.stopPreview(), לשנות את הכיוון ואז להפעיל שוב את התצוגה המקדימה באמצעות Camera.startPreview().

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

Kotlin

class CameraActivity : Activity() {

    private var mCamera: Camera? = null
    private var mPreview: CameraPreview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create an instance of Camera
        mCamera = getCameraInstance()

        mPreview = mCamera?.let {
            // Create our Preview view
            CameraPreview(this, it)
        }

        // Set the Preview view as the content of our activity.
        mPreview?.also {
            val preview: FrameLayout = findViewById(R.id.camera_preview)
            preview.addView(it)
        }
    }
}

Java

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Create an instance of Camera
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }
}

הערה: השיטה getCameraInstance() בדוגמה שלמעלה מתייחסת לשיטה לדוגמה שמוצגת בקטע גישה למצלמות.

צילום תמונות

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

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

Kotlin

private val mPicture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG, ("Error creating media file, check storage permissions"))
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "File not found: ${e.message}")
    } catch (e: IOException) {
        Log.d(TAG, "Error accessing file: ${e.message}")
    }
}

Java

private PictureCallback mPicture = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions");
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
    }
};

מפעילים צילום תמונה באמצעות קריאה ל-method‏ Camera.takePicture(). בדוגמת הקוד הבאה אפשר לראות איך להפעיל את השיטה הזו מלחצן View.OnClickListener.

Kotlin

val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    // get an image from the camera
    mCamera?.takePicture(null, null, picture)
}

Java

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // get an image from the camera
            mCamera.takePicture(null, null, picture);
        }
    }
);

הערה: האיבר mPicture בדוגמה הבאה מתייחס לקוד לדוגמה שלמעלה.

זהירות: אל תשכחו לשחרר את האובייקט Camera באמצעות קריאה ל-Camera.release() כשהאפליקציה מסיימת להשתמש בו. במאמר שחרור המצלמה מוסבר איך לשחרר את המצלמה.

צילום סרטונים

כדי לצלם וידאו באמצעות ה-framework של Android, צריך לנהל בקפידה את האובייקט Camera ולתאם עם המחלקה MediaRecorder. כשמקליטים וידאו באמצעות Camera, צריך לנהל את הקריאות ל-Camera.lock() ול-Camera.unlock() כדי לאפשר ל-MediaRecorder לגשת לחומרת המצלמה, בנוסף לקריאות ל-Camera.open() ול-Camera.release().

הערה: החל מ-Android 4.0 (רמת API‏ 14), הקריאות ל-Camera.lock() ול-Camera.unlock() מנוהלות בשבילכם באופן אוטומטי.

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

  1. פתיחת המצלמה – משתמשים ב-Camera.open() כדי לקבל מופע של אובייקט המצלמה.
  2. תצוגה מקדימה של החיבור – מכינים תצוגה מקדימה של תמונה ממצלמה בשידור חי על ידי חיבור SurfaceView למצלמה באמצעות Camera.setPreviewDisplay().
  3. התחלת התצוגה המקדימה – קריאה ל-Camera.startPreview() כדי להתחיל להציג את התמונות מהמצלמה בשידור חי.
  4. התחלת צילום הסרטון – כדי לצלם את הסרטון כמו שצריך, צריך לבצע את השלבים הבאים לפי הסדר:
    1. ביטול הנעילה של המצלמה – כדי לבטל את הנעילה של המצלמה לשימוש של MediaRecorder, צריך להתקשר אל Camera.unlock().
    2. הגדרת MediaRecorder – קוראים לMediaRecorder השיטות הבאות בסדר הזה. למידע נוסף, תוכלו לקרוא את מאמרי העזרה של MediaRecorder.
      1. ‫setCamera() – כדי להגדיר את המצלמה שבה רוצים להשתמש לצילום וידאו, משתמשים במופע הנוכחי של Camera באפליקציה.
      2. ‫setAudioSource() – הגדרת מקור האודיו, באמצעות MediaRecorder.AudioSource.CAMCORDER.
      3. setVideoSource() – כדי להגדיר את מקור הסרטון, משתמשים ב-MediaRecorder.VideoSource.CAMERA.
      4. מגדירים את הפורמט והקידוד של פלט הווידאו. ב-Android 2.2 (רמת API‏ 8) ומעלה, משתמשים ב-method‏ MediaRecorder.setProfile ומקבלים מופע של פרופיל באמצעות CamcorderProfile.get(). בגרסאות Android שקודמות ל-2.2, צריך להגדיר את פורמט הפלט של הווידאו ואת פרמטרי הקידוד:
        1. ‫setOutputFormat() – הגדרת פורמט הפלט, ציון הגדרת ברירת המחדל או MediaRecorder.OutputFormat.MPEG_4.
        2. ‫setAudioEncoder() – הגדרת סוג קידוד הצליל, ציון הגדרת ברירת המחדל או MediaRecorder.AudioEncoder.AMR_NB.
        3. setVideoEncoder() – הגדרת סוג קידוד הווידאו, ציון הגדרת ברירת המחדל או MediaRecorder.VideoEncoder.MPEG_4_SP.
      5. ‫setOutputFile() – הגדרת קובץ הפלט, באמצעות getOutputMediaFile(MEDIA_TYPE_VIDEO).toString() מהשיטה לדוגמה בקטע שמירת קובצי מדיה.
      6. ‫setPreviewDisplay() – מציינים את רכיב הפריסה של התצוגה המקדימה של SurfaceView באפליקציה. משתמשים באותו אובייקט שציינתם ב-Connect Preview.

      זהירות: צריך להפעיל את MediaRecorder שיטות ההגדרה האלה בסדר הזה, אחרת תהיה שגיאה באפליקציה וההקלטה תיכשל.

    3. הכנת MediaRecorder – מכינים את MediaRecorder עם הגדרות אישיות שסופקו על ידי קריאה ל-MediaRecorder.prepare().
    4. הפעלת MediaRecorder – הפעלת הקלטת וידאו באמצעות קריאה ל-MediaRecorder.start().
  5. הפסקת הקלטת הסרטון – כדי להשלים בהצלחה את הקלטת הסרטון, צריך להפעיל את השיטות הבאות לפי הסדר:
    1. עצירת MediaRecorder – הפסקת הקלטת הסרטון באמצעות קריאה ל-MediaRecorder.stop().
    2. איפוס MediaRecorder – אם רוצים, אפשר להסיר את הגדרות התצורה מהמקליט באמצעות קריאה ל-MediaRecorder.reset().
    3. שחרור MediaRecorder – משחררים את MediaRecorder על ידי קריאה ל-MediaRecorder.release().
    4. נעילת המצלמה – נועלים את המצלמה כדי שסשנים עתידיים של MediaRecorder יוכלו להשתמש בה באמצעות קריאה ל-Camera.lock(). החל מ-Android 4.0 (רמת API‏ 14), הקריאה הזו לא נדרשת, אלא אם הקריאה ל-MediaRecorder.prepare() נכשלת.
  6. הפסקת התצוגה המקדימה – כשהפעילות מסתיימת, מפסיקים את השימוש במצלמה באמצעות Camera.stopPreview().
  7. שחרור המצלמה – שחרור המצלמה כדי שאפליקציות אחרות יוכלו להשתמש בה באמצעות קריאה ל-Camera.release().

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

טיפ: אם האפליקציה שלכם משמשת בדרך כלל להקלטת וידאו, הגדירו את setRecordingHint(boolean) ל-true לפני שתתחילו את התצוגה המקדימה. ההגדרה הזו יכולה לעזור לצמצם את הזמן שלוקח להתחיל את ההקלטה.

הגדרת MediaRecorder

כשמשתמשים במחלקה MediaRecorder כדי להקליט וידאו, צריך לבצע את שלבי ההגדרה בסדר ספציפי ואז להפעיל את ה-method‏ MediaRecorder.prepare() כדי לבדוק ולהטמיע את ההגדרה. הקוד לדוגמה הבא ממחיש איך להגדיר ולהכין כראוי את המחלקה MediaRecorder להקלטת וידאו.

Kotlin

private fun prepareVideoRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    mCamera?.let { camera ->
        // Step 1: Unlock and set camera to MediaRecorder
        camera?.unlock()

        mediaRecorder?.run {
            setCamera(camera)

            // Step 2: Set sources
            setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
            setVideoSource(MediaRecorder.VideoSource.CAMERA)

            // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
            setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

            // Step 4: Set output file
            setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

            // Step 5: Set the preview output
            setPreviewDisplay(mPreview?.holder?.surface)

            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
            setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)


            // Step 6: Prepare configured MediaRecorder
            return try {
                prepare()
                true
            } catch (e: IllegalStateException) {
                Log.d(TAG, "IllegalStateException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            } catch (e: IOException) {
                Log.d(TAG, "IOException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            }
        }

    }
    return false
}

Java

private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}

בגרסאות Android שקדמו ל-Android 2.2 (רמת API‏ 8), צריך להגדיר את הפרמטרים של פורמט הפלט ופורמט הקידוד באופן ישיר, במקום להשתמש ב-CamcorderProfile. הגישה הזו מודגמת בקוד הבא:

Kotlin

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder?.apply {
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
    }

Java

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

הפרמטרים הבאים של הקלטת וידאו ב-MediaRecorder מוגדרים כברירת מחדל, אבל יכול להיות שתרצו לשנות את ההגדרות האלה באפליקציה שלכם:

הפעלה והפסקה של MediaRecorder

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

  1. ביטול נעילת המצלמה באמצעות Camera.unlock()
  2. מגדירים את MediaRecorder כמו בדוגמת הקוד שלמעלה
  3. התחלת הקלטה באמצעות MediaRecorder.start()
  4. מצלמים את הסרטון
  5. הפסקת ההקלטה באמצעות MediaRecorder.stop()
  6. שחרור מקליט המדיה באמצעות MediaRecorder.release()
  7. נעילת המצלמה באמצעות Camera.lock()

הקוד לדוגמה הבא מדגים איך לחבר כפתור כדי להתחיל ולהפסיק את הקלטת הווידאו באמצעות המצלמה והמחלקה MediaRecorder.

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

Kotlin

var isRecording = false
val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    if (isRecording) {
        // stop recording and release camera
        mediaRecorder?.stop() // stop the recording
        releaseMediaRecorder() // release the MediaRecorder object
        mCamera?.lock() // take camera access back from MediaRecorder

        // inform the user that recording has stopped
        setCaptureButtonText("Capture")
        isRecording = false
    } else {
        // initialize video camera
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared,
            // now you can start recording
            mediaRecorder?.start()

            // inform the user that recording has started
            setCaptureButtonText("Stop")
            isRecording = true
        } else {
            // prepare didn't work, release the camera
            releaseMediaRecorder()
            // inform user
        }
    }
}

Java

private boolean isRecording = false;

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isRecording) {
                // stop recording and release camera
                mediaRecorder.stop();  // stop the recording
                releaseMediaRecorder(); // release the MediaRecorder object
                mCamera.lock();         // take camera access back from MediaRecorder

                // inform the user that recording has stopped
                setCaptureButtonText("Capture");
                isRecording = false;
            } else {
                // initialize video camera
                if (prepareVideoRecorder()) {
                    // Camera is available and unlocked, MediaRecorder is prepared,
                    // now you can start recording
                    mediaRecorder.start();

                    // inform the user that recording has started
                    setCaptureButtonText("Stop");
                    isRecording = true;
                } else {
                    // prepare didn't work, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);

הערה: בדוגמה שלמעלה, השיטה prepareVideoRecorder() מתייחסת לקוד לדוגמה שמוצג בקטע הגדרת MediaRecorder. השיטה הזו נועלת את המצלמה, מגדירה ומכינה את המכונה של MediaRecorder.

המצלמה משתחררת

מצלמות הן משאב שמשותף לאפליקציות במכשיר. האפליקציה יכולה להשתמש במצלמה אחרי שהיא מקבלת מופע של Camera. חשוב מאוד לשחרר את אובייקט המצלמה כשהאפליקציה מפסיקה להשתמש בו, וברגע שהאפליקציה מושהית (Activity.onPause()). אם האפליקציה לא משחררת את המצלמה כמו שצריך, כל הניסיונות הבאים לגשת למצלמה, כולל ניסיונות של האפליקציה עצמה, ייכשלו ועלולים לגרום להשבתה של האפליקציה או של אפליקציות אחרות.

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

Kotlin

class CameraActivity : Activity() {
    private var mCamera: Camera?
    private var preview: SurfaceView?
    private var mediaRecorder: MediaRecorder?

    override fun onPause() {
        super.onPause()
        releaseMediaRecorder() // if you are using MediaRecorder, release it first
        releaseCamera() // release the camera immediately on pause event
    }

    private fun releaseMediaRecorder() {
        mediaRecorder?.reset() // clear recorder configuration
        mediaRecorder?.release() // release the recorder object
        mediaRecorder = null
        mCamera?.lock() // lock camera for later use
    }

    private fun releaseCamera() {
        mCamera?.release() // release the camera for other applications
        mCamera = null
    }
}

Java

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView preview;
    private MediaRecorder mediaRecorder;

    ...

    @Override
    protected void onPause() {
        super.onPause();
        releaseMediaRecorder();       // if you are using MediaRecorder, release it first
        releaseCamera();              // release the camera immediately on pause event
    }

    private void releaseMediaRecorder(){
        if (mediaRecorder != null) {
            mediaRecorder.reset();   // clear recorder configuration
            mediaRecorder.release(); // release the recorder object
            mediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

    private void releaseCamera(){
        if (mCamera != null){
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }
}

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

שמירת קובצי מדיה

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

  • ‫Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) – השיטה הזו מחזירה את המיקום הרגיל, המשותף והמומלץ לשמירת תמונות וסרטונים. הספרייה הזו משותפת (ציבורית), כך שאפליקציות אחרות יכולות למצוא, לקרוא, לשנות ולמחוק בקלות קבצים שנשמרו במיקום הזה. אם המשתמש מסיר את האפליקציה, קובצי המדיה שנשמרו במיקום הזה לא יוסרו. כדי לא להפריע לתמונות ולסרטונים הקיימים של המשתמשים, צריך ליצור ספריית משנה לקובצי המדיה של האפליקציה בתוך הספרייה הזו, כמו שמוצג בדוגמת הקוד שבהמשך. השיטה הזו זמינה ב-Android 2.2 (רמת API‏ 8). לקריאות מקבילות בגרסאות API קודמות, ראו שמירת קבצים משותפים.
  • ‫Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) – השיטה הזו מחזירה מיקום סטנדרטי לשמירת תמונות וסרטונים שמשויכים לאפליקציה. אם מסירים את האפליקציה, כל הקבצים שנשמרו במיקום הזה נמחקים. האבטחה לא נאכפת בקבצים במיקום הזה, ואפליקציות אחרות יכולות לקרוא, לשנות ולמחוק אותם.

הקוד לדוגמה הבא מדגים איך ליצור מיקום File או Uri לקובץ מדיה שאפשר להשתמש בו כשמפעילים את המצלמה של המכשיר באמצעות Intent או כחלק מפיתוח אפליקציית מצלמה.

Kotlin

val MEDIA_TYPE_IMAGE = 1
val MEDIA_TYPE_VIDEO = 2

/** Create a file Uri for saving an image or video */
private fun getOutputMediaFileUri(type: Int): Uri {
    return Uri.fromFile(getOutputMediaFile(type))
}

/** Create a File for saving an image or video */
private fun getOutputMediaFile(type: Int): File? {
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    val mediaStorageDir = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            "MyCameraApp"
    )
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    mediaStorageDir.apply {
        if (!exists()) {
            if (!mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory")
                return null
            }
        }
    }

    // Create a media file name
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    return when (type) {
        MEDIA_TYPE_IMAGE -> {
            File("${mediaStorageDir.path}${File.separator}IMG_$timeStamp.jpg")
        }
        MEDIA_TYPE_VIDEO -> {
            File("${mediaStorageDir.path}${File.separator}VID_$timeStamp.mp4")
        }
        else -> null
    }
}

Java

public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;

/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
      return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
              Environment.DIRECTORY_PICTURES), "MyCameraApp");
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            Log.d("MyCameraApp", "failed to create directory");
            return null;
        }
    }

    // Create a media file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile;
    if (type == MEDIA_TYPE_IMAGE){
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "IMG_"+ timeStamp + ".jpg");
    } else if(type == MEDIA_TYPE_VIDEO) {
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "VID_"+ timeStamp + ".mp4");
    } else {
        return null;
    }

    return mediaFile;
}

הערה: Environment.getExternalStoragePublicDirectory() זמין ב-Android 2.2 (רמת API‏ 8) ומעלה. אם אתם מטרגטים מכשירים עם גרסאות קודמות של Android, השתמשו במקום זאת ב-Environment.getExternalStorageDirectory(). מידע נוסף זמין במאמר בנושא שמירת קבצים משותפים.

כדי שה-URI יתמוך בפרופילי עבודה, קודם צריך להמיר את ה-URI של הקובץ ל-URI של תוכן. לאחר מכן, מוסיפים את ה-URI של התוכן אל EXTRA_OUTPUT של Intent.

מידע נוסף על שמירת קבצים במכשיר Android זמין במאמר אחסון נתונים.

תכונות מצלמה

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

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

טבלה 1. תכונות נפוצות של מצלמות, ממוינות לפי רמת ה-API ב-Android שבה הן נוספו.

תכונה רמת API תיאור
זיהוי פנים 14 זיהוי פנים של אנשים בתמונה ושימוש בהם למיקוד, למדידה ולאיזון לבן
אזורי מדידה 14 צריך לציין אזור אחד או יותר בתוך תמונה כדי לחשב את איזון הצבע הלבן
נושאים שכדאי להתמקד בהם 14 הגדרת אזור אחד או יותר בתמונה שבהם רוצים להתמקד
White Balance Lock 14 איך להפסיק או להפעיל את הכוונון האוטומטי של איזון הלבן
Exposure Lock 14 איך להפסיק או להפעיל את ההתאמות האוטומטיות של החשיפה
Video Snapshot 14 צילום תמונה בזמן צילום סרטון (צילום פריים)
סרטון Time Lapse 11 הקלטת פריימים עם השהיות מוגדרות כדי להקליט סרטון Time Lapse
Multiple Cameras 9 תמיכה ביותר ממצלמה אחת במכשיר, כולל מצלמה קדמית ומצלמה אחורית
Focus Distance 9 מדווח על המרחקים בין המצלמה לבין אובייקטים שנראים בפוקוס
Zoom 8 הגדרת הגדלת התמונה
Exposure Compensation 8 הגברה או הפחתה של רמת החשיפה לאור
GPS Data 5 הוספה או השמטה של נתוני מיקום גיאוגרפי בתמונה
White Balance 5 הגדרת מצב איזון הלבן, שמשפיע על ערכי הצבע בתמונה שצולמה
Focus Mode 5 הגדרת אופן המיקוד של המצלמה על אובייקט, למשל אוטומטי, קבוע, מאקרו או אינסוף
Scene Mode 5 החלת מצב מוגדר מראש לסוגים ספציפיים של מצבי צילום, כמו סצנות לילה, חוף, שלג או אור נרות
JPEG Quality 5 הגדרת רמת הדחיסה של תמונת JPEG, שמגדילה או מקטינה את האיכות והגודל של קובץ הפלט של התמונה
Flash Mode 5 להפעיל או להשבית את הפלאש, או להשתמש בהגדרה האוטומטית
Color Effects 5 הוספת אפקט צבע לתמונה שצולמה, כמו שחור-לבן, גוון ספיה או נגטיב.
Anti-Banding 5 מפחית את ההשפעה של פסים בטווח הצבעים שנוצרים בגלל דחיסת JPEG
Picture Format 1 מציינים את פורמט הקובץ של התמונה
Picture Size 1 מציינים את מידות הפיקסלים של התמונה השמורה

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

בדיקת הזמינות של התכונות

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

כדי לבדוק את הזמינות של תכונות המצלמה, אפשר לקבל מופע של אובייקט הפרמטרים של המצלמה ולבדוק את ה-methods הרלוונטיות. דוגמת הקוד הבאה מראה איך לקבל אובייקט Camera.Parameters ולבדוק אם המצלמה תומכת בתכונה של מיקוד אוטומטי:

Kotlin

val params: Camera.Parameters? = camera?.parameters
val focusModes: List<String>? = params?.supportedFocusModes
if (focusModes?.contains(Camera.Parameters.FOCUS_MODE_AUTO) == true) {
    // Autofocus mode is supported
}

Java

// get Camera parameters
Camera.Parameters params = camera.getParameters();

List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
  // Autofocus mode is supported
}

אתם יכולים להשתמש בשיטה שמוצגת למעלה ברוב התכונות של המצלמה. האובייקט Camera.Parameters מספק את ה-method‏ getSupported...(), ‏ is...Supported() או getMax...() כדי לקבוע אם (ובאיזו מידה) יש תמיכה בתכונה.

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

שימוש בתכונות המצלמה

רוב התכונות של המצלמה מופעלות ונשלטות באמצעות אובייקט Camera.Parameters. כדי לקבל את האובייקט הזה, קודם צריך לקבל מופע של האובייקט Camera, לשלוח קריאה ל-method‏ getParameters(), לשנות את אובייקט הפרמטר שמוחזר ואז להגדיר אותו בחזרה באובייקט המצלמה, כמו שמוצג בקוד לדוגמה הבא:

Kotlin

val params: Camera.Parameters? = camera?.parameters
params?.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
camera?.parameters = params

Java

// get Camera parameters
Camera.Parameters params = camera.getParameters();
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
camera.setParameters(params);

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

חשוב: אי אפשר לשנות חלק מהתכונות של המצלמה. למשל, כדי לשנות את הגודל או הכיוון של התצוגה המקדימה של המצלמה, צריך קודם לעצור את התצוגה המקדימה, לשנות את הגודל שלה ואז להפעיל אותה מחדש. החל מ-Android 4.0 (רמת API‏ 14), אפשר לשנות את כיוון התצוגה המקדימה בלי להפעיל אותה מחדש.

כדי להטמיע תכונות אחרות של המצלמה, צריך להוסיף עוד קוד, כולל:

  • מדידת אור ואזורי מיקוד
  • איתור פנים
  • סרטון Time Lapse

בקטעים הבאים מופיע תיאור קצר של אופן ההטמעה של התכונות האלה.

מדידת אור ואזורי מיקוד

במצבי צילום מסוימים, יכול להיות שהמיקוד האוטומטי ומדידת האור לא יפיקו את התוצאות הרצויות. החל מ-Android 4.0 (רמת API ‏14), אפליקציית המצלמה יכולה לספק אמצעי בקרה נוספים שמאפשרים לאפליקציה או למשתמשים לציין אזורים בתמונה שבהם ייקבעו הגדרות המיקוד או רמת האור, ולהעביר את הערכים האלה לחומרת המצלמה כדי להשתמש בהם בצילום תמונות או סרטונים.

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

Kotlin

// Create an instance of Camera
camera = getCameraInstance()

// set Camera parameters
val params: Camera.Parameters? = camera?.parameters

params?.apply {
    if (maxNumMeteringAreas > 0) { // check that metering areas are supported
        meteringAreas = ArrayList<Camera.Area>().apply {
            val areaRect1 = Rect(-100, -100, 100, 100) // specify an area in center of image
            add(Camera.Area(areaRect1, 600)) // set weight to 60%
            val areaRect2 = Rect(800, -1000, 1000, -800) // specify an area in upper right of image
            add(Camera.Area(areaRect2, 400)) // set weight to 40%
        }
    }
    camera?.parameters = this
}

Java

// Create an instance of Camera
camera = getCameraInstance();

// set Camera parameters
Camera.Parameters params = camera.getParameters();

if (params.getMaxNumMeteringAreas() > 0){ // check that metering areas are supported
    List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();

    Rect areaRect1 = new Rect(-100, -100, 100, 100);    // specify an area in center of image
    meteringAreas.add(new Camera.Area(areaRect1, 600)); // set weight to 60%
    Rect areaRect2 = new Rect(800, -1000, 1000, -800);  // specify an area in upper right of image
    meteringAreas.add(new Camera.Area(areaRect2, 400)); // set weight to 40%
    params.setMeteringAreas(meteringAreas);
}

camera.setParameters(params);

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

השדה Rect באובייקט Camera.Area מתאר צורה מלבנית שממופה ברשת של 2,000 על 2,000 יחידות. הקואורדינטות ‎-1000, -1000 מייצגות את הפינה השמאלית העליונה של התמונה במצלמה, והקואורדינטות 1000, 1000 מייצגות את הפינה הימנית התחתונה של התמונה במצלמה, כמו שמוצג באיור הבא.

איור 1. הקווים האדומים ממחישים את מערכת הקואורדינטות לציון Camera.Area בתצוגה המקדימה של המצלמה. התיבה הכחולה מציגה את המיקום והצורה של אזור המצלמה עם הערכים 333,333,667,667 של Rect.

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

איתור פנים

בתמונות שכוללות אנשים, הפנים הם בדרך כלל החלק הכי חשוב בתמונה, וצריך להשתמש בהם כדי לקבוע גם את המיקוד וגם את איזון הצבע הלבן כשמצלמים תמונה. ה-framework של Android 4.0 (רמת API‏ 14) מספק ממשקי API לזיהוי פנים ולחישוב הגדרות תמונה באמצעות טכנולוגיה לזיהוי פנים.

הערה: כשתכונת זיהוי הפנים פועלת, אין השפעה על setWhiteBalance(String),‏ setFocusAreas(List<Camera.Area>) ו-setMeteringAreas(List<Camera.Area>).

כדי להשתמש בתכונה "זיהוי פנים" באפליקציית המצלמה, צריך לבצע כמה שלבים כלליים:

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

התכונה "זיהוי פנים" לא נתמכת בכל המכשירים. כדי לבדוק אם התכונה הזו נתמכת, אפשר להתקשר אל getMaxNumDetectedFaces(). דוגמה לבדיקה כזו מוצגת בשיטה לדוגמה startFaceDetection() שבהמשך.

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

Kotlin

internal class MyFaceDetectionListener : Camera.FaceDetectionListener {

    override fun onFaceDetection(faces: Array<Camera.Face>, camera: Camera) {
        if (faces.isNotEmpty()) {
            Log.d("FaceDetection", ("face detected: ${faces.size}" +
                    " Face 1 Location X: ${faces[0].rect.centerX()}" +
                    "Y: ${faces[0].rect.centerY()}"))
        }
    }
}

Java

class MyFaceDetectionListener implements Camera.FaceDetectionListener {

    @Override
    public void onFaceDetection(Face[] faces, Camera camera) {
        if (faces.length > 0){
            Log.d("FaceDetection", "face detected: "+ faces.length +
                    " Face 1 Location X: " + faces[0].rect.centerX() +
                    "Y: " + faces[0].rect.centerY() );
        }
    }
}

אחרי שיוצרים את המחלקה הזו, מגדירים אותה באובייקט Camera של האפליקציה, כמו שמוצג בקוד לדוגמה שבהמשך:

Kotlin

camera?.setFaceDetectionListener(MyFaceDetectionListener())

Java

camera.setFaceDetectionListener(new MyFaceDetectionListener());

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

Kotlin

fun startFaceDetection() {
    // Try starting Face Detection
    val params = mCamera?.parameters
    // start face detection only *after* preview has started

    params?.apply {
        if (maxNumDetectedFaces > 0) {
            // camera supports face detection, so can start it:
            mCamera?.startFaceDetection()
        }
    }
}

Java

public void startFaceDetection(){
    // Try starting Face Detection
    Camera.Parameters params = mCamera.getParameters();

    // start face detection only *after* preview has started
    if (params.getMaxNumDetectedFaces() > 0){
        // camera supports face detection, so can start it:
        mCamera.startFaceDetection();
    }
}

צריך להפעיל את זיהוי הפנים בכל פעם שמפעילים (או מפעילים מחדש) את התצוגה המקדימה של המצלמה. אם אתם משתמשים במחלקת התצוגה המקדימה שמוצגת בקטע יצירת מחלקת תצוגה מקדימה, הוסיפו את השיטה startFaceDetection() גם לשיטה surfaceCreated() וגם לשיטה surfaceChanged() במחלקת התצוגה המקדימה, כמו שמוצג בקוד לדוגמה שבהמשך.

Kotlin

override fun surfaceCreated(holder: SurfaceHolder) {
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // start face detection feature
    } catch (e: IOException) {
        Log.d(TAG, "Error setting camera preview: ${e.message}")
    }
}

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    if (holder.surface == null) {
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null")
        return
    }
    try {
        mCamera.stopPreview()
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: ${e.message}")
    }
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // re-start face detection feature
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: ${e.message}")
    }
}

Java

public void surfaceCreated(SurfaceHolder holder) {
    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // start face detection feature

    } catch (IOException e) {
        Log.d(TAG, "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

    if (holder.getSurface() == null){
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null");
        return;
    }

    try {
        mCamera.stopPreview();

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: " + e.getMessage());
    }

    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // re-start face detection feature

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}

הערה: חשוב לזכור להפעיל את השיטה הזו אחרי שמפעילים את startPreview(). אל תנסו להפעיל את איתור הפנים ב-method‏ onCreate() של הפעילות הראשית באפליקציית המצלמה, כי התצוגה המקדימה לא זמינה בשלב הזה של הפעלת האפליקציה.

סרטון Time Lapse

סרטון Time Lapse מאפשר למשתמשים ליצור קטעי וידאו שמשלבים תמונות שצולמו בהפרש של כמה שניות או דקות. התכונה הזו משתמשת ב-MediaRecorder כדי לצלם את התמונות לרצף של Time Lapse.

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

Kotlin

mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH))
mediaRecorder.setCaptureRate(0.1) // capture a frame every 10 seconds

Java

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH));
...
// Step 5.5: Set the video capture rate to a low number
mediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds

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

הדוגמאות Camera2Video ו-HdrViewfinder ממחישות את השימוש בממשקי ה-API שמופיעים בדף הזה.

שדות של המצלמה שנדרשת להם הרשאה

באפליקציות שפועלות ב-Android 10 (רמת API‏ 29) ומעלה, צריך את ההרשאה CAMERA כדי לגשת לערכים של השדות הבאים שהשיטה getCameraCharacteristics() מחזירה:

  • LENS_POSE_ROTATION
  • LENS_POSE_TRANSLATION
  • LENS_INTRINSIC_CALIBRATION
  • LENS_RADIAL_DISTORTION
  • LENS_POSE_REFERENCE
  • LENS_DISTORTION
  • LENS_INFO_HYPERFOCAL_DISTANCE
  • LENS_INFO_MINIMUM_FOCUS_DISTANCE
  • SENSOR_REFERENCE_ILLUMINANT1
  • SENSOR_REFERENCE_ILLUMINANT2
  • SENSOR_CALIBRATION_TRANSFORM1
  • SENSOR_CALIBRATION_TRANSFORM2
  • SENSOR_COLOR_TRANSFORM1
  • SENSOR_COLOR_TRANSFORM2
  • SENSOR_FORWARD_MATRIX1
  • SENSOR_FORWARD_MATRIX2

עוד קוד לדוגמה

כדי להוריד אפליקציות לדוגמה, עיינו בדוגמה ל-Camera2Basic ובאפליקציה הרשמית לדוגמה של CameraX.