درخواست‌ها و جلسات ضبط دوربین

توجه: این صفحه به بسته Camera2 اشاره دارد. توصیه می‌کنیم از CameraX استفاده کنید، مگراینکه برنامه‌تان به ویژگی‌های خاص و سطح پایین Camera2 نیاز داشته باشد. ‫CameraX و Camera2 هر دو از Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر پشتیبانی می‌کنند.

یک دستگاه Android می‌تواند چند دوربین داشته باشد. هر دوربین یک CameraDevice است و یک CameraDevice می‌تواند بیش‌از یک جاری‌سازی را به‌طور هم‌زمان خروجی دهد.

یکی از دلایل انجام این کار این است که یک جاری‌سازی، یعنی فریم‌های متوالی دوربین که از CameraDevice می‌آیند، برای کار خاصی مثل نمایش نمایاب بهینه‌سازی شود، درحالی‌که ممکن است از جاری‌سازی‌های دیگر برای گرفتن عکس یا ضبط ویدیو استفاده شود. جریان‌ها به‌عنوان خطوط لوله موازی عمل می‌کنند که فریم‌های خام خارج‌شده از دوربین را پردازش می‌کنند، هر بار یک فریم:

جاری‌سازی‌های موازی که قاب‌های خام را از دستگاه دوربین به قالب‌های خروجی مختلف پردازش می‌کنند
شکل ۱. تصویر از «ساخت برنامه دوربین جهانی» (Google I/O '18)

پردازش موازی نشان می‌دهد که بسته به قدرت پردازش دردسترس از CPU،‏ GPU، یا پردازنده دیگر، ممکن است محدودیت‌های عملکردی وجود داشته باشد. اگر خط لوله نتواند با فریم‌های ورودی همگام شود، شروع به حذف آن‌ها می‌کند.

هر خط لوله قالب خروجی خودش را دارد. داده‌های خام ورودی به‌طور خودکار توسط منطق ضمنی مرتبط با هر خط لوله به قالب خروجی مناسب تبدیل می‌شوند. CameraDevice استفاده‌شده در سراسر نمونه‌های کد این صفحه غیرخاص است، بنابراین ابتدا باید همه دوربین‌های دردسترس را برشمارید تا بتوانید ادامه دهید.

می‌توانید از CameraDevice برای ایجاد CameraCaptureSession استفاده کنید که مختص آن CameraDevice است. CameraDevice باید پیکربندی قاب را برای هر قاب خام بااستفاده از CameraCaptureSession دریافت کند. پیکربندی، ویژگی‌های دوربین مانند فوکوس خودکار، دیافراگم، جلوه‌ها، و نوردهی را مشخص می‌کند. به‌دلیل محدودیت‌های سخت‌افزاری، در هر زمان فقط یک پیکربندی در حسگر دوربین فعال است که به آن پیکربندی فعال می‌گویند.

بااین‌حال، «موارد استفاده از جاری‌سازی» روش‌های قبلی استفاده از CameraDevice را برای جاری‌سازی جلسه‌های ضبط بهبود می‌بخشد و گسترش می‌دهد، که به شما امکان می‌دهد جاری‌سازی دوربین را برای مورد استفاده خاص خود بهینه‌سازی کنید. برای مثال، می‌تواند عمر باتری را هنگام بهینه‌سازی تماس‌های ویدیویی بهبود بخشد.

CameraCaptureSession همه خطوط لوله ممکن را که به CameraDevice محدود شده‌اند توصیف می‌کند. وقتی جلسه‌ای ایجاد می‌شود، نمی‌توانید خطوط لوله را اضافه یا حذف کنید. CameraCaptureSession صفی از CaptureRequest را حفظ می‌کند که به پیکربندی فعال تبدیل می‌شود.

CaptureRequest پیکربندی‌ای را به صف اضافه می‌کند و یک، چند، یا همه خطوط لوله دردسترس را برای دریافت قاب از CameraDevice انتخاب می‌کند. می‌توانید درخواست‌های ضبط زیادی را در طول عمر یک جلسه ضبط ارسال کنید. هر درخواست می‌تواند پیکربندی فعال و مجموعه خطوط لوله خروجی را که تصویر خام را دریافت می‌کنند تغییر دهد.

برای عملکرد بهتر، از «موارد استفاده از جریان» استفاده کنید

«موارد استفاده از جاری‌سازی» روشی برای بهبود عملکرد جلسه‌های ضبط Camera2 است. این حالت‌ها اطلاعات بیشتری به دستگاه سخت‌افزاری می‌دهند تا پارامترها را تنظیم کند، که این امر تجربه دوربین بهتری برای تکلیف خاص شما ارائه می‌دهد.

این کار به دستگاه دوربین اجازه می‌دهد خطوط لوله سخت‌افزار و نرم‌افزار دوربین را براساس سناریوهای کاربر برای هر جاری‌سازی بهینه‌سازی کند. برای اطلاعات بیشتر درباره موارد استفاده از Stream، به setStreamUseCase مراجعه کنید.

«موارد استفاده از جاری‌سازی» به شما امکان می‌دهد نحوه استفاده از یک جاری‌سازی دوربین خاص را با جزئیات بیشتر مشخص کنید، علاوه‌بر تنظیم الگو در CameraDevice.createCaptureRequest(). این کار به سخت‌افزار دوربین اجازه می‌دهد پارامترهایی مثل تنظیم، حالت حسگر، یا تنظیمات حسگر دوربین را براساس کیفیت یا تأخیر مناسب برای موارد استفاده خاص بهینه‌سازی کند.

موارد استفاده از «جاری‌سازی» عبارتند از:

  • DEFAULT: همه رفتارهای موجود برنامه را پوشش می‌دهد. این معادل تنظیم نکردن هیچ «مورد استفاده از جاری‌سازی» است.

  • PREVIEW: برای «نمایاب» یا تجزیه‌وتحلیل تصویر درون‌برنامه توصیه می‌شود.

  • STILL_CAPTURE: برای ضبط با وضوح بالا و کیفیت بالا بهینه‌سازی شده است و انتظار نمی‌رود نرخ فریم‌های شبیه پیش‌نمایش را حفظ کند.

  • ‫VIDEO_RECORD: بهینه‌سازی‌شده برای ضبط ویدیو با کیفیت بالا، ازجمله لرزش‌گیر تصویر با کیفیت بالا، درصورت پشتیبانی دستگاه و فعال بودن توسط برنامه. این گزینه ممکن است فریم‌های خروجی را با تأخیر قابل‌توجهی نسبت به زمان واقعی تولید کند تا امکان تثبیت با بالاترین کیفیت یا پردازش‌های دیگر فراهم شود.

  • VIDEO_CALL: برای استفاده‌های طولانی‌مدت از دوربین که مصرف باتری اهمیت دارد توصیه می‌شود.

  • ‫PREVIEW_VIDEO_STILL: برای برنامه‌های رسانه‌های اجتماعی یا موارد استفاده از جاری‌سازی تکی توصیه می‌شود. این یک جاری‌سازی چندمنظوره است.

  • VENDOR_START: برای موارد استفاده تعریف‌شده توسط OEM استفاده می‌شود.

ایجاد CameraCaptureSession

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

تکه‌کد زیر نشان می‌دهد که چگونه می‌توانید یک جلسه دوربین را با دو میان‌گیر خروجی آماده کنید، یکی متعلق به SurfaceView و دیگری متعلق به ImageReader. افزودن PREVIEW «مورد استفاده جاری‌سازی» به previewSurface و STILL_CAPTURE «مورد استفاده جاری‌سازی» به imReaderSurface به سخت‌افزار دستگاه امکان می‌دهد این جاری‌سازی‌ها را حتی بیشتر بهینه‌سازی کند.

// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
// reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(
    // ...
)
val imageReader = ImageReader.newInstance(
    // ...
)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated().
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready.
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>) {
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    // ...
    device.createCaptureSession(session)
}

