از چندین جریان دوربین به طور همزمان استفاده کنید

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

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

  • ضبط ویدئو : یک جریان برای پیش نمایش، دیگری در حال کدگذاری و ذخیره در یک فایل.
  • اسکن بارکد : یک جریان برای پیش نمایش، دیگری برای تشخیص بارکد.
  • عکاسی محاسباتی : یک جریان برای پیش نمایش، دیگری برای تشخیص چهره/صحنه.

هنگام پردازش فریم ها، هزینه عملکرد غیر پیش پا افتاده ای وجود دارد و هنگام انجام پردازش جریان موازی یا خط لوله، هزینه آن چند برابر می شود.

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

چندین هدف در هر درخواست

چندین جریان دوربین را می توان در یک CameraCaptureRequest ترکیب کرد. قطعه کد زیر نحوه تنظیم یک جلسه دوربین با یک جریان برای پیش نمایش دوربین و یک جریان دیگر برای پردازش تصویر را نشان می دهد:

کاتلین

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

// You will use the preview capture template for the combined streams
// because it is optimized for low latency; for high-quality images, use
// TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD
val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
val combinedRequest = session.device.createCaptureRequest(requestTemplate)

// Link the Surface targets with the combined request
combinedRequest.addTarget(previewSurface)
combinedRequest.addTarget(imReaderSurface)

// In this simple case, the SurfaceView gets updated automatically. ImageReader
// has its own callback that you have to listen to in order to retrieve the
// frames so there is no need to set up a callback for the capture request
session.setRepeatingRequest(combinedRequest.build(), null, null)

جاوا

CameraCaptureSession session = …;  // from CameraCaptureSession.StateCallback

// You will use the preview capture template for the combined streams
// because it is optimized for low latency; for high-quality images, use
// TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD
        CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

// Link the Surface targets with the combined request
        combinedRequest.addTarget(previewSurface);
        combinedRequest.addTarget(imReaderSurface);

// In this simple case, the SurfaceView gets updated automatically. ImageReader
// has its own callback that you have to listen to in order to retrieve the
// frames so there is no need to set up a callback for the capture request
        session.setRepeatingRequest(combinedRequest.build(), null, null);

اگر سطوح هدف را به درستی پیکربندی کنید، این کد فقط جریان هایی تولید می کند که حداقل FPS تعیین شده توسط StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) و StreamComfigurationMap.GetOutputStallDuration(int, Size) را داشته باشند. عملکرد واقعی از دستگاهی به دستگاه دیگر متفاوت است، اگرچه Android برای پشتیبانی از ترکیب‌های خاص بسته به سه متغیر: نوع خروجی ، اندازه خروجی و سطح سخت‌افزار تضمین‌هایی ارائه می‌کند.

استفاده از ترکیب پشتیبانی نشده از متغیرها ممکن است با نرخ فریم پایین کار کند. اگر این کار را انجام ندهد، یکی از تماس های شکست را راه اندازی می کند. مستندات createCaptureSession آنچه را که تضمین شده کار می‌کند، توضیح می‌دهد.

نوع خروجی

نوع خروجی به فرمتی اشاره دارد که فریم ها در آن کدگذاری می شوند. مقادیر ممکن عبارتند از PRIV، YUV، JPEG و RAW. مستندات createCaptureSession آنها را توصیف می کند.

هنگام انتخاب نوع خروجی برنامه، اگر هدف به حداکثر رساندن سازگاری است، از ImageFormat.YUV_420_888 برای تحلیل فریم و ImageFormat.JPEG برای تصاویر ثابت استفاده کنید. برای پیش‌نمایش و سناریوهای ضبط، احتمالاً از SurfaceView ، TextureView ، MediaRecorder ، MediaCodec یا RenderScript.Allocation استفاده می‌کنید. در این موارد، فرمت تصویر را مشخص نکنید. برای سازگاری، بدون توجه به فرمت واقعی استفاده شده در داخل، به عنوان ImageFormat.PRIVATE محاسبه می شود. برای پرس و جو فرمت های پشتیبانی شده توسط دستگاه با توجه به CameraCharacteristics آن، از کد زیر استفاده کنید:

کاتلین

val characteristics: CameraCharacteristics = ...
val supportedFormats = characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats

جاوا

CameraCharacteristics characteristics = …;
        int[] supportedFormats = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();

اندازه خروجی

