ใช้สตรีมจากกล้องหลายตัวพร้อมกัน

หมายเหตุ: หน้านี้อ้างอิงถึงแพ็กเกจ Camera2 เราขอแนะนำให้ใช้ CameraX เว้นแต่ว่าแอปของคุณต้องใช้ฟีเจอร์ระดับต่ำที่เฉพาะเจาะจงจาก Camera2 ทั้ง CameraX และ Camera2 รองรับ Android 5.0 (API ระดับ 21) ขึ้นไป

แอปพลิเคชันกล้องสามารถใช้เฟรมมากกว่า 1 สตรีมพร้อมกันได้ ในบางกรณี สตรีมที่ต่างกันอาจต้องใช้ความละเอียดเฟรมหรือรูปแบบพิกเซลที่ต่างกันด้วย กรณีการใช้งานทั่วไปมีดังนี้

  • การบันทึกวิดีโอ: สตรีมหนึ่งสำหรับการแสดงตัวอย่าง และอีกสตรีมหนึ่งจะได้รับการเข้ารหัสและบันทึกลงในไฟล์
  • การสแกนบาร์โค้ด: สตรีมหนึ่งสำหรับการแสดงตัวอย่าง และอีกสตรีมหนึ่งสำหรับการตรวจจับบาร์โค้ด
  • การถ่ายภาพแบบดิจิทัล: สตรีมหนึ่งสำหรับแสดงตัวอย่าง และอีกสตรีมหนึ่งสำหรับการตรวจจับใบหน้าหรือฉาก

การประมวลผลเฟรมมีต้นทุนด้านประสิทธิภาพที่สำคัญ และต้นทุนจะเพิ่มขึ้นเมื่อประมวลผลสตรีมหรือไปป์ไลน์แบบขนาน

ทรัพยากรต่างๆ เช่น CPU, GPU และ DSP อาจใช้ประโยชน์จากความสามารถในการประมวลผลซ้ำของเฟรมเวิร์กได้ แต่ทรัพยากรอย่างหน่วยความจำจะเพิ่มขึ้นแบบเชิงเส้น

เป้าหมายหลายรายการต่อคำขอ

คุณสามารถรวมสตรีมจากกล้องหลายรายการไว้ใน CameraCaptureRequest เดียวได้ ข้อมูลโค้ดต่อไปนี้แสดงวิธีตั้งค่าเซสชันกล้องที่มีสตรีม 1 รายการสำหรับตัวอย่างภาพจากกล้องและสตรีมอีก 1 รายการสำหรับการประมวลผลรูปภาพ

Kotlin

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 basic 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)

Java

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 basic 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 จะรับประกันการรองรับชุดค่าผสมที่เฉพาะเจาะจงโดยขึ้นอยู่กับตัวแปร 3 ตัว ได้แก่ ประเภทเอาต์พุต ขนาดเอาต์พุต และระดับฮาร์ดแวร์

การใช้ตัวแปรที่ใช้ร่วมกันซึ่งไม่รองรับอาจทำงานที่อัตราเฟรมต่ำ หากไม่เป็นเช่นนั้น ระบบจะทริกเกอร์ Callback ที่ไม่สำเร็จรายการใดรายการหนึ่ง เอกสารประกอบสำหรับ createCaptureSession อธิบายสิ่งที่รับประกันว่าจะทำงานได้

ประเภทเอาต์พุต

ประเภทเอาต์พุตหมายถึงรูปแบบที่ใช้เข้ารหัสเฟรม ค่าที่เป็นไปได้คือ PRIV, YUV, JPEG และ RAW เอกสารประกอบของ createCaptureSession จะอธิบายเกี่ยวกับเรื่องนี้

เมื่อเลือกประเภทเอาต์พุตของแอปพลิเคชัน หากเป้าหมายคือการเพิ่มความเข้ากันได้สูงสุด ให้ใช้ ImageFormat.YUV_420_888 สำหรับการวิเคราะห์เฟรม และ ImageFormat.JPEG สำหรับภาพนิ่ง สำหรับสถานการณ์การแสดงตัวอย่างและการบันทึก คุณน่าจะใช้ SurfaceView, TextureView, MediaRecorder, MediaCodec หรือ RenderScript.Allocation ในกรณีดังกล่าว โปรดอย่าระบุรูปแบบรูปภาพ เพื่อความเข้ากันได้ ระบบจะนับเป็น ImageFormat.PRIVATE ไม่ว่ารูปแบบจริงที่ใช้ภายในจะเป็นอย่างไร หากต้องการค้นหารูปแบบที่อุปกรณ์รองรับโดยระบุ CameraCharacteristics ให้ใช้โค้ดต่อไปนี้

Kotlin

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

Java

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

ขนาดเอาต์พุต

ขนาดเอาต์พุตทั้งหมดที่ใช้ได้จะแสดงตาม StreamConfigurationMap.getOutputSizes() แต่มีเพียง 2 ขนาดเท่านั้นที่เกี่ยวข้องกับความเข้ากันได้ ได้แก่ PREVIEW และ MAXIMUM ขนาดจะทำหน้าที่เป็นขอบเขตบน หากขนาด PREVIEW ใช้งานได้ ขนาดที่เล็กกว่า PREVIEW ก็จะใช้งานได้เช่นกัน MAXIMUM ก็เช่นกัน เอกสารประกอบของ CameraDevice จะอธิบายขนาดเหล่านี้