در این مرحله، پیکربندی فعال دوربین را تعریف نکرده‌اید. وقتی که جلسه پیکربندی شد، می‌توانید درخواست‌های ضبط را برای انجام این کار ایجاد و ارسال کنید.

تبدیلی که بر ورودی‌ها هنگام نوشته شدن در میان‌گیرشان اعمال می‌شود براساس نوع هر هدف تعیین می‌شود که باید Surface باشد. چارچوب Android می‌داند که چگونه یک تصویر خام را در پیکربندی فعال به قالبی مناسب برای هر هدف تبدیل کند. تبدیل توسط قالب پیکسل و اندازه Surface خاص کنترل می‌شود.

چارچوب تلاش می‌کند بهترین عملکرد را داشته باشد، اما ممکن است برخی‌از ترکیب‌های پیکربندی Surface کار نکنند و مشکلاتی مانند ایجاد نشدن جلسه، ایجاد خطای زمان اجرا هنگام ارسال درخواست، یا کاهش عملکرد را ایجاد کنند. این چارچوب تضمین‌هایی برای ترکیب‌های خاصی از دستگاه، سطح، و پارامترهای درخواست ارائه می‌دهد. اسناد مربوط به createCaptureSession() اطلاعات بیشتری ارائه می‌دهد.

Single CaptureRequests

