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

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

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

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

شکل 1. تصویری از ساختن یک برنامه دوربین جهانی (Google I/O '18)

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

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

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

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

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

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

برای عملکرد بهتر از Stream Use Cases استفاده کنید

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

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

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

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

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

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

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

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

  • VIDEO_CALL : برای استفاده طولانی مدت از دوربین که در آن تخلیه برق نگران کننده است، توصیه می شود.

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

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

CameraCaptureSession ایجاد کنید

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

قطعه کد زیر نشان می دهد که چگونه می توانید یک جلسه دوربین با دو بافر خروجی، یکی متعلق به SurfaceView و دیگری به ImageReader آماده کنید. اضافه کردن PREVIEW Stream Use Case برای previewSurface و STILL_CAPTURE Stream Use Case به 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() اطلاعات بیشتری را ارائه می دهد.

Single CaptureRequests

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

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

برای ایجاد یک درخواست عکسبرداری برای SurfaceView با استفاده از الگوی طراحی شده برای پیش نمایش بدون هیچ گونه تغییری، از CameraDevice.TEMPLATE_PREVIEW استفاده کنید.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 ، هنگامی که فریم موجود در آن پردازش می‌شود، فعال می‌شود. در این مرحله است که می توانید داده های تصویر را از بافر مشخص شده بازیابی کنید.

CaptureRequests را تکرار کنید

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

کاتلین

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 به نمایش گذاشته شده است، ویدیوی حرکت آهسته را ارائه کند.

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)

جاوا

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

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

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

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

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