Camera1 را به CameraX منتقل کنید

اگر برنامه شما از کلاس Camera اصلی ("Camera1") استفاده می کند که از Android نسخه 5.0 (سطح API 21) منسوخ شده است، به شدت توصیه می کنیم به یک API دوربین Android مدرن به روز رسانی کنید. Android CameraX (یک API استاندارد و قوی دوربین Jetpack ) و Camera2 (یک API سطح پایین چارچوب) را ارائه می دهد. در اکثر موارد، توصیه می کنیم برنامه خود را به CameraX منتقل کنید. در اینجا دلیل آن است:

  • سهولت استفاده: CameraX جزئیات سطح پایین را کنترل می کند، به طوری که می توانید کمتر روی ایجاد یک تجربه دوربین از ابتدا تمرکز کنید و بیشتر روی متمایز کردن برنامه خود تمرکز کنید.
  • CameraX تکه تکه شدن را برای شما کنترل می کند: CameraX هزینه های نگهداری طولانی مدت و کدهای خاص دستگاه را کاهش می دهد و تجربه های با کیفیت بالاتری را برای کاربران به ارمغان می آورد. برای اطلاعات بیشتر در این مورد، پست وبلاگ سازگاری بهتر دستگاه با CameraX را بررسی کنید.
  • قابلیت‌های پیشرفته: CameraX با دقت طراحی شده است تا قابلیت‌های پیشرفته را در برنامه شما ساده کند. برای مثال، می‌توانید به راحتی بوکه، روتوش چهره، HDR (محدوده دینامیکی بالا) و حالت عکاسی در شب با روشنایی کم را روی عکس‌های خود با برنامه‌های افزودنی CameraX اعمال کنید.
  • قابلیت به روز رسانی: اندروید قابلیت های جدید و رفع اشکالات را در CameraX در طول سال منتشر می کند. با مهاجرت به CameraX، برنامه شما آخرین فناوری دوربین Android را با هر نسخه CameraX دریافت می‌کند، نه فقط در نسخه‌های سالانه Android.

در این راهنما، سناریوهای رایج برای برنامه های دوربین را خواهید یافت. هر سناریو شامل اجرای Camera1 و اجرای CameraX برای مقایسه است.

وقتی صحبت از مهاجرت به میان می‌آید، گاهی اوقات برای ادغام با یک پایگاه کد موجود به انعطاف‌پذیری بیشتری نیاز دارید. تمام کدهای CameraX در این راهنما دارای یک اجرای CameraController هستند - اگر می‌خواهید ساده‌ترین راه را برای استفاده از CameraX داشته باشید - و همچنین اجرای CameraProvider - عالی است اگر به انعطاف‌پذیری بیشتری نیاز دارید. برای کمک به شما در تصمیم گیری اینکه کدام یک برای شما مناسب است، در اینجا مزایای هر کدام آورده شده است:

دوربین کنترلر

Camera Provider

به کد راه اندازی کمی نیاز دارد امکان کنترل بیشتر را فراهم می کند
اجازه دادن به CameraX برای انجام بیشتر مراحل راه‌اندازی به این معنی است که عملکردهایی مانند فوکوس ضربه زدن و زوم کردن به‌طور خودکار کار می‌کنند. از آنجایی که توسعه‌دهنده برنامه تنظیمات را انجام می‌دهد، فرصت‌های بیشتری برای سفارشی کردن پیکربندی وجود دارد، مانند فعال کردن چرخش تصویر خروجی یا تنظیم فرمت تصویر خروجی در ImageAnalysis
نیاز به PreviewView برای پیش‌نمایش دوربین به CameraX اجازه می‌دهد تا یکپارچه‌سازی یکپارچه سرتاسر را ارائه دهد، مانند ادغام کیت ML ما که می‌تواند مختصات نتیجه مدل ML (مانند جعبه‌های محدودکننده چهره) را مستقیماً روی مختصات پیش‌نمایش ترسیم کند. امکان استفاده از «Surface» سفارشی برای پیش‌نمایش دوربین، به انعطاف‌پذیری بیشتری اجازه می‌دهد، مانند استفاده از کد «Surface» موجود شما که می‌تواند ورودی به بخش‌های دیگر برنامه شما باشد.