پیکربندی استفاده‌شده برای هر قاب در CaptureRequest کدبندی می‌شود و به دوربین ارسال می‌شود. برای ایجاد درخواست ضبط، می‌توانید از یکی از الگوهای ازپیش تعریف‌شده استفاده کنید، یا می‌توانید از TEMPLATE_MANUAL برای کنترل کامل استفاده کنید. وقتی الگویی را انتخاب می‌کنید، باید یک یا چند میان‌گیر خروجی برای استفاده با درخواست ارائه دهید. فقط می‌توانید از میانگیرهایی استفاده کنید که قبلاً در جلسه ضبطی که قصد استفاده از آن را دارید تعریف شده‌اند.

درخواست‌های ضبط از الگوی سازنده استفاده می‌کنند و به توسعه‌دهندگان فرصت می‌دهند تا گزینه‌های مختلفی ازجمله نوردهی خودکار، فوکوس خودکار، و گشادگی لنز را تنظیم کنند. قبل‌از تنظیم فیلد، با فراخوانی CameraCharacteristics.getAvailableCaptureRequestKeys() مطمئن شوید که گزینه خاص برای دستگاه دردسترس است و با بررسی مشخصه مناسب دوربین، مانند حالت‌های نوردهی خودکار دردسترس، مطمئن شوید که مقدار انتخاب‌شده پشتیبانی می‌شود.

برای ایجاد درخواست ضبط برای SurfaceView بااستفاده از الگوی طراحی‌شده برای پیش‌نمایش بدون هیچ‌گونه اصلاح، از CameraDevice.TEMPLATE_PREVIEW استفاده کنید:

val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

با تعریف درخواست ضبط، اکنون می‌توانید آن را به جلسه دوربین ارسال کنید:

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest.Builder = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null.
session.capture(captureRequest.build(), null, null)

وقتی قاب خروجی در میان‌گیر خاصی قرار می‌گیرد، فراخوان ضبط راه‌اندازی می‌شود. در بسیاری از موارد، فراخوانی‌های برگشتی اضافی، مانند ImageReader.OnImageAvailableListener، هنگام پردازش قاب حاوی آن راه‌اندازی می‌شود. در این مرحله است که می‌توانید داده‌های تصویر را از میان‌گیر مشخص‌شده بازیابی کنید.

تکرار CaptureRequests