همه اندازه‌های خروجی موجود توسط StreamConfigurationMap.getOutputSizes() فهرست شده‌اند، اما تنها دو مورد به سازگاری مرتبط هستند: PREVIEW و MAXIMUM . اندازه ها به عنوان مرزهای بالایی عمل می کنند. اگر چیزی با اندازه PREVIEW کار کند، هر چیزی با اندازه کوچکتر از PREVIEW نیز کار خواهد کرد. همین امر برای MAXIMUM نیز صادق است. مستندات CameraDevice این اندازه ها را توضیح می دهد.

اندازه خروجی موجود به انتخاب فرمت بستگی دارد. با توجه به CameraCharacteristics و فرمت، می‌توانید اندازه‌های خروجی موجود را مانند این جستجو کنید:

کاتلین

val characteristics: CameraCharacteristics = ...
val outputFormat: Int = ...  // such as ImageFormat.JPEG
val sizes = characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
    .getOutputSizes(outputFormat)

جاوا

CameraCharacteristics characteristics = …;
        int outputFormat = …;  // such as ImageFormat.JPEG
Size[] sizes = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                .getOutputSizes(outputFormat);

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

کاتلین

val characteristics: CameraCharacteristics = ...
val targetClass: Class <T> = ...  // such as SurfaceView::class.java
val sizes = characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
    .getOutputSizes(targetClass)

جاوا

CameraCharacteristics characteristics = …;
   int outputFormat = …;  // such as ImageFormat.JPEG
   Size[] sizes = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                .getOutputSizes(outputFormat);

برای بدست آوردن MAXIMUM اندازه، اندازه های خروجی را بر اساس مساحت مرتب کنید و بزرگ ترین را برگردانید:

کاتلین

fun <T>getMaximumOutputSize(
    characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null):
    Size {
  val config = characteristics.get(
      CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)

  // If image format is provided, use it to determine supported sizes; or else use target class
  val allSizes = if (format == null)
    config.getOutputSizes(targetClass) else config.getOutputSizes(format)
  return allSizes.maxBy { it.height * it.width }
}

جاوا

 @RequiresApi(api = Build.VERSION_CODES.N)
    <T> Size getMaximumOutputSize(CameraCharacteristics characteristics,
                                            Class <T> targetClass,
                                            Integer format) {
        StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

        // If image format is provided, use it to determine supported sizes; else use target class
        Size[] allSizes;
        if (format == null) {
            allSizes = config.getOutputSizes(targetClass);
        } else {
            allSizes = config.getOutputSizes(format);
        }
        return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get();
    }

PREVIEW به بهترین اندازه مطابقت با وضوح صفحه نمایش دستگاه یا 1080p (1920x1080)، هر کدام کوچکتر اشاره دارد. نسبت تصویر ممکن است دقیقاً با نسبت ابعاد صفحه نمایش مطابقت نداشته باشد، بنابراین ممکن است لازم باشد برای نمایش در حالت تمام صفحه، حروف-باکس یا برش را روی جریان اعمال کنید. برای به دست آوردن اندازه پیش نمایش مناسب، اندازه های خروجی موجود را با اندازه نمایش مقایسه کنید و در عین حال در نظر بگیرید که نمایشگر ممکن است چرخش داشته باشد.

کد زیر یک کلاس کمکی به نام SmartSize تعریف می کند که مقایسه اندازه را کمی آسان تر می کند:

کاتلین

/** Helper class used to pre-compute shortest and longest sides of a [Size] */
class SmartSize(width: Int, height: Int) {
    var size = Size(width, height)
    var long = max(size.width, size.height)
    var short = min(size.width, size.height)
    override fun toString() = "SmartSize(${long}x${short})"
}

/** Standard High Definition size for pictures and video */
val SIZE_1080P: SmartSize = SmartSize(1920, 1080)

/** Returns a [SmartSize] object for the given [Display] */
fun getDisplaySmartSize(display: Display): SmartSize {
    val outPoint = Point()
    display.getRealSize(outPoint)
    return SmartSize(outPoint.x, outPoint.y)
}

/**
 * Returns the largest available PREVIEW size. For more information, see:
 * https://d.android.com/reference/android/hardware/camera2/CameraDevice
 */