ขนาดเอาต์พุตที่ใช้ได้จะขึ้นอยู่กับรูปแบบที่เลือก เมื่อมี CameraCharacteristics และรูปแบบ คุณจะค้นหาขนาดเอาต์พุตที่ใช้ได้ดังนี้

Kotlin

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

Java

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

ในกรณีการใช้งานการแสดงตัวอย่างและการบันทึกของกล้อง ให้ใช้คลาสเป้าหมายเพื่อกำหนดขนาดที่รองรับ เฟรมเวิร์กของกล้องจะจัดการรูปแบบเอง ดังนี้

Kotlin

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

Java

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

หากต้องการรับขนาด MAXIMUM ให้จัดเรียงขนาดเอาต์พุตตามพื้นที่และแสดงผลขนาดที่ใหญ่ที่สุด ดังนี้

Kotlin

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 }
}

Java

@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) แล้วแต่ว่าค่าใดจะน้อยกว่า สัดส่วนภาพอาจไม่ตรงกับสัดส่วนภาพของหน้าจอพอดี คุณจึงอาจต้องใช้ Letterbox หรือครอบตัดสตรีมเพื่อแสดงในโหมดเต็มหน้าจอ หากต้องการได้ขนาดตัวอย่างที่เหมาะสม ให้เปรียบเทียบขนาดเอาต์พุตที่มีกับขนาดการแสดงผล โดยคำนึงว่าการแสดงผลอาจหมุนได้

โค้ดต่อไปนี้จะกำหนดคลาสตัวช่วย SmartSize ซึ่งจะทำให้การเปรียบเทียบขนาดง่ายขึ้นเล็กน้อย

Kotlin

/** 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
}

Java

/** 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

เมื่อใช้ออบเจ็กต์ CameraCharacteristics คุณจะดึงข้อมูลระดับฮาร์ดแวร์ได้ด้วยคำสั่งเดียว ดังนี้

Kotlin

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)

Java

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 JPEG MAXIMUM การจับภาพนิ่งและการประมวลผลในแอป

LEGACY เป็นระดับฮาร์ดแวร์ต่ำสุดที่เป็นไปได้ ตารางนี้แสดงให้เห็นว่าอุปกรณ์ทุกเครื่องที่รองรับ Camera2 (ระดับ API 21 ขึ้นไป) สามารถแสดงผลสตรีมพร้อมกันได้สูงสุด 3 รายการโดยใช้การกำหนดค่าที่เหมาะสม และหากไม่มีโอเวอร์เฮดที่จำกัดประสิทธิภาพมากเกินไป เช่น ข้อจำกัดด้านหน่วยความจำ, CPU หรือความร้อน

นอกจากนี้ แอปของคุณยังต้องกำหนดค่าบัฟเฟอร์เอาต์พุตเป้าหมายด้วย ตัวอย่างเช่น หากต้องการกำหนดเป้าหมายอุปกรณ์ที่มีระดับฮาร์ดแวร์ LEGACY คุณสามารถตั้งค่าพื้นผิวเอาต์พุตเป้าหมาย 2 รายการ โดยรายการหนึ่งใช้ ImageFormat.PRIVATE และอีกรายการใช้ ImageFormat.YUV_420_888 นี่คือชุดค่าผสมที่รองรับขณะใช้ขนาด PREVIEW การใช้ฟังก์ชันที่กำหนดไว้ก่อนหน้านี้ในเอกสารนี้ การรับขนาดตัวอย่างที่จำเป็นสำหรับรหัสกล้องต้องใช้โค้ดต่อไปนี้

Kotlin

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)

Java

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 จะพร้อมใช้งานโดยใช้ Callback ที่ระบุไว้

Kotlin

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
  }
  ...
})

Java

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
            }
            ...
        });

คุณสามารถบังคับให้ SurfaceView ตรงกับขนาดเอาต์พุตของกล้องได้โดยเรียกใช้ SurfaceHolder.setFixedSize() หรือจะใช้วิธีการที่คล้ายกับ AutoFitSurfaceView จากโมดูลทั่วไปของตัวอย่างกล้องใน GitHub ซึ่งจะกำหนดขนาดสัมบูรณ์โดยพิจารณาทั้งสัดส่วนภาพและพื้นที่ว่าง พร้อมทั้งปรับโดยอัตโนมัติเมื่อมีการทริกเกอร์การเปลี่ยนแปลงกิจกรรม

การตั้งค่าแพลตฟอร์มอื่นจาก ImageReader ด้วยรูปแบบที่เลือกจะง่ายกว่า เนื่องจากไม่ต้องรอ Callback

Kotlin

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)

Java

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 ให้ทิ้งเฟรมหลังจากใช้แล้ว

Kotlin

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

Java

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

ระดับฮาร์ดแวร์ LEGACY กำหนดเป้าหมายเป็นอุปกรณ์ที่มีตัวคูณร่วมน้อยที่สุด คุณสามารถเพิ่มการแยกสาขาแบบมีเงื่อนไขและใช้ขนาด RECORD สำหรับพื้นผิวเป้าหมายเอาต์พุตรายการใดรายการหนึ่งในอุปกรณ์ที่มีระดับฮาร์ดแวร์ LIMITED หรือแม้แต่เพิ่มเป็นขนาด MAXIMUM สำหรับอุปกรณ์ที่มีระดับฮาร์ดแวร์ FULL