ממשק API של מצלמה

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

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

שיקולים

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

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

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

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

android.hardware.camera2
החבילה הזו היא ממשק ה-API הראשי לניהול המצלמות במכשיר. אפשר להשתמש בו כדי לצלם תמונות או סרטונים בזמן פיתוח אפליקציית מצלמה.
Camera
המחלקה הזו היא ה-API הישן יותר שהוצא משימוש לשליטה במצלמות של מכשירים.
SurfaceView
הקלאס הזה משמש להצגת תצוגה מקדימה של המצלמה בשידור חי למשתמש.
MediaRecorder
הקלאס הזה משמש לצילום סרטון מהמצלמה.
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. הפרטים מפורטים בשיעורי ההדרכה צילום תמונות פשוט וצילום סרטונים פשוט.

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

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

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

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

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

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

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

זיהוי חומרת המצלמה

אם האפליקציה לא דורשת מצלמה באופן ספציפי באמצעות הצהרת מניפסט, צריך לבדוק אם המצלמה זמינה בסביבת זמן הריצה. כדי לבצע את הבדיקה הזו, משתמשים בשיטה 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 Level 9) ואילך אפשר לבדוק את מספר המצלמות הזמינות במכשיר באמצעות השיטה Camera.getNumberOfCameras().

גישה למצלמות

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

כדי לגשת למצלמה הראשית, משתמשים בשיטה 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() כדי לקבוע אם המצלמה ממוקמת בחלק הקדמי או האחורי של המכשיר ואת כיוון התמונה.

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

כדי שמשתמשים יוכלו לצלם תמונות או סרטונים בצורה יעילה, הם צריכים לראות מה המצלמה של המכשיר רואה. סיווג תצוגה מקדימה של מצלמה הוא 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());
        }
    }
}

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

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

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

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

קוד הפריסה הבא מספק תצוגה בסיסית מאוד שאפשר להשתמש בה כדי להציג תצוגה מקדימה של המצלמה. בדוגמה הזו, הרכיב 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() בדוגמה שלמעלה מתייחסת לשיטה לדוגמה שמופיעה בקטע גישה למצלמות.

צילום תמונות

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

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

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

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

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

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

  1. פתיחת המצלמה – משתמשים ב-Camera.open() כדי לקבל מופע של אובייקט המצלמה.
  2. Connect Preview (חיבור לתצוגה מקדימה) – כדי להכין תצוגה מקדימה של תמונה מהמצלמה בשידור חי, מחברים 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. Stop MediaRecorder – כדי להפסיק את הקלטת הסרטון, קוראים ל-MediaRecorder.stop().
    2. איפוס MediaRecorder – לחלופין, אפשר להסיר את הגדרות התצורה ממכשיר ההקלטה על ידי שליחת קריאה ל-MediaRecorder.reset().
    3. Release 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 כדי לצלם סרטון, צריך לבצע את שלבי ההגדרה בסדר ספציפי ואז להפעיל את השיטה 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 שקדמו ל-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, משתמשים בשיטה 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 צילום פריימים עם עיכובים מוגדרים כדי לצלם סרטון בהילוך מהיר
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, חשוב לדעת שלא כל התכונות נתמכות בכל המכשירים. בנוסף, יכול להיות שמכשירים שתומכים בתכונה מסוימת תומכים בה ברמות שונות או עם אפשרויות שונות. לכן, חלק מתהליך קבלת ההחלטות במהלך הפיתוח של אפליקציית מצלמה הוא להחליט אילו תכונות מצלמה רוצים לתמוך בהן ובאיזה רמה. אחרי שתקבלו את ההחלטה הזו, כדאי שתכננו לכלול באפליקציית המצלמה קוד שבודק אם חומרת המכשיר תומכת בתכונות האלה, ומפסיק את הפעולה בצורה תקינה אם תכונה מסוימת לא זמינה.

כדי לבדוק את הזמינות של תכונות המצלמה, אפשר לקבל מופע של אובייקט פרמטרים של מצלמה ולבדוק את השיטות הרלוונטיות. דוגמת הקוד הבאה מראה איך לקבל אובייקט 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 מספק את השיטות 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. בדרך כלל, שינויים בפרמטרים גלויים למשתמש באופן מיידי בתצוגה המקדימה של המצלמה באפליקציה. בצד התוכנה, יכול להיות שיחלפו כמה פריימים עד ששינויים בפרמטר ייכנסו לתוקף, כי חומרת המצלמה מעבדת את ההוראות החדשות ואז שולחת נתוני תמונה מעודכנים.

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

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

  • אזורי מדידה והתמקדות
  • זיהוי פנים
  • סרטון בהילוך מהיר

בחלקים הבאים מוסבר בקצרה איך מטמיעים את התכונות האלה.

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

בתרחישים מסוימים של צילום, יכול להיות שהמיקוד האוטומטי והמדידה האוטומטית של התאורה לא יניבו את התוצאות הרצויות. החל מ-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 מתאר צורה מלבנית שממופה על רשת של יחידות בגודל 2000 על 2000. הקואורדינטות -1000,‏ -1000 מייצגות את הפינה השמאלית העליונה של התמונה מהמצלמה, והקואורדינטות 1000,‏ 1000 מייצגות את הפינה השמאלית התחתונה של התמונה מהמצלמה, כפי שמוצג באיור שבהמשך.

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

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

זיהוי פנים

בתמונות עם אנשים, פנים הן בדרך כלל החלק החשוב ביותר בתמונה, ויש להשתמש בהן כדי לקבוע את המיקוד ואת איזון הלבן כשמצלמים תמונה. המסגרת של 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 מאפשר למשתמשים ליצור קליפים שמשלבים תמונות שצולמו בהפרש של כמה שניות או דקות. התכונה הזו משתמשת ב-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 ואילך) יוכלו לגשת לערכים של השדות הבאים שמוחזרים על ידי השיטה getCameraCharacteristics(), הן צריכות את ההרשאה CAMERA:

  • 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.