اگر در تلاش برای مهاجرت گیر کردید، در گروه بحث CameraX با ما تماس بگیرید.

قبل از مهاجرت

استفاده از CameraX را با Camera1 مقایسه کنید

در حالی که ممکن است کد متفاوت به نظر برسد، مفاهیم اساسی در Camera1 و CameraX بسیار شبیه هستند. CameraX عملکردهای رایج دوربین را در موارد استفاده خلاصه می‌کند و در نتیجه، بسیاری از وظایفی که در Camera1 به توسعه‌دهنده واگذار شده بود، به‌طور خودکار توسط CameraX انجام می‌شود. چهار UseCase در CameraX وجود دارد که می‌توانید از آنها برای کارهای مختلف دوربین استفاده کنید: Preview ، ImageCapture ، VideoCapture و ImageAnalysis .

یک نمونه از CameraX که جزئیات سطح پایین را برای توسعه دهندگان مدیریت می کند، ViewPort است که بین UseCase های فعال به اشتراک گذاشته شده است. این تضمین می‌کند که همه UseCase پیکسل‌های مشابهی را ببینند. در Camera1، شما باید خودتان این جزئیات را مدیریت کنید، و با توجه به تنوع نسبت‌های تصویر در حسگرها و صفحه‌نمایش‌های دوربین دستگاه‌ها، اطمینان از اینکه پیش‌نمایش شما با عکس‌ها و ویدیوهای گرفته شده مطابقت دارد، می‌تواند مشکل باشد.

به عنوان مثالی دیگر، CameraX تماس‌های Lifecycle را به‌طور خودکار در نمونه Lifecycle که از آن عبور می‌کنید، کنترل می‌کند. این بدان معناست که CameraX اتصال برنامه شما به دوربین را در طول چرخه حیات Android کنترل می‌کند، از جمله موارد زیر: بستن دوربین زمانی که برنامه شما به پس‌زمینه می‌رود. حذف پیش نمایش دوربین زمانی که صفحه دیگر نیازی به نمایش آن ندارد. و توقف پیش‌نمایش دوربین هنگامی که فعالیت دیگری در پیش‌زمینه اولویت دارد، مانند تماس ویدیویی ورودی.

در نهایت، CameraX چرخش و مقیاس بندی را بدون نیاز به کد اضافی از جانب شما انجام می دهد. در مورد یک Activity با جهت قفل نشده، هر بار که دستگاه چرخانده می شود، تنظیم UseCase انجام می شود، زیرا سیستم باعث از بین رفتن و ایجاد مجدد Activity در تغییرات جهت گیری می شود. این باعث می شود که UseCases چرخش هدف خود را هر بار به طور پیش فرض مطابق با جهت نمایشگر تنظیم کند. درباره چرخش در CameraX بیشتر بخوانید .

قبل از پرداختن به جزئیات، در اینجا نگاهی سطح بالا به UseCase s CameraX و نحوه ارتباط یک برنامه Camera1 می‌دهیم. (مفاهیم CameraX به رنگ آبی و مفاهیم Camera1 به رنگ سبز هستند.)

CameraX

CameraController / پیکربندی CameraProvider
پیش نمایش ImageCapture فیلمبرداری تجزیه و تحلیل تصویر
پیش نمایش سطح را مدیریت کنید و آن را روی دوربین تنظیم کنید PictureCallback را تنظیم کنید و takePicture() را روی دوربین فراخوانی کنید پیکربندی دوربین و MediaRecorder را به ترتیب خاصی مدیریت کنید کد تجزیه و تحلیل سفارشی ساخته شده در بالای سطح پیش نمایش
کد مخصوص دستگاه
چرخش دستگاه و مدیریت مقیاس
مدیریت جلسه دوربین (انتخاب دوربین، مدیریت چرخه زندگی)