fun <T>getPreviewOutputSize(
        display: Display,
        characteristics: CameraCharacteristics,
        targetClass: Class <T>,
        format: Int? = null
): Size {

    // Find which is smaller: screen or 1080p
    val screenSize = getDisplaySmartSize(display)
    val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short
    val maxSize = if (hdScreen) SIZE_1080P else screenSize

    // If image format is provided, use it to determine supported sizes; else use target class
    val config = characteristics.get(
            CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
    if (format == null)
        assert(StreamConfigurationMap.isOutputSupportedFor(targetClass))
    else
        assert(config.isOutputSupportedFor(format))
    val allSizes = if (format == null)
        config.getOutputSizes(targetClass) else config.getOutputSizes(format)

    // Get available sizes and sort them by area from largest to smallest
    val validSizes = allSizes
            .sortedWith(compareBy { it.height * it.width })
            .map { SmartSize(it.width, it.height) }.reversed()

    // Then, get the largest output size that is smaller or equal than our max size
    return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size
}

جاوا

/** Helper class used to pre-compute shortest and longest sides of a [Size] */
    class SmartSize {
        Size size;
        double longSize;
        double shortSize;

        public SmartSize(Integer width, Integer height) {
            size = new Size(width, height);
            longSize = max(size.getWidth(), size.getHeight());
            shortSize = min(size.getWidth(), size.getHeight());
        }

        @Override
        public String toString() {
            return String.format("SmartSize(%sx%s)", longSize, shortSize);
        }
    }

    /** Standard High Definition size for pictures and video */
    SmartSize SIZE_1080P = new SmartSize(1920, 1080);

    /** Returns a [SmartSize] object for the given [Display] */
    SmartSize getDisplaySmartSize(Display display) {
        Point outPoint = new Point();
        display.getRealSize(outPoint);
        return new SmartSize(outPoint.x, outPoint.y);
    }

    /**
     * Returns the largest available PREVIEW size. For more information, see:
     * https://d.android.com/reference/android/hardware/camera2/CameraDevice
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    <T> Size getPreviewOutputSize(
            Display display,
            CameraCharacteristics characteristics,
            Class <T> targetClass,
            Integer format
    ){

        // Find which is smaller: screen or 1080p
        SmartSize screenSize = getDisplaySmartSize(display);
        boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize;
        SmartSize maxSize;
        if (hdScreen) {
            maxSize = SIZE_1080P;
        } else {
            maxSize = screenSize;
        }

        // If image format is provided, use it to determine supported sizes; else use target class
        StreamConfigurationMap config = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if (format == null)
            assert(StreamConfigurationMap.isOutputSupportedFor(targetClass));
        else
            assert(config.isOutputSupportedFor(format));
        Size[] allSizes;
        if (format == null) {
            allSizes = config.getOutputSizes(targetClass);
        } else {
            allSizes = config.getOutputSizes(format);
        }

        // Get available sizes and sort them by area from largest to smallest
        List <Size> sortedSizes = Arrays.asList(allSizes);
        List <SmartSize> validSizes =
                sortedSizes.stream()
                        .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth()))
                        .map(s -> new SmartSize(s.getWidth(), s.getHeight()))
                        .sorted(Collections.reverseOrder()).collect(Collectors.toList());

        // Then, get the largest output size that is smaller or equal than our max size
        return validSizes.stream()
                .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize)
                .findFirst().get().size;
    }

سطح سخت افزار پشتیبانی شده را بررسی کنید

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

با یک شی CameraCharacteristics ، می توانید سطح سخت افزار را با یک عبارت بازیابی کنید:

کاتلین

val characteristics: CameraCharacteristics = ...

// Hardware level will be one of:
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
val hardwareLevel = characteristics.get(
        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)

جاوا

CameraCharacteristics characteristics = ...;

// Hardware level will be one of:
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
// - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
Integer hardwareLevel = characteristics.get(
                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);

قرار دادن تمام قطعات در کنار هم

با نوع خروجی، اندازه خروجی و سطح سخت‌افزار، می‌توانید تعیین کنید که کدام ترکیب از جریان‌ها معتبر هستند. نمودار زیر یک عکس فوری از پیکربندی های پشتیبانی شده توسط CameraDevice با سطح سخت افزار LEGACY است.

هدف 1 هدف 2 هدف 3 نمونه مورد استفاده
تایپ کنید حداکثر اندازه تایپ کنید حداکثر اندازه تایپ کنید حداکثر اندازه
PRIV MAXIMUM پیش نمایش ساده، پردازش ویدیوی GPU یا ضبط ویدیو بدون پیش نمایش.
JPEG MAXIMUM ضبط تصویر ثابت بدون منظره یاب.
YUV MAXIMUM پردازش ویدیو/تصویر درون برنامه
PRIV PREVIEW JPEG MAXIMUM تصویربرداری ثابت استاندارد
YUV PREVIEW JPEG MAXIMUM پردازش درون‌برنامه به‌علاوه عکس‌برداری.
PRIV PREVIEW PRIV PREVIEW ضبط استاندارد
PRIV PREVIEW YUV PREVIEW پیش نمایش به علاوه پردازش درون برنامه ای.
PRIV PREVIEW YUV PREVIEW پیش نمایش به علاوه پردازش درون برنامه ای.
PRIV PREVIEW YUV PREVIEW JPEG MAXIMUM همچنان ضبط به علاوه پردازش درون برنامه ای.

LEGACY پایین ترین سطح سخت افزاری ممکن است. این جدول نشان می‌دهد که هر دستگاهی که از Camera2 (سطح API 21 و بالاتر) پشتیبانی می‌کند، می‌تواند تا سه جریان همزمان را با استفاده از پیکربندی مناسب و در صورتی که عملکرد محدودکننده سربار زیادی مانند حافظه، CPU یا محدودیت‌های حرارتی وجود نداشته باشد، خروجی دهد.

برنامه شما همچنین باید بافرهای خروجی هدفمند را پیکربندی کند. برای مثال، برای هدف قرار دادن دستگاهی با سطح سخت‌افزار LEGACY ، می‌توانید دو سطح خروجی هدف را تنظیم کنید، یکی با استفاده از ImageFormat.PRIVATE و دیگری با استفاده از ImageFormat.YUV_420_888 . این یک ترکیب پشتیبانی شده در حین استفاده از اندازه PREVIEW است. با استفاده از تابعی که قبلا در این مبحث تعریف شده است، دریافت اندازه های پیش نمایش مورد نیاز برای شناسه دوربین به کد زیر نیاز دارد:

کاتلین

val characteristics: CameraCharacteristics = ...
val context = this as Context  // assuming you are inside of an activity

val surfaceViewSize = getPreviewOutputSize(
    context, characteristics, SurfaceView::class.java)
val imageReaderSize = getPreviewOutputSize(
    context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)

جاوا

CameraCharacteristics characteristics = ...;
        Context context = this; // assuming you are inside of an activity

        Size surfaceViewSize = getPreviewOutputSize(
                context, characteristics, SurfaceView.class);
        Size imageReaderSize = getPreviewOutputSize(
                context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);

باید منتظر بمانید تا SurfaceView با استفاده از تماس‌های ارائه شده آماده شود:

کاتلین

val surfaceView = findViewById <SurfaceView>(...)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
  override fun surfaceCreated(holder: SurfaceHolder) {
    // You do not need to specify image format, and it will be considered of type PRIV
    // Surface is now ready and you could use it as an output target for CameraSession
  }
  ...
})

جاوا

SurfaceView surfaceView = findViewById <SurfaceView>(...);

surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
                // You do not need to specify image format, and it will be considered of type PRIV
                // Surface is now ready and you could use it as an output target for CameraSession
            }
            ...
        });

می‌توانید با فراخوانی SurfaceHolder.setFixedSize() SurfaceView را مجبور کنید تا با اندازه خروجی دوربین مطابقت داشته باشد یا می‌توانید رویکردی شبیه به AutoFitSurfaceView از ماژول مشترک نمونه‌های دوربین در GitHub اتخاذ کنید که اندازه مطلق را با در نظر گرفتن هر دو جنبه تعیین می‌کند. نسبت و فضای موجود، در حالی که به طور خودکار زمانی که تغییرات فعالیت فعال می شود، تنظیم می شود.

تنظیم سطح دیگر از ImageReader با فرمت دلخواه آسان تر است، زیرا هیچ تماسی برای انتظار وجود ندارد:

کاتلین

val frameBufferCount = 3  // just an example, depends on your usage of ImageReader
val imageReader = ImageReader.newInstance(
    imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888,
    frameBufferCount)

جاوا

int frameBufferCount = 3;  // just an example, depends on your usage of ImageReader
ImageReader imageReader = ImageReader.newInstance(
                imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888,
                frameBufferCount);

هنگام استفاده از بافر هدف مسدود کننده مانند ImageReader ، پس از استفاده از فریم ها، آنها را دور بیندازید:

کاتلین

imageReader.setOnImageAvailableListener({
  val frame =  it.acquireNextImage()
  // Do something with "frame" here
  it.close()
}, null)

جاوا

imageReader.setOnImageAvailableListener(listener -> {
            Image frame = listener.acquireNextImage();
            // Do something with "frame" here
            listener.close();
        }, null);

سطح سخت افزار LEGACY پایین ترین دستگاه های مخرج مشترک را هدف قرار می دهد. می‌توانید انشعاب شرطی را اضافه کنید و از اندازه RECORD برای یکی از سطوح هدف خروجی در دستگاه‌هایی با سطح سخت‌افزار LIMITED استفاده کنید، یا حتی برای دستگاه‌هایی با سطح سخت‌افزار FULL آن را به MAXIMUM اندازه افزایش دهید.