אם באפליקציה נעשה שימוש בגרסה המקורית של Camera
class ('camera1'), שהוצא משימוש מאז
Android 5.0 (רמת API 21),
מומלץ מאוד לעדכן ל-API מודרני של מצלמת Android. מבצעים ל-Android
cameraX (מצלמת Jetpack סטנדרטית וחזקה
API) ו-Camera2 (ממשק API של framework ברמה נמוכה). עבור
ברוב המקרים, מומלץ להעביר את האפליקציה ל- CameraX. הנה תיאור הסיבות לכך:
- קלות השימוש: CameraX מטפל בפרטים ברמה נמוכה, כדי שתוכל להתמקד פחות ביצירת חוויית צילום מאפס, להבדיל בין האפליקציה שלכם.
- מצלמת CameraX מטפלת בפרגמנטציה: CameraX מפחיתה את הטווח הארוך עלויות התחזוקה וקוד ספציפי למכשיר, וכך לקבל חוויות ברמה גבוהה יותר למשתמשים. ניתן למצוא מידע נוסף בנושא תאימות טובה יותר למכשירים עם CameraX לפוסט בבלוג.
- יכולות מתקדמות: CameraX תוכנן בקפידה כדי להפוך פונקציונליות שקל לשלב באפליקציה. לדוגמה, אתם יכולים לקבוע בקלות שימוש באפקט בוקה, ריטוש פנים, HDR (טווח דינמי גבוה) והבהרה בתאורה חלשה מצב צילום לילה בתמונות שלך באמצעות תוספי CameraX.
- אפשרות לקבל עדכונים: ב-Android יש יכולות חדשות ותיקוני באגים ב- CameraX במהלך השנה. לאחר ההעברה אל CameraX, האפליקציה שלך תקבל את גרסת Android העדכנית ביותר טכנולוגיית מצלמה בכל גרסה של CameraX, לא רק גרסאות שנתיות של Android.
במדריך הזה תלמדו תרחישים נפוצים לאפליקציות מצלמה. כל אחד כולל הטמעה של Camera1 והטמעת CameraX עבור להשוואה.
בנושא העברה, לפעמים נדרשת גמישות נוספת כדי לבצע את השילוב
עם Codebase קיים. כל קוד CameraX במדריך הזה כולל
CameraController
מצוין - אם אתם רוצים את הדרך הפשוטה ביותר להשתמש ב- CameraX -
CameraProvider
טוב יותר – אם אתם צריכים יותר גמישות. כדי לעזור לכם להחליט
במיוחד בשבילך, הנה היתרונות של כל אחת מהן:
שלט רחוק למצלמה |
CameraProvider |
נדרש מעט קוד הגדרה | שליטה רבה יותר |
המשמעות של מתן הרשאה ל- CameraX לטפל בחלק גדול יותר של תהליך ההגדרה היא פונקציונליות כמו 'הקשה למיקוד' ו'צביטה לשינוי מרחק התצוגה' פועלות באופן אוטומטי |
מכיוון שמפתחי האפליקציות מטפלים בתהליך ההגדרה, יש יותר הזדמנויות
כדי להתאים אישית את התצורה, למשל להפעיל סיבוב תמונה של פלט
או הגדרת הפורמט של תמונת הפלט ב-ImageAnalysis
|
אם צריך PreviewView לתצוגה המקדימה של המצלמה, אפשר
CameraX להציע שילוב חלק מקצה לקצה, כמו בערכת למידת המכונה שלנו
שיכול למפות את קואורדינטות התוצאה של מודל למידת מכונה (כמו פנים)
תיבות תוחמות) ישירות לקואורדינטות של התצוגה המקדימה
|
היכולת להשתמש ב-'Surface' מותאם אישית לתצוגה המקדימה של המצלמה מאפשרת גמישות רבה יותר, כמו שימוש בקוד 'Surface' הקיים שלכם, יכול להיות קלט לחלקים אחרים באפליקציה |
אם נתקעת במהלך ניסיון ההעברה, אפשר לפנות אלינו קבוצת דיון של CameraX.
לפני ההעברה
השוואת השימוש ב- CameraX ל- Camera1
הקוד עשוי להיראות שונה, אבל העקרונות הבסיסיים של Camera1 ו
CameraX דומים מאוד. מצלמהX
מפשטים את הפונקציונליות המשותפת של המצלמה בתרחישים לדוגמה.
וכתוצאה מכך, משימות רבות שנותרו למפתח ב- Camera1
מטופל באופן אוטומטי על ידי CameraX. יש ארבעה
UseCase
ב- CameraX, שאותו אפשר
משמשים למגוון משימות במצלמה: Preview
,
ImageCapture
,
VideoCapture
, וגם
ImageAnalysis
.
דוגמה אחת לטיפול בפרטים ברמה נמוכה למפתחים של CameraX היא
ViewPort
שמשותף בין
UseCase
שנ' פעילות. כך כל הUseCase
יראו בדיוק את אותם הפיקסלים.
ב- Camera1, עליכם לנהל את הפרטים האלה בעצמכם, תוך התחשבות בשונות
ביחסי גובה-רוחב במכשירים שונים חיישני המצלמה והמסכים, עלולים להיות מסובכים
מוודאים שהתצוגה המקדימה תואמת לתמונות ולסרטונים שצולמו.
דוגמה נוספת, CameraX מטפל ב-Lifecycle
קריאות חוזרות (callback) באופן אוטומטי
מכונה אחת (Lifecycle
) שמעבירים אותה. כלומר, CameraX מטפל
חיבור למצלמה במשך כל
מחזור החיים של הפעילות ב-Android,
כולל המקרים הבאים: סגירת המצלמה כשהאפליקציה עוברת
רקע; להסיר את התצוגה המקדימה של המצלמה כשאין יותר צורך במסך
הצגתו, והשהיית התצוגה המקדימה של המצלמה כשמתבצעת פעילות אחרת
קדימות בחזית, כמו שיחת וידאו נכנסת.
לבסוף, CameraX מטפל בסיבוב ובשינוי קנה מידה ללא צורך בקוד נוסף
מצידכם. במקרה של Activity
עם כיוון לא נעול, המאפיין
ההגדרה UseCase
מתבצעת בכל פעם שמסובבים את המכשיר, מכיוון שהמערכת משמיצה
ויוצרת מחדש את Activity
כשיש שינוי בכיוון. התוצאה של התהליך הזה
על ידי UseCases
להגדיר את סיבוב היעד כך שיתאים לכיוון המסך באמצעות
כברירת מחדל בכל פעם.
מידע נוסף על סיבובים ב- CameraX
לפני שנרד לפרטים, הנה מבט כללי על
UseCase
והקשר בין אפליקציית Camera1. (הקונספטים של CameraX כבר זמינים
כחול ו- Camera1
שני מושגים
ירוק.)
מצלמהX |
|||
הגדרת CameraController / CameraProvider | |||
↓ | ↓ | ↓ | ↓ |
תצוגה מקדימה | צילום תמונה | צילום וידאו | ניתוח תמונה |
⁞ | ⁞ | ⁞ | ⁞ |
ניהול התצוגה המקדימה של המסך והפעלת המצלמה | הגדרת PictureCallback ו-callTakePicture() במצלמה | ניהול התצורה של המצלמה ושל MediaRecorder בסדר ספציפי | קוד ניתוח מותאם אישית המבוסס על גרסת התצוגה המקדימה |
↑ | ↑ | ↑ | ↑ |
קוד ספציפי למכשיר | |||
↑ | |||
ניהול של סבב מכשירים והתאמה לעומס (scaling) | |||
↑ | |||
ניהול סשן המצלמה (בחירת מצלמה, ניהול מחזור חיים) | |||
מצלמה1 |
תאימות וביצועים ב- CameraX
CameraX תומך במכשירים פועלים Android 5.0 (רמת API 21) ואילך. הזה יותר מ-98% ממכשירי Android הקיימים. CameraX מיועד לטפל הבדלים אוטומטיים בין מכשירים, כך שיש פחות צורך במכשירים ספציפיים באפליקציה שלכם. בנוסף, אנחנו בודקים יותר מ-150 מכשירים פיזיים בכל מכשירי Android מגרסה 5.0 ואילך במעבדת בדיקת CameraX שלנו. שלך אפשר לעיין ברשימה המלאה של המכשירים שנמצאים כרגע ב-Test Lab.
CameraX משתמש ב-Executor
כדי
לכונן את מקבץ המצלמה. אפשר
הגדרת קובץ הפעלה משלכם ב- CameraX
אם לאפליקציה יש דרישות ספציפיות לגבי השרשורים. אם המדיניות לא מוגדרת, CameraX יוצר
ומשתמש ב-Executor
פנימי שעבר אופטימיזציה. הרבה מממשקי ה-API של הפלטפורמה מופעלים
אילו מצלמות CameraX נבנתה דורשת חסימה של תקשורת בין תהליכים (IPC) עם
חומרה שלפעמים לוקחת מאות אלפיות שנייה כדי להגיב. בשביל זה
הסיבה לכך היא ש- CameraX קורא לממשקי ה-API האלה רק משרשורי רקע, דבר שמבטיח
ה-thread הראשי לא חסום וממשק המשתמש נשאר גמיש.
למידע נוסף על שרשורים
אם שוק היעד של האפליקציה שלך כולל מכשירים פשוטים, CameraX מספקת
דרך לקצר את זמן ההגדרה באמצעות
הגבלת המצלמה. מאז
תהליך החיבור לרכיבי חומרה עשוי להימשך
במיוחד במכשירים פשוטים, ניתן לציין את סדרת המצלמות שהאפליקציה
לצרכים שלכם. CameraX מתחבר למצלמות האלה רק במהלך ההגדרה. לדוגמה, אם
האפליקציה משתמשת רק במצלמות אחוריות, היא יכולה להגדיר את התצורה הזו
באמצעות DEFAULT_BACK_CAMERA
ולאחר מכן CameraX מונע אתחול קדמי
מצלמות כדי לקצר את זמן האחזור.
מושגי פיתוח של Android
המדריך הזה מבוסס על ההנחה שיש לכם היכרות כללית עם נושא הפיתוח של Android. מעבר ל- הנה כמה מושגים שיכולים לעזור לכם להבין מעבר לקוד שלמטה:
- View Binding יוצרת מחלקה מחייבת עבור
קובצי פריסת ה-XML שלכם, מה שמאפשר לכם
לעיין בצפיות שלכם בקטע 'פעילויות'.
כפי שנעשה בכמה קטעי קוד בהמשך. יש כמה מודלים
הבדלים בין קישור תצוגות מפורטות לבין
findViewById()
(הדרך הקודמת להפנות לתצוגות), אבל בקוד שלמטה אמורה להיות לכם אפשרות החלפה של הקווים המקשרים של התצוגה בקריאה דומה ל-findViewById()
. - קורוטינים אסינכרוניים הם עיצוב בו-זמניות
נוסף ב-Kotlin 1.3 שיכול לשמש לטיפול בשיטות CameraX
הפונקציה מחזירה
ListenableFuture
. עכשיו קל יותר עם Jetpack הספרייה בו-זמנית החל מגרסה 1.1.0. כדי להוסיף קורוטין אסינכרוני לאפליקציה:- הוספה של
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
לקובץ Gradle. - יש להזין כל קוד של CameraX שמחזיר
ListenableFuture
launch
חסימה או הפונקציה מושהית. - הוספת
await()
לקריאה לפונקציה שמחזירהListenableFuture
. - להבנה עמוקה יותר של האופן שבו פועלים קורוטין, קראו את המאמר הפעלת מדריך בנושא קורוטין.
- הוספה של
העברת תרחישים נפוצים
הקטע הזה מסביר איך להעביר תרחישים נפוצים מ- Camera1 ל- CameraX.
כל תרחיש מתייחס להטמעה של Camera1, CameraX CameraProvider
והטמעה של CameraX CameraController
.
בחירת מצלמה
באפליקציית המצלמה, אחד הדברים הראשונים שכדאי לך להציע הוא כדי לבחור מצלמות שונות.
מצלמה1
ב- Camera1, אפשר להתקשר
Camera.open()
ללא פרמטרים
כדי לפתוח את המצלמה האחורית הראשונה, או שאפשר להעביר מספר שלם עבור
המצלמה שרוצים לפתוח. הדוגמה הזו יכולה להיראות:
// Camera1: select a camera from id. // Note: opening the camera is a non-trivial task, and it shouldn't be // called from the main thread, unlike CameraX calls, which can be // on the main thread since CameraX kicks off background threads // internally as needed. private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() camera = Camera.open(id) true } catch (e: Exception) { Log.e(TAG, "failed to open camera", e) false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) camera?.release() camera = null }
CameraX: CameraController
ב- CameraX, בחירת המצלמה מטופלת על ידי המחלקה CameraSelector
. מצלמהX
מאפשר להשתמש בקלות במצלמת ברירת המחדל. אפשר לציין אם
ברירת המחדל של המצלמה הקדמית או המצלמה האחורית המוגדרת כברירת מחדל. בנוסף,
באמצעות אובייקט CameraControl
של CameraX אפשר בקלות
להגדיר את רמת הזום עבור האפליקציה, כך שאם
האפליקציה שלך פועלת במכשיר שתומך
מצלמות לוגיות, ואז הוא יוחלף
לעדשה הנכונה.
הנה קוד CameraX לשימוש במצלמה האחורית המוגדרת כברירת מחדל עם
CameraController
:
// CameraX: select a camera with CameraController var cameraController = LifecycleCameraController(baseContext) val selector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() cameraController.cameraSelector = selector
CameraX: CameraProvider
הנה דוגמה לבחירה במצלמה הקדמית שמוגדרת כברירת המחדל באמצעות CameraProvider
(ניתן להשתמש במצלמה הקדמית או האחורית עם CameraController
או
CameraProvider
):
// CameraX: select a camera with CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Set up UseCases (more on UseCases in later scenarios) var useCases:Array= ... // Set the cameraSelector to use the default front-facing (selfie) // camera. val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
אם אתם רוצים לקבוע איזו מצלמה תיבחר, אפשר לעשות את זה גם
CameraX אם משתמשים ב-CameraProvider
על ידי התקשרות
getAvailableCameraInfos()
,
הפעולה הזו נותנת אובייקט CameraInfo
כדי לבדוק מאפייני מצלמה מסוימים, כמו
isFocusMeteringSupported()
.
אחר כך אפשר להמיר אותו לCameraSelector
ולהשתמש בו כמו שצוין למעלה
באמצעות השיטה CameraInfo.getCameraSelector()
.
אפשר לקבל פרטים נוספים על כל מצלמה באמצעות
Camera2CameraInfo
בכיתה. שיחת טלפון
getCameraCharacteristic()
באמצעות מפתח לנתוני המצלמה הרצויים. כדאי לבדוק את
CameraCharacteristics
בשביל רשימה של כל המפתחות שאפשר לשלוח לגביהם שאילתות.
הדוגמה הבאה היא באמצעות פונקציית checkFocalLength()
בהתאמה אישית,
הגדר את עצמך:
// CameraX: get a cameraSelector for first camera that matches the criteria // defined in checkFocalLength(). val cameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val focalLengths = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ) return checkFocalLength(focalLengths) } val cameraSelector = cameraInfo.getCameraSelector()
תצוגה מקדימה
ברוב אפליקציות המצלמה צריך להציג את פיד המצלמה במסך, בחלק מהמקרים לנקודה. עם Camera1, עליך לנהל את הקריאות החוזרות של מחזור החיים בצורה נכונה, צריך גם לקבוע את הסיבוב וההגדלה של התצוגה המקדימה.
בנוסף, ב- Camera1 צריך להחליט אם להשתמש
TextureView
או
SurfaceView
כפלטפורמת התצוגה המקדימה.
לשתי האפשרויות יש יתרונות וחסרונות, ובכל מקרה, Camera1 דורש
תטפלו בסיבוב ובשינוי קנה המידה בצורה נכונה. PreviewView
של CameraX, מצד שני
יש יישומים בסיסיים של TextureView
וגם של SurfaceView
.
CameraX מחליט איזה יישום הוא הטוב ביותר בהתאם לגורמים כמו
סוג המכשיר וגרסת Android שהאפליקציה פועלת בה. אם אחד מהם
תואם, תוכל להצהיר על ההעדפה שלך עם
PreviewView.ImplementationMode
האפשרות COMPATIBLE
משתמשת ב-TextureView
לתצוגה המקדימה, ו-
הערך של PERFORMANCE
משתמש ב-SurfaceView
(כשהדבר אפשרי).
מצלמה1
כדי להציג תצוגה מקדימה, עליך לכתוב כיתה משלך ב-Preview
באמצעות
יישום של
android.view.SurfaceHolder.Callback
שמשמש להעברת נתוני תמונה מחומרת המצלמה
תרגום מכונה. לאחר מכן, לפני שיתאפשר לך לצפות בתצוגה המקדימה של התמונה הפעילה, Preview
יש להעביר את המחלקה לאובייקט Camera
.
// Camera1: set up a camera preview. class Preview( context: Context, private val camera: Camera ) : SurfaceView(context), SurfaceHolder.Callback { private val holder: SurfaceHolder = holder.apply { addCallback(this@Preview) 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. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: IOException) { Log.d(TAG, "error setting camera preview", e) } } } override fun surfaceDestroyed(holder: SurfaceHolder) { // 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 (holder.surface == null) { return // The preview surface does not exist. } // Stop preview before making changes. try { camera.stopPreview() } catch (e: Exception) { // Tried to stop a non-existent preview; nothing to do. } // Set preview size and make any resize, rotate or // reformatting changes here. // Start preview with new settings. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: Exception) { Log.d(TAG, "error starting camera preview", e) } } } } class CameraActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private var camera: Camera? = null private var preview: Preview? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create an instance of Camera. camera = getCameraInstance() preview = camera?.let { // Create the Preview view. Preview(this, it) } // Set the Preview view as the content of the activity. val cameraPreview: FrameLayout = viewBinding.cameraPreview cameraPreview.addView(preview) } }
CameraX: CameraController
ב- CameraX יש הרבה פחות ניהול עבורך, המפתח. אם משתמשים
CameraController
, לאחר מכן צריך להשתמש גם ב-PreviewView
. המשמעות היא
Preview
UseCase
לא משתמעת, ולכן ההגדרה הרבה פחות יעילה:
// CameraX: set up a camera preview with a CameraController. class MainActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create the CameraController and set it on the previewView. var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) val previewView: PreviewView = viewBinding.cameraPreview previewView.controller = cameraController } }
CameraX: CameraProvider
בCameraProvider
של CameraX לא צריך להשתמש ב-PreviewView
, אבל
עדיין מפשט מאוד את הגדרת התצוגה המקדימה בהשוואה למצלמה1. להדגמה
למטרות, הדוגמה הזו משתמשת בPreviewView
, אבל אפשר לכתוב
SurfaceProvider
כדי לעבור אל setSurfaceProvider()
אם יש לך מסלולים מורכבים יותר
לצרכים שלכם.
כאן, הפרמטר Preview
UseCase
לא מרומז כמו שהוא ב-CameraController
,
לכן עליכם להגדיר אותו:
// CameraX: set up a camera preview with a CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the "Android development concepts" // section above. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Create Preview UseCase. val preview = Preview.Builder() .build() .also { it.setSurfaceProvider( viewBinding.viewFinder.surfaceProvider ) } // Select default back camera. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera() in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
הקשה למיקוד
כשהתצוגה המקדימה של המצלמה מופיעה במסך, אחת הדרכים הנפוצות היא להגדיר את המיקוד כשהמשתמש מקיש על התצוגה המקדימה.
מצלמה1
כדי להטמיע את התכונה 'מצמידים ומשלמים' במצלמה1, צריך לחשב את המיקוד האופטימלי
Area
כדי לציין איפה Camera
ינסה להתמקד. הArea
הזה
מועבר אל setFocusAreas()
. כמו כן, עליך להגדיר מצב התמקדות תואם
Camera
אפשר להשפיע על אזור ההתמקדות רק אם מצב ההתמקדות הנוכחי הוא
FOCUS_MODE_AUTO
, FOCUS_MODE_MACRO
, FOCUS_MODE_CONTINUOUS_VIDEO
, או
FOCUS_MODE_CONTINUOUS_PICTURE
כל Area
הוא מלבן במשקל שצוין. המשקל הוא ערך בין
1 ו-1,000, והוא משמש לקביעת סדר העדיפויות של המוקד Areas
אם הוגדרו מרובות. הזה
לדוגמה משתמשת רק ב-Area
אחד, לכן ערך המשקל לא משנה. קואורדינטות של
טווח המלבנים הוא בין 1000- ל-1000. הנקודה השמאלית העליונה היא (-1000, -1000).
הנקודה הימנית התחתונה היא (1000, 1000). הכיוון הוא ביחס לחיישן
כיוון, כלומר, מה שהחיישן רואה. הכיוון לא מושפע
סיבוב או שיקוף של Camera.setDisplayOrientation()
, כך שצריך
ממירים את הקואורדינטות של אירועי המגע לקואורדינטות של החיישן.
// Camera1: implement tap-to-focus. class TapToFocusHandler : Camera.AutoFocusCallback { private fun handleFocus(event: MotionEvent) { val camera = camera ?: return val parameters = try { camera.getParameters() } catch (e: RuntimeException) { return } // Cancel previous auto-focus function, if one was in progress. camera.cancelAutoFocus() // Create focus Area. val rect = calculateFocusAreaCoordinates(event.x, event.y) val weight = 1 // This value's not important since there's only 1 Area. val focusArea = Camera.Area(rect, weight) // Set the focus parameters. parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO) parameters.setFocusAreas(listOf(focusArea)) // Set the parameters back on the camera and initiate auto-focus. camera.setParameters(parameters) camera.autoFocus(this) } private fun calculateFocusAreaCoordinates(x: Int, y: Int) { // Define the size of the Area to be returned. This value // should be optimized for your app. val focusAreaSize = 100 // You must define functions to rotate and scale the x and y values to // be values between 0 and 1, where (0, 0) is the upper left-hand side // of the preview, and (1, 1) is the lower right-hand side. val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000 val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000 // Calculate the values for left, top, right, and bottom of the Rect to // be returned. If the Rect would extend beyond the allowed values of // (-1000, -1000, 1000, 1000), then crop the values to fit inside of // that boundary. val left = max(normalizedX - (focusAreaSize / 2), -1000) val top = max(normalizedY - (focusAreaSize / 2), -1000) val right = min(left + focusAreaSize, 1000) val bottom = min(top + focusAreaSize, 1000) return Rect(left, top, left + focusAreaSize, top + focusAreaSize) } override fun onAutoFocus(focused: Boolean, camera: Camera) { if (!focused) { Log.d(TAG, "tap-to-focus failed") } } }
CameraX: CameraController
האפליקציה CameraController
מאזינה לאירועי המגע של PreviewView
כדי לטפל בה
טקסט למיקוד באופן אוטומטי. ניתן להפעיל או להשבית את התכונה 'מצמידים ומשלמים' באמצעות
setTapToFocusEnabled()
ולבדוק את הערך עם הערך של הפרמטר הבא,
isTapToFocusEnabled()
getTapToFocusState()
ה-method מחזירה אובייקט LiveData
למעקב אחר שינויים במצב המיקוד בCameraController
.
// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView, // with handlers you can define for focused, not focused, and failed states. val tapToFocusStateObserver = Observer{ state -> when (state) { CameraController.TAP_TO_FOCUS_NOT_STARTED -> Log.d(TAG, "tap-to-focus init") CameraController.TAP_TO_FOCUS_STARTED -> Log.d(TAG, "tap-to-focus started") CameraController.TAP_TO_FOCUS_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focus successful)") CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") CameraController.TAP_TO_FOCUS_FAILED -> Log.d(TAG, "tap-to-focus failed") } } cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)
CameraX: CameraProvider
כשמשתמשים ב-CameraProvider
, יש צורך בהגדרה מסוימת כדי לקבל התמקדות בנגיעה
עובדת. בדוגמה הזו ההנחה היא שנעשה שימוש ב-PreviewView
. אם לא, צריך
צריך לשנות את הלוגיקה כך שתחול על Surface
בהתאמה אישית.
אלה השלבים לשימוש ב-PreviewView
:
- צריך להגדיר מזהה תנועות לטיפול באירועי הקשה.
- עם האירוע מסוג Tap, יוצרים
MeteringPoint
באמצעותMeteringPointFactory.createPoint()
. - באמצעות
MeteringPoint
, יוצריםFocusMeteringAction
. - באמצעות האובייקט
CameraControl
ב-Camera
(מוחזר מ-bindToLifecycle()
), התקשרותstartFocusAndMetering()
, מעבירה אתFocusMeteringAction
- (אופציונלי) מגיבים ל
FocusMeteringResult
. - איך מגדירים את מזהה התנועה להגיב לאירועי מגע ב-
PreviewView.setOnTouchListener()
// CameraX: implement tap-to-focus with CameraProvider. // Define a gesture detector to respond to tap events and call // startFocusAndMetering on CameraControl. If you want to use a // coroutine with await() to check the result of focusing, see the // "Android development concepts" section above. val gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { val previewView = previewView ?: return val camera = camera ?: return val meteringPointFactory = previewView.meteringPointFactory val focusPoint = meteringPointFactory.createPoint(e.x, e.y) val meteringAction = FocusMeteringAction .Builder(meteringPoint).build() lifecycleScope.launch { val focusResult = camera.cameraControl .startFocusAndMetering(meteringAction).await() if (!result.isFocusSuccessful()) { Log.d(TAG, "tap-to-focus failed") } } } } ) ... // Set the gestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> // See pinch-to-zooom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { didConsume = gestureDetector.onTouchEvent(event) } didConsume }
צביטה לשינוי מרחק התצוגה
הגדלה והקטנה של התצוגה המקדימה היא עוד מניפולציה ישירה נפוצה של תצוגה מקדימה של המצלמה. עם המספר ההולך וגדל של מצלמות במכשירים, המשתמשים גם נצפה שהעדשה עם אורך המוקד הטוב ביותר תיבחר באופן אוטומטי בתור כתוצאה משינוי מרחק התצוגה.
מצלמה1
יש שתי דרכים לשנות את מרחק התצוגה באמצעות Camera1. השיטה Camera.startSmoothZoom()
יוצר אנימציה, מרמת הזום הנוכחית ועד לרמת הזום שמעבירים.
שיטת Camera.Parameters.setZoom()
מדלגת ישירות לרמת הזום שמעבירים
אינץ' לפני שמשתמשים באחד מהם, צריך להתקשר למספר isSmoothZoomSupported()
או
isZoomSupported()
, בהתאמה, כדי לוודא את השיטות הקשורות לשינוי מרחק התצוגה
זמינים במצלמה.
כדי ליישם תנועת צביטה לשינוי מרחק התצוגה, הדוגמה הזו משתמשת ב-setZoom()
כי המגע
ה-listener משטח התצוגה המקדימה מפעיל כל הזמן אירועים בתור הצביטה
מתבצעת תנועה, ולכן רמת הזום מתעדכנת מיד בכל פעם.
מחלקה ZoomTouchListener
מוגדרת למטה, ויש להגדירה כקריאה חוזרת (callback)
למאזינים במגע של משטח התצוגה המקדימה.
// Camera1: implement pinch-to-zoom. // Define a scale gesture detector to respond to pinch events and call // setZoom on Camera.Parameters. val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.OnScaleGestureListener { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return false val parameters = try { camera.parameters } catch (e: RuntimeException) { return false } // In case there is any focus happening, stop it. camera.cancelAutoFocus() // Set the zoom level on the Camera.Parameters, and set // the Parameters back onto the Camera. val currentZoom = parameters.zoom parameters.setZoom(detector.scaleFactor * currentZoom) camera.setParameters(parameters) return true } } ) // Define a View.OnTouchListener to attach to your preview view. class ZoomTouchListener : View.OnTouchListener { override fun onTouch(v: View, event: MotionEvent): Boolean = scaleGestureDetector.onTouchEvent(event) } // Set a ZoomTouchListener to handle touch events on your preview view // if zoom is supported by the current camera. if (camera.getParameters().isZoomSupported()) { view.setOnTouchListener(ZoomTouchListener()) }
CameraX: CameraController
בדומה להקשה למיקוד, CameraController
מקשיב למגע של PreviewView
כדי לטפל אוטומטית בתנועת צביטה לשינוי מרחק התצוגה. אפשר להפעיל ולהשבית
לעשות תנועת צביטה כדי לשנות את מרחק התצוגה באמצעות
setPinchToZoomEnabled()
ולבדוק את הערך עם הערך של הפרמטר הבא,
isPinchToZoomEnabled()
getZoomState()
ה-method מחזירה אובייקט LiveData
כדי לעקוב אחר שינויים
ZoomState
ב
CameraController
.
// CameraX: track the state of pinch-to-zoom over the Lifecycle of // a PreviewView, logging the linear zoom ratio. val pinchToZoomStateObserver = Observer{ state -> val zoomRatio = state.getZoomRatio() Log.d(TAG, "ptz-zoom-ratio $zoomRatio") } cameraController.getZoomState().observe(this, pinchToZoomStateObserver)
CameraX: CameraProvider
כדי ששינוי מרחק התצוגה יפעל עם CameraProvider
, צריך לקבוע הגדרות מסוימות. אם המיקום
אתם לא משתמשים ב-PreviewView
, עליכם לשנות את הלוגיקה כך שתחול
Surface
בהתאמה אישית.
אלה השלבים לשימוש ב-PreviewView
:
- הגדרת מזהה של תנועות שינוי גודל כדי לטפל באירועי צביטה.
- מקבלים את
ZoomState
מהאובייקטCamera.CameraInfo
, שבוCamera
המופע מוחזר כשקוראיםbindToLifecycle()
- אם ל
ZoomState
יש ערךzoomRatio
, צריך לשמור אותו כזום הנוכחי יחס הגובה-רוחב. אם איןzoomRatio
ב-ZoomState
, צריך להשתמש בברירת המחדל של המצלמה שיעור הזום (1.0). - לוקחים את המכפלה של יחס הזום הנוכחי עם
scaleFactor
אל לקבוע את יחס הזום החדש, ולהעביר אותו ל-CameraControl.setZoomRatio()
. - איך מגדירים את מזהה התנועה להגיב לאירועי מגע ב-
PreviewView.setOnTouchListener()
// CameraX: implement pinch-to-zoom with CameraProvider. // Define a scale gesture detector to respond to pinch events and call // setZoomRatio on CameraControl. val scaleGestureDetector = ScaleGestureDetector(context, object : SimpleOnGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return val zoomState = camera.cameraInfo.zoomState val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f camera.cameraControl.setZoomRatio( detector.scaleFactor * currentZoomRatio ) } } ) ... // Set the scaleGestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { // See pinch-to-zooom scenario for gestureDetector definition. didConsume = gestureDetector.onTouchEvent(event) } didConsume }
צילום תמונה
בקטע הזה נסביר איך להפעיל צילום תמונה, אם צריך לעשות זאת לאחר סיום הטיימר, או בכל אירוע אחר בחירה.
מצלמה1
במצלמה1, קודם אתם מגדירים
Camera.PictureCallback
כדי לנהל את נתוני התמונה כשנשלחת בקשה. דוגמה פשוטה
PictureCallback
לטיפול בנתוני תמונה בפורמט JPEG:
// Camera1: define a Camera.PictureCallback to handle JPEG data. private val picture = 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) } catch (e: IOException) { Log.d(TAG, "error accessing file", e) } }
לאחר מכן, בכל פעם שרוצים לצלם תמונה, מפעילים את השיטה takePicture()
.
במכונה של Camera
. ל-method הזה של takePicture()
יש שלושה סוגים
שונים לסוגים שונים של נתונים. הפרמטר הראשון הוא
ShutterCallback
(שלא מוגדר בדוגמה הזו). הפרמטר השני הוא
ל-PictureCallback
כדי לטפל בנתוני המצלמה הגולמיים (לא דחוסים). השלישי
הפרמטר הוא הפרמטר שבו משתמשת בדוגמה הזו, כי הוא PictureCallback
שצריך לטפל בו.
נתוני תמונה בפורמט JPEG.
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
CameraX: CameraController
CameraController
של CameraX שומר על הפשטות של Camera1 לתמונה
לתעד על ידי הטמעת שיטת takePicture()
משלו. כאן מגדירים
כדי להגדיר רשומת MediaStore
ולצלם תמונה שתישמר שם.
// CameraX: define a function that uses CameraController to take a photo. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun takePhoto() { // Create time stamped name and MediaStore entry. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") } } // Create output options object which contains file + metadata. val outputOptions = ImageCapture.OutputFileOptions .Builder(context.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) .build() // Set up image capture listener, which is triggered after photo has // been taken. cameraController.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(e: ImageCaptureException) { Log.e(TAG, "photo capture failed", e) } override fun onImageSaved( output: ImageCapture.OutputFileResults ) { val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } } ) }
CameraX: CameraProvider
צילום תמונה עם CameraProvider
עובד כמעט באותו אופן כמו עם
CameraController
, אבל קודם צריך ליצור ולקשר ImageCapture
UseCase
כדי לקבל אובייקט להפעיל את takePicture()
באמצעות:
// CameraX: create and bind an ImageCapture UseCase. // Make a reference to the ImageCapture UseCase at a scope that can be accessed // throughout the camera logic in your app. private var imageCapture: ImageCapture? = null ... // Create an ImageCapture instance (can be added with other // UseCase definitions). imageCapture = ImageCapture.Builder().build() ... // Bind UseCases to camera (adding imageCapture along with preview here, but // preview is not required to use imageCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture)
בכל פעם שתרצו לצלם תמונה, תוכלו להתקשר
ImageCapture.takePicture()
הצגת הקוד CameraController
בקטע הזה
תופיע דוגמה מלאה לפונקציה takePhoto()
.
// CameraX: define a function that uses CameraController to take a photo. private fun takePhoto() { // Get a stable reference of the modifiable ImageCapture UseCase. val imageCapture = imageCapture ?: return ... // Call takePicture on imageCapture instance. imageCapture.takePicture( ... ) }
הקלטת סרטון
צילום סרטון הוא הרבה יותר מסובך מהתרחישים שהוצגו עד עכשיו. כל חלק בתהליך צריך להיות מוגדר כראוי, בדרך כלל הזמנה מסוימת. בנוסף, אולי צריך לוודא שהווידאו והאודיו לסנכרן או לטפל בחוסר עקביות נוסף במכשיר.
כמו שאתם רואים, CameraX מטפל שוב במורכבות הזו.
מצלמה1
צילום וידאו באמצעות Camera1 מחייב ניהול זהיר של Camera
MediaRecorder
, והשיטות חייבות
לקרוא לפי סדר מסוים. חובה לעקוב אחר ההזמנה הזו עבור
כדי שהאפליקציה תפעל כראוי:
- פותחים את המצלמה.
- הכנות והתחלת תצוגה מקדימה (אם האפליקציה מציגה את הסרטון המוקלט, וזה בדרך כלל המצב).
- צריך לבטל את נעילת המצלמה לשימוש על ידי
MediaRecorder
על ידי התקשרות אלCamera.unlock()
. - מגדירים את ההקלטה על ידי שליחת קריאה ל-methods ב-
MediaRecorder
:- מחברים את המכונה של
Camera
אלsetCamera(camera)
. - התקשרות אל
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
. - התקשרות אל
setVideoSource(MediaRecorder.VideoSource.CAMERA)
. - חיוג למספר
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
כדי להגדיר את האיכות. צפייהCamcorderProfile
לכולם אפשרויות האיכות. - התקשרות אל
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
. - אם באפליקציה יש תצוגה מקדימה של הסרטון, צריך להתקשר
setPreviewDisplay(preview?.holder?.surface)
- התקשרות אל
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
. - התקשרות אל
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
. - התקשרות אל
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
. - קוראים לפונקציה
prepare()
כדי להשלים את ההגדרה שלMediaRecorder
.
- מחברים את המכונה של
- כדי להתחיל להקליט, צריך להתקשר למספר
MediaRecorder.start()
. - כדי להפסיק את ההקלטה, צריך להפעיל את השיטות האלה. שוב, הקפידו על הסדר המדויק הבא:
- התקשרות אל
MediaRecorder.stop()
. - אפשר להסיר את ההגדרה הנוכחית של
MediaRecorder
באמצעות התקשרותMediaRecorder.reset()
. - התקשרות אל
MediaRecorder.release()
. - נעילת המצלמה כדי שאפשר יהיה להשתמש בה בסשנים עתידיים של
MediaRecorder
באמצעות בהתקשרות אלCamera.lock()
.
- התקשרות אל
- כדי להפסיק את התצוגה המקדימה, צריך להתקשר אל
Camera.stopPreview()
. - לבסוף, כדי לשחרר את
Camera
כדי שתהליכים אחרים יוכלו להשתמש בו, צריך לקרואCamera.release()
.
השילוב של כל השלבים האלה הוא:
// Camera1: set up a MediaRecorder and a function to start and stop video // recording. // Make a reference to the MediaRecorder at a scope that can be accessed // throughout the camera logic in your app. private var mediaRecorder: MediaRecorder? = null private var isRecording = false ... private fun prepareMediaRecorder(): Boolean { mediaRecorder = MediaRecorder() // Unlock and set camera to MediaRecorder. camera?.unlock() mediaRecorder?.run { setCamera(camera) // Set the audio and video sources. setAudioSource(MediaRecorder.AudioSource.CAMCORDER) setVideoSource(MediaRecorder.VideoSource.CAMERA) // Set a CamcorderProfile (requires API Level 8 or higher). setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)) // Set the output file. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) // Set the preview output. setPreviewDisplay(preview?.holder?.surface) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) // Prepare configured MediaRecorder. return try { prepare() true } catch (e: IllegalStateException) { Log.d(TAG, "preparing MediaRecorder failed", e) releaseMediaRecorder() false } catch (e: IOException) { Log.d(TAG, "setting MediaRecorder file failed", e) releaseMediaRecorder() false } } return false } private fun releaseMediaRecorder() { mediaRecorder?.reset() mediaRecorder?.release() mediaRecorder = null camera?.lock() } private fun startStopVideo() { if (isRecording) { // Stop recording and release camera. mediaRecorder?.stop() releaseMediaRecorder() camera?.lock() isRecording = false // This is a good place to inform user that video recording has stopped. } else { // Initialize video camera. if (prepareVideoRecorder()) { // Camera is available and unlocked, MediaRecorder is prepared, now // you can start recording. mediaRecorder?.start() isRecording = true // This is a good place to inform the user that recording has // started. } else { // Prepare didn't work, release the camera. releaseMediaRecorder() // Inform user here. } } }
CameraX: CameraController
באמצעות CameraController
של CameraX, אפשר להחליף את המצב של ImageCapture
,
VideoCapture
, ו-ImageAnalysis
UseCase
בנפרד,
כל עוד אפשר להשתמש ברשימת התרחישים לדוגמה בו-זמנית.
האפליקציות ImageCapture
ו-ImageAnalysis
UseCase
מופעלות כברירת מחדל,
לכן לא צריך להתקשר אל setEnabledUseCases()
כדי לצלם תמונה.
כדי להשתמש ב-CameraController
לצילום וידאו, צריך קודם להשתמש
setEnabledUseCases()
כדי לאשר את VideoCapture
UseCase
.
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
כשתרצו להתחיל בהקלטת וידאו, תוכלו להתקשר
CameraController.startRecording()
מותאמת אישית. הפונקציה הזו יכולה לשמור את הסרטון המוקלט ב-File
, כמו שאפשר לראות
בדוגמה שלמטה. בנוסף, עליך לעבור Executor
בכיתה
שמיישם
OnVideoSavedCallback
כדי לטפל בקריאות חוזרות (callback) ושגיאות הצלחה. כשההקלטה אמורה להסתיים, מבצעים קריאה
CameraController.stopRecording()
הערה: אם אתם משתמשים ב- CameraX מגרסה 1.3.0-alpha02 ואילך, יש
הפרמטר AudioConfig
שמאפשר להפעיל או להשבית את הקלטת האודיו בסרטון. כדי להפעיל
הקלטת אודיו, עליך לוודא שיש לך הרשאות לשימוש במיקרופון.
בנוסף, ה-method stopRecording()
מוסרת בגרסה 1.3.0-alpha02, וכן
הפונקציה startRecording()
מחזירה אובייקט Recording
שאפשר להשתמש בו להשהיה,
ממשיך ומפסיק את הקלטת הסרטון.
// CameraX: implement video capture with CameraController. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" // Define a VideoSaveCallback class for handling success and error states. class VideoSaveCallback : OnVideoSavedCallback { override fun onVideoSaved(outputFileResults: OutputFileResults) { val msg = "Video capture succeeded: ${outputFileResults.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) { Log.d(TAG, "error saving video: $message", cause) } } private fun startStopVideo() { if (cameraController.isRecording()) { // Stop the current recording session. cameraController.stopRecording() return } // Define the File options for saving the video. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val outputFileOptions = OutputFileOptions .Builder(File(this.filesDir, name)) .build() // Call startRecording on the CameraController. cameraController.startRecording( outputFileOptions, ContextCompat.getMainExecutor(this), VideoSaveCallback() ) }
CameraX: CameraProvider
אם משתמשים ב-CameraProvider
, צריך ליצור VideoCapture
UseCase
ומעבירים אובייקט Recorder
. ב-Recorder.Builder
אפשר
את איכות הווידאו, ואם רוצים,
FallbackStrategy
,
מטפל במקרים שבהם המכשיר לא יכול לעמוד במפרטי האיכות הרצויים. לאחר מכן
מקשרים את המכונה של VideoCapture
למופע של CameraProvider
UseCase
שנ'.
// CameraX: create and bind a VideoCapture UseCase with CameraProvider. // Make a reference to the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your app. private lateinit var videoCapture: VideoCaptureprivate var recording: Recording? = null ... // Create a Recorder instance to set on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.FHD)) .build() videoCapture = VideoCapture.withOutput(recorder) ... // Bind UseCases to camera (adding videoCapture along with preview here, but // preview is not required to use videoCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture)
בשלב הזה אפשר לגשת ל-Recorder
דרך videoCapture.output
לנכס. המכשיר Recorder
יכול להתחיל הקלטות וידאו שנשמרו ב-File
,
ParcelFileDescriptor
או MediaStore
. בדוגמה הזו נעשה שימוש ב-MediaStore
.
בRecorder
, יש כמה שיטות להתקשר כדי להכין אותו. שיחת טלפון
prepareRecording()
כדי להגדיר את אפשרויות הפלט של MediaStore
. אם באפליקציה יש
הרשאה להשתמש במיקרופון של המכשיר, יש להתקשר גם אל withAudioEnabled()
.
לאחר מכן, קוראים לפונקציה start()
כדי להתחיל להקליט, להעביר בהקשר
האזנה לאירוע של Consumer<VideoRecordEvent>
לטיפול באירועי הקלטת וידאו. אם המיקום
ניתן להשתמש בפונקציה Recording
שהוחזרה כדי להשהות, להמשיך או להפסיק
מוקלטת.
// CameraX: implement video capture with CameraProvider. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun startStopVideo() { val videoCapture = this.videoCapture ?: return if (recording != null) { // Stop the current recording session. recording.stop() recording = null return } // Create and start a new recording session. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") } } val mediaStoreOutputOptions = MediaStoreOutputOptions .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues) .build() recording = videoCapture.output .prepareRecording(this, mediaStoreOutputOptions) .withAudioEnabled() .start(ContextCompat.getMainExecutor(this)) { recordEvent -> when(recordEvent) { is VideoRecordEvent.Start -> { viewBinding.videoCaptureButton.apply { text = getString(R.string.stop_capture) isEnabled = true } } is VideoRecordEvent.Finalize -> { if (!recordEvent.hasError()) { val msg = "Video capture succeeded: " + "${recordEvent.outputResults.outputUri}" Toast.makeText( baseContext, msg, Toast.LENGTH_SHORT ).show() Log.d(TAG, msg) } else { recording?.close() recording = null Log.e(TAG, "video capture ends with error", recordEvent.error) } viewBinding.videoCaptureButton.apply { text = getString(R.string.start_capture) isEnabled = true } } } } }
מקורות מידע נוספים
יש לנו כמה אפליקציות CameraX שלמות מאגר GitHub של דגימות מצלמה הדוגמאות הבאות מראות איך התרחישים במדריך הזה משתלבים אפליקציה ל-Android.
אם ברצונך לקבל תמיכה נוספת במעבר אל CameraX, או אם יש לך שאלות בנוגע לחבילת ממשקי ה-API של מצלמת Android, יש לפנות אלינו דיון CameraX קבוצה.