دوربین 1

سازگاری و عملکرد در CameraX

CameraX از دستگاه های دارای Android نسخه 5.0 (سطح API 21) و بالاتر پشتیبانی می کند. این بیش از 98٪ از دستگاه های اندروید موجود را نشان می دهد. CameraX برای کنترل خودکار تفاوت‌های بین دستگاه‌ها ساخته شده است و نیاز به کدهای خاص دستگاه را در برنامه شما کاهش می‌دهد. علاوه بر این، ما بیش از 150 دستگاه فیزیکی را در همه نسخه‌های Android از نسخه 5.0 در آزمایشگاه تست CameraX خود آزمایش می‌کنیم. می‌توانید فهرست کامل دستگاه‌هایی را که در حال حاضر در آزمایشگاه تست هستند، مرور کنید.

CameraX از یک Executor برای هدایت پشته دوربین استفاده می کند. در صورتی که برنامه شما نیاز به رشته خاصی دارد، می‌توانید مجری خود را در CameraX تنظیم کنید . اگر تنظیم نشود، CameraX یک Executor داخلی پیش‌فرض بهینه‌سازی شده ایجاد و استفاده می‌کند. بسیاری از APIهای پلتفرمی که CameraX بر روی آنها ساخته شده است نیاز به مسدود کردن ارتباطات بین پردازشی (IPC) با سخت افزار دارند که گاهی اوقات ممکن است صدها میلی ثانیه طول بکشد تا پاسخ دهند. به همین دلیل، CameraX فقط این APIها را از رشته‌های پس‌زمینه فراخوانی می‌کند، که تضمین می‌کند رشته اصلی مسدود نشده است و رابط کاربری روان باقی می‌ماند. در مورد رشته ها بیشتر بخوانید .

اگر بازار هدف برنامه شما شامل دستگاه‌های ارزان‌قیمت است، CameraX راهی برای کاهش زمان راه‌اندازی با محدودکننده دوربین ارائه می‌کند. از آنجایی که فرآیند اتصال به اجزای سخت‌افزاری، به‌ویژه در دستگاه‌های ارزان‌قیمت می‌تواند زمان بی‌اهمیتی را ببرد، می‌توانید مجموعه دوربین‌های مورد نیاز برنامه خود را مشخص کنید. CameraX فقط در حین راه اندازی به این دوربین ها متصل می شود. برای مثال، اگر برنامه فقط از دوربین‌های پشتی استفاده می‌کند، می‌تواند این پیکربندی را با DEFAULT_BACK_CAMERA تنظیم کند و سپس CameraX از تنظیم اولیه دوربین‌های جلو برای کاهش تأخیر جلوگیری می‌کند.

مفاهیم توسعه اندروید

این راهنما آشنایی کلی با توسعه اندروید را فرض می کند. فراتر از اصول اولیه، در اینجا چند مفهوم وجود دارد که درک آنها قبل از پرش به کد زیر مفید است:

  • View Binding یک کلاس اتصال برای فایل‌های طرح‌بندی XML شما ایجاد می‌کند، که به شما امکان می‌دهد به راحتی به نماهای خود در Activities ارجاع دهید ، همانطور که در چند قطعه کد زیر انجام می‌شود. بین view binding و findViewById() (روش قبلی برای ارجاع به view ها) تفاوت هایی وجود دارد، اما در کد زیر باید بتوانید خطوط view binding را با فراخوانی findViewById() مشابه جایگزین کنید.
  • Coroutine های ناهمزمان یک الگوی طراحی همزمان هستند که در Kotlin 1.3 اضافه شده است که می تواند برای مدیریت روش های CameraX که یک ListenableFuture برمی گرداند استفاده شود. این کار با کتابخانه همزمان Jetpack از نسخه 1.1.0 آسانتر شده است. برای افزودن یک کوروتین ناهمزمان به برنامه خود:
    1. implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") را به فایل Gradle خود اضافه کنید.
    2. هر کد CameraX که ListenableFuture را برمی گرداند را در بلوک launch یا عملکرد تعلیق قرار دهید.
    3. یک فراخوان await() به فراخوانی تابع اضافه کنید که ListenableFuture را برمی گرداند.
    4. برای درک عمیق تر از نحوه عملکرد کوروتین ها، به راهنمای شروع یک کار روتین مراجعه کنید.