درخواست‌های دوربین تکی به‌راحتی انجام می‌شوند، اما برای نمایش پیش‌نمایش زنده یا ویدیو، چندان مفید نیستند. در این صورت، باید جریانی پیوسته از فریم‌ها را دریافت کنید، نه فقط یک فریم. تکه‌کد زیر نحوه افزودن درخواست تکرارشونده به جلسه را نشان می‌دهد:

val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the session is torn down or session.stopRepeating() is called.
session.setRepeatingRequest(captureRequest.build(), null, null)

درخواست ضبط تکراری باعث می‌شود دستگاه دوربین به‌طور مداوم بااستفاده از تنظیمات ارائه‌شده در CaptureRequest، تصویر ضبط کند. Camera2 API همچنین به کاربران اجازه می‌دهد با ارسال CaptureRequests تکراری، از دوربین ویدیو ضبط کنند، همان‌طور که در این مخزن نمونه Camera2 در GitHub دیده می‌شود. همچنین می‌تواند با ضبط ویدیو با سرعت بالا (حرکت آهسته) بااستفاده از CaptureRequests پشت‌سرهم تکراری، ویدیو حرکت آهسته ارائه دهد، همان‌طور که در برنامه نمونه ویدیو حرکت آهسته Camera2 در GitHub نشان داده شده است.

درهم‌بافی CaptureRequests

برای ارسال درخواست ضبط دوم درحالی‌که درخواست ضبط تکراری فعال است، مثلاً برای نمایش نمایاب و اجازه دادن به کاربران برای گرفتن عکس، نیازی نیست درخواست تکراری درحال انجام را متوقف کنید. درعوض، درخواست ضبط غیرتکراری صادر می‌کنید درحالی‌که درخواست تکراری همچنان اجرا می‌شود.

هر میان‌گیر خروجی استفاده‌شده باید به‌عنوان بخشی از جلسه دوربین هنگام ایجاد اولیه جلسه پیکربندی شود. درخواست‌های تکراری اولویت کمتری نسبت به درخواست‌های تک‌فریم یا پشت‌سرهم دارند، که باعث می‌شود نمونه زیر کار کند:

val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it.
val repeatingRequest = session.device.createCaptureRequest(
    CameraDevice.TEMPLATE_PREVIEW
)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it.
// NOTE: This can disrupt the ongoing repeating request momentarily.
val singleRequest = session.device.createCaptureRequest(
    CameraDevice.TEMPLATE_STILL_CAPTURE
)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

بااین‌حال، این رویکرد یک اشکال دارد: شما دقیقاً نمی‌دانید که درخواست تکی چه زمانی رخ می‌دهد. در شکل زیر، اگر A درخواست ضبط تکرارشونده و B درخواست ضبط تک‌فریم باشد، جلسه صف درخواست را به این صورت پردازش می‌کند:

صف درخواست ضبط تکرارشونده فعال با یک درخواست ضبط تکی قطع شد
شکل ۲. تصویر صف درخواست برای جلسه دوربین درحال انجام

هیچ تضمینی برای تأخیر بین آخرین درخواست تکراری از A قبل‌از فعال شدن درخواست B و دفعه بعدی که دوباره از A استفاده می‌شود وجود ندارد، بنابراین ممکن است برخی‌از فریم‌ها رد شوند. برای کاهش این مشکل می‌توانید کارهایی انجام دهید:

  • هدف‌های خروجی را از درخواست الف به درخواست ب اضافه کنید. به این ترتیب، وقتی قاب B آماده شد، در هدف‌های خروجی A کپی می‌شود. برای مثال، این کار هنگام گرفتن تصویر کلی ویدیو برای حفظ نرخ فریم ثابت ضروری است. در کد قبلی، singleRequest.addTarget(previewSurface) را قبل‌از ساختن درخواست اضافه می‌کنید.

  • از ترکیبی از الگوهای طراحی‌شده برای کار در این سناریو خاص، مانند تأخیر صفر شاتر، استفاده کنید.