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

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

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

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

شکل ۱. تصویرسازی از «ساختن برنامه دوربین جهانی» (Google I/O‏ ۲۰۱۸)

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

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

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

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

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

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

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

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

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

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

موارد استفاده از «جاری‌سازی» شامل موارد زیر می‌شود:

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

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

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

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

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

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

  • ‫VENDOR_START: برای موارد استفاده تعریف‌شده توسط سازنده تجهیزات اصلی استفاده می‌شود.

ایجاد 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)
}

جاوا

// 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. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// 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
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

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

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

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

تک‌درخواست‌های ضبط

پیکربندی استفاده‌شده برای هر قاب در 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)

جاوا

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

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

کاتلین

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

جاوا

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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، زمانی فعال می‌شود که چارچوبی که دربردارد پردازش شود. در این نقطه می‌توانید داده‌های تصویر را از بافر مشخص‌شده بازیابی کنید.

تکرار درخواست‌های ضبط

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

کاتلین

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

جاوا

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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 نمایش داده شده است.

درهم‌آمیزی کردن «درخواست‌های ضبط»

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

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

کاتلین

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)

جاوا

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().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
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

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

شکل ۲. تصویر صف درخواست برای جلسه دوربین جاری

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

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

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