سناریوهای رایج را مهاجرت کنید

این بخش نحوه انتقال سناریوهای رایج از Camera1 به CameraX را توضیح می دهد. هر سناریو شامل اجرای Camera1، اجرای CameraX CameraProvider و اجرای CameraX CameraController است.

انتخاب دوربین

در برنامه دوربین خود، یکی از اولین چیزهایی که ممکن است بخواهید ارائه دهید، راهی برای انتخاب دوربین های مختلف است.

دوربین 1

در Camera1، می‌توانید Camera.open() بدون هیچ پارامتری برای باز کردن اولین دوربین پشتی فراخوانی کنید، یا می‌توانید یک ID عدد صحیح برای دوربینی که می‌خواهید باز کنید ارسال کنید. در اینجا مثالی از نحوه ظاهر آن آمده است:

// 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 انجام می شود. CameraX استفاده از دوربین پیش فرض را آسان می کند. می‌توانید مشخص کنید که دوربین جلوی پیش‌فرض یا دوربین پشتی پیش‌فرض را می‌خواهید. علاوه بر این، شی 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()
    }
}

اگر می‌خواهید روی دوربینی که انتخاب می‌شود کنترل کنید، اگر از CameraProvider استفاده می‌کنید با فراخوانی getAvailableCameraInfos() که یک شی CameraInfo برای بررسی ویژگی‌های دوربین مانند isFocusMeteringSupported() به شما می‌دهد، در CameraX نیز امکان‌پذیر است. سپس می توانید آن را به 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 بسته به عواملی مانند نوع دستگاه و نسخه اندرویدی که برنامه شما روی آن اجرا می شود، تصمیم می گیرد که کدام پیاده سازی بهترین است. اگر هر یک از پیاده سازی ها سازگار است، می توانید اولویت خود را با 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 استفاده کنید، اما همچنان تنظیمات پیش نمایش را در Camera1 بسیار ساده می کند. برای اهداف نمایشی، این مثال از یک 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

برای پیاده سازی tap-to-focus در Camera1، باید Area فوکوس بهینه را محاسبه کنید تا مشخص کنید Camera باید کجا فوکوس کند. این Area به setFocusAreas() منتقل می شود. همچنین، باید یک حالت فوکوس سازگار روی Camera تنظیم کنید. منطقه فوکوس فقط در صورتی تأثیر دارد که حالت فوکوس فعلی FOCUS_MODE_AUTO ، FOCUS_MODE_MACRO ، FOCUS_MODE_CONTINUOUS_VIDEO ، یا FOCUS_MODE_CONTINUOUS_PICTURE باشد.

هر Area یک مستطیل با وزن مشخص است. وزن مقداری بین 1 تا 1000 است و برای اولویت بندی 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() tap-to-focus را فعال و غیرفعال کنید و مقدار را با دریافت‌کننده مربوطه isTapToFocusEnabled() بررسی کنید.

متد getTapToFocusState() یک شی 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 آمده است:

  1. یک آشکارساز ژست‌ها برای مدیریت رویدادهای ضربه‌ای تنظیم کنید.
  2. با رویداد ضربه بزنید، یک MeteringPoint با استفاده از MeteringPointFactory.createPoint() ایجاد کنید.
  3. با MeteringPoint ، یک FocusMeteringAction ایجاد کنید.
  4. با شئ CameraControl در Camera خود (برگردانده شده از bindToLifecycle()startFocusAndMetering() را فراخوانی کنید و از FocusMeteringAction عبور کنید.
  5. (اختیاری) به نتیجه FocusMeteringResult پاسخ دهید.
  6. آشکارساز ژست خود را طوری تنظیم کنید که به رویدادهای لمسی در 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() به ترتیب، برای اطمینان از اینکه روش‌های بزرگنمایی مرتبطی که نیاز دارید در دوربین شما موجود است.

برای پیاده‌سازی pinch-to-zoom، این مثال از setZoom() استفاده می‌کند، زیرا شنونده لمسی روی سطح پیش‌نمایش به‌طور پیوسته رویدادها را همزمان با انجام ژست پینچ کردن، اجرا می‌کند، بنابراین هر بار سطح بزرگ‌نمایی را فوراً به‌روزرسانی می‌کند. کلاس ZoomTouchListener در زیر تعریف شده است و باید به عنوان یک تماس برای شنونده لمسی سطح پیش نمایش شما تنظیم شود.

// 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 گوش می‌دهد تا به طور خودکار زوم کردن را انجام دهد. می‌توانید pinch-to-zoom را با setPinchToZoomEnabled() فعال یا غیرفعال کنید و مقدار را با دریافت‌کننده مربوطه isPinchToZoomEnabled() بررسی کنید.

متد getZoomState() یک شی 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 آمده است:

  1. یک آشکارساز ژست مقیاس برای مدیریت رویدادهای نیشگون گرفتن راه اندازی کنید.
  2. ZoomState از شی Camera.CameraInfo دریافت کنید، جایی که با فراخوانی bindToLifecycle() نمونه Camera برگردانده می شود.
  3. اگر ZoomState یک مقدار zoomRatio دارد، آن را به عنوان نسبت بزرگنمایی فعلی ذخیره کنید. اگر در ZoomState هیچ zoomRatio وجود ندارد، از نرخ بزرگنمایی پیش‌فرض دوربین (1.0) استفاده کنید.
  4. برای تعیین نسبت بزرگنمایی جدید، حاصل ضرب نسبت بزرگنمایی فعلی را با scaleFactor بگیرید و آن را به CameraControl.setZoomRatio() منتقل کنید.
  5. آشکارساز ژست خود را طوری تنظیم کنید که به رویدادهای لمسی در 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

در Camera1، ابتدا 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 خود فراخوانی می‌کنید. این متد takePicture() دارای سه پارامتر مختلف برای انواع داده های مختلف است. اولین پارامتر مربوط به ShutterCallback است (که در این مثال تعریف نشده است). پارامتر دوم برای یک PictureCallback برای مدیریت داده های خام (غیر فشرده) دوربین است. سومین پارامتر همان پارامتری است که این مثال استفاده می کند، زیرا یک PictureCallback برای مدیریت داده های تصویر JPEG است.

// Camera1: call takePicture on Camera instance, passing our PictureCallback.

camera?.takePicture(null, null, picture)

CameraX: CameraController

CameraController CameraX با پیاده سازی یک متد takePicture() خود، سادگی Camera1 را برای گرفتن تصویر حفظ می کند. در اینجا، تابعی را برای پیکربندی ورودی 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() را فراخوانی کنید. برای مثال کامل تابع takePhoto() کد CameraController را در این بخش ببینید.

// 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 نیاز دارد و روش‌ها باید به ترتیب خاصی فراخوانی شوند. شما باید این ترتیب را دنبال کنید تا برنامه شما به درستی کار کند:

  1. دوربین را باز کنید.
  2. یک پیش‌نمایش را آماده و شروع کنید (اگر برنامه شما ویدیوی در حال ضبط را نشان می‌دهد، که معمولاً همین‌طور است).
  3. با فراخوانی Camera.unlock() قفل دوربین را برای استفاده MediaRecorder باز کنید.
  4. ضبط را با فراخوانی این روش ها در MediaRecorder پیکربندی کنید:
    1. نمونه Camera خود را با setCamera(camera) وصل کنید.
    2. با setAudioSource(MediaRecorder.AudioSource.CAMCORDER) تماس بگیرید.
    3. با setVideoSource(MediaRecorder.VideoSource.CAMERA) تماس بگیرید.
    4. برای تنظیم کیفیت setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) تماس بگیرید. برای همه گزینه های کیفیت CamcorderProfile را ببینید.
    5. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) را فراخوانی کنید.
    6. اگر برنامه شما پیش نمایشی از ویدیو دارد، با setPreviewDisplay(preview?.holder?.surface) تماس بگیرید.
    7. setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) فراخوانی کنید.
    8. با setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) تماس بگیرید.
    9. با setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) تماس بگیرید.
    10. برای نهایی کردن پیکربندی MediaRecorder خود prepare() فراخوانی کنید.
  5. برای شروع ضبط، MediaRecorder.start() را فراخوانی کنید.
  6. برای توقف ضبط، با این روش ها تماس بگیرید. باز هم به این ترتیب دقیق عمل کنید:
    1. MediaRecorder.stop() را فراخوانی کنید.
    2. در صورت تمایل، پیکربندی فعلی MediaRecorder را با فراخوانی MediaRecorder.reset() حذف کنید.
    3. MediaRecorder.release() را فراخوانی کنید.
    4. دوربین را قفل کنید تا جلسات بعدی MediaRecorder بتوانند با فراخوانی Camera.lock() از آن استفاده کنند.
  7. برای توقف پیش‌نمایش، Camera.stopPreview() را فراخوانی کنید.
  8. در نهایت، برای آزاد کردن 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

با CameraX's CameraController ، می‌توانید ImageCapture ، VideoCapture ، و ImageAnalysis UseCase را به‌طور مستقل تغییر دهید، تا زمانی که فهرست UseCase‌ها به طور همزمان قابل استفاده باشد . ImageCapture و ImageAnalysis UseCase به طور پیش فرض فعال هستند، به همین دلیل است که برای گرفتن عکس نیازی به فراخوانی setEnabledUseCases() ندارید.

برای استفاده از CameraController برای ضبط ویدیو، ابتدا باید از setEnabledUseCases() استفاده کنید تا به VideoCapture UseCase اجازه دهید.

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

هنگامی که می خواهید ضبط ویدیو را شروع کنید، می توانید تابع CameraController.startRecording() را فراخوانی کنید. این عملکرد می تواند ویدیوی ضبط شده را در یک File ذخیره کند، همانطور که در مثال زیر مشاهده می کنید. علاوه بر این، باید یک Executor و کلاسی را پاس کنید که OnVideoSavedCallback را پیاده سازی کند تا پاسخگوی موفقیت و خطا را مدیریت کند. وقتی ضبط باید تمام شود، CameraController.stopRecording() را فراخوانی کنید.

توجه: اگر از CameraX 1.3.0-alpha02 یا جدیدتر استفاده می کنید، یک پارامتر AudioConfig اضافی وجود دارد که به شما امکان می دهد ضبط صدا را در ویدیوی خود فعال یا غیرفعال کنید. برای فعال کردن ضبط صدا، باید مطمئن شوید که مجوز میکروفون را دارید. علاوه بر این، متد 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 را با UseCase دیگر خود به CameraProvider متصل کنید.

// 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: VideoCapture
private 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 ، چندین روش برای فراخوانی برای آماده سازی آن وجود دارد. برای تنظیم گزینه های خروجی MediaStore prepareRecording() فراخوانی کنید. اگر برنامه شما اجازه استفاده از میکروفون دستگاه را دارد، 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 در مخزن Camera Samples GitHub خود داریم. این نمونه‌ها به شما نشان می‌دهند که چگونه سناریوهای موجود در این راهنما در یک برنامه اندرویدی کامل قرار می‌گیرند.

اگر برای مهاجرت به CameraX پشتیبانی بیشتری می‌خواهید یا در مورد مجموعه APIهای دوربین Android سؤالی دارید، لطفاً در گروه بحث CameraX با ما تماس بگیرید.