Cơ chế thông báo sự kiện và khung

Trong bản phát hành trước của Hệ thống quan sát bên ngoài (EVS), giao diện IEvsCameraStream đã xác định một phương thức gọi lại duy nhất để chỉ phân phối các khung hình video đã chụp. Mặc dù việc triển khai ứng dụng dịch vụ EVS đơn giản này, nhưng cũng khiến ứng dụng khó xác định mọi sự cố phát trực tuyến và do đó, khó xử lý chúng đúng cách. Để nâng cao trải nghiệm phát triển EVS, AOSP hiện chứa một lệnh gọi lại bổ sung để phân phối các sự kiện phát trực tuyến.

package android.hardware.automotive.evs@1.1;

import @1.0::IEvsCameraStream;

/**
 * Implemented on client side to receive asynchronous video frame deliveries.
 */
interface IEvsCameraStream extends @1.0::IEvsCameraStream {
    /**
     * Receives calls from the HAL each time a video frame is ready for inspection.
     * Buffer handles received by this method must be returned via calls to
     * IEvsCamera::doneWithFrame_1_1(). When the video stream is stopped via a call
     * to IEvsCamera::stopVideoStream(), this callback may continue to happen for
     * some time as the pipeline drains. Each frame must still be returned.
     * When the last frame in the stream has been delivered, STREAM_STOPPED
     * event must be delivered. No further frame deliveries may happen
     * thereafter.
     *
     * @param buffer a buffer descriptor of a delivered image frame.
     */
    oneway deliverFrame_1_1(BufferDesc buffer);

    /**
     * Receives calls from the HAL each time an event happens.
     *
     * @param  event EVS event with possible event information.
     */
    oneway notify(EvsEvent event);
};

Phương thức này phân phối EvsEventDesc bao gồm ba trường:

  • Loại sự kiện.
  • Chuỗi để xác định nguồn gốc của sự kiện.
  • Dữ liệu từ 4x 32 bit để chứa thông tin sự kiện có thể có.
/**
 * Structure that describes informative events occurred during EVS is streaming
 */
struct EvsEvent {
    /**
     * Type of an informative event
     */
    EvsEventType aType;
    /**
     * Device identifier
     */
    string deviceId;
    /**
     * Possible additional information
     */
    uint32_t[4] payload;
};

Ngoài ra, để tránh mọi sự khác biệt trong nội dung mô tả vùng đệm đồ hoạ giữa EVS và các thành phần đồ hoạ khác của Android, BufferDesc đã được xác định lại để sử dụng HardwareBuffer được nhập từ giao diện android.hardware.graphics.common@1.2. HardwareBuffer chứa HardwareBufferDescription, là đối tác HIDL của AHardwareBuffer_Desc của Android NDK, với một tay điều khiển vùng đệm.

/**
 * HIDL counterpart of AHardwareBuffer_Desc.
 *
 * An AHardwareBuffer_Desc object can be converted to and from a
 * HardwareBufferDescription object by memcpy().
 *
 * @sa +ndk libnativewindow#AHardwareBuffer_Desc.
 */
typedef uint32_t[10] HardwareBufferDescription;

/**
 * HIDL counterpart of AHardwareBuffer.
 *
 * AHardwareBuffer_createFromHandle() can be used to convert a HardwareBuffer
 * object to an AHardwareBuffer object.
 *
 * Conversely, AHardwareBuffer_getNativeHandle() can be used to extract a native
 * handle from an AHardwareBuffer object. Paired with AHardwareBuffer_Desc,
 * AHardwareBuffer_getNativeHandle() can be used to convert between
 * HardwareBuffer and AHardwareBuffer.
 *
 * @sa +ndk libnativewindow#AHardwareBuffer".
 */
struct HardwareBuffer {
    HardwareBufferDescription description;
    handle nativeHandle;
}

/**
 * Structure representing an image buffer through our APIs
 *
 * In addition to the handle to the graphics memory, need to retain
 * the properties of the buffer for easy reference and reconstruction of
 * an ANativeWindowBuffer object on the remote side of API calls.
 * Not least because OpenGL expect an ANativeWindowBuffer* for us as a
 * texture via eglCreateImageKHR().
 */
struct BufferDesc {
    /**
     * HIDL counterpart of AHardwareBuffer_Desc. Please see
     * hardware/interfaces/graphics/common/1.2/types.hal for more details.
     */
    HardwareBuffer buffer;
    /**
     * The size of a pixel in the units of bytes
     */
    uint32_t pixelSize;
    /**
     * Opaque value from driver
     */
    uint32_t bufferId;
    /**
     * Unique identifier of the physical camera device that produces this buffer.
     */
    string deviceId;
    /**
     * Time that this buffer is being filled
     */
    int64_t timestamp;
    /**
     * Frame metadata. This is opaque to EVS manager
     */
    vec<uint8_t> metadata
};

Lưu ý: HardwareBufferDescription được xác định là một mảng gồm 10 từ 32 bit. Bạn có thể truyền giá trị này dưới dạng loại AHardwareBuffer_Desc và điền nội dung.

EvsEventDesc là một cấu trúc của enum EvsEventType, liệt kê một số sự kiện phát trực tuyến và tải trọng từ 32 bit, trong đó nhà phát triển có thể đặt thêm thông tin. Ví dụ: nhà phát triển có thể đặt mã lỗi cho sự kiện lỗi phát trực tuyến.

/**
 * Types of informative streaming events
 */
enum EvsEventType : uint32_t {
    /**
     * Video stream is started
     */
    STREAM_STARTED = 0,
    /**
     * Video stream is stopped
     */
    STREAM_STOPPED,
    /**
     * Video frame is dropped
     */
    FRAME_DROPPED,
    /**
     * Timeout happens
     */
    TIMEOUT,
    /**
     * Camera parameter is changed; payload contains a changed parameter ID and
     * its value
     */
    PARAMETER_CHANGED,
    /**
     * Master role has become available
     */
    MASTER_RELEASED,
};

Phân phối khung hình

Với BufferDesc mới, IEvsCameraStream cũng giới thiệu các phương thức gọi lại mới để nhận các khung và sự kiện truyền trực tuyến từ các hoạt động triển khai dịch vụ.

/**
 * Implemented on client side to receive asynchronous streaming event deliveries.
 */
interface IEvsCameraStream extends @1.0::IEvsCameraStream {
   /**
    * Receives calls from the HAL each time video frames are ready for inspection.
    * Buffer handles received by this method must be returned via calls to
    * IEvsCamera::doneWithFrame_1_1(). When the video stream is stopped via a call
    * to IEvsCamera::stopVideoStream(), this callback may continue to happen for
    * some time as the pipeline drains. Each frame must still be returned.
    * When the last frame in the stream has been delivered, STREAM_STOPPED
    * event must be delivered. No further frame deliveries may happen
    * thereafter.
    *
    * A camera device delivers the same number of frames as number of
    * backing physical camera devices; it means, a physical camera device
    * sends always a single frame and a logical camera device sends multiple
    * frames as many as the number of backing physical camera devices.
    *
    * @param buffer Buffer descriptors of delivered image frames.
    */
   oneway deliverFrame_1_1(vec<BufferDesc> buffer);

   /**
    * Receives calls from the HAL each time an event happens.
    *
    * @param  event EVS event with possible event information.
    */
   oneway notify(EvsEventDesc event);
};

Phiên bản mới hơn của phương thức gọi lại khung được thiết kế để phân phối nhiều chỉ số mô tả vùng đệm. Do đó, việc triển khai máy ảnh EVS có thể chuyển tiếp nhiều khung hình bằng một lệnh gọi duy nhất nếu máy ảnh quản lý nhiều nguồn.

Ngoài ra, giao thức trước đó để thông báo về việc kết thúc luồng (đang gửi khung rỗng) đã ngừng hoạt động và được thay thế bằng sự kiện STREAM_STOPPED.

Sơ đồ trình tự thông báo sự kiện

Hình 1. Sơ đồ trình tự thông báo sự kiện

Sử dụng cơ chế thông báo sự kiện và khung

Xác định phiên bản IEvsCameraStream do ứng dụng triển khai

Dịch vụ có thể xác định phiên bản giao diện IEvsCameraStream sắp tới mà ứng dụng triển khai bằng cách cố gắng chuyển xuống:

using IEvsCameraStream_1_0 =
    ::android::hardware::automotive::evs::V1_0::IEvsCameraStream;
using IEvsCameraStream_1_1 =
    ::android::hardware::automotive::evs::V1_1::IEvsCameraStream;

Return<EvsResult> EvsV4lCamera::startVideoStream(
    const sp<IEvsCameraStream_1_0>& stream)  {

    IEvsCameraStream_1_0 aStream = stream;
    // Try to downcast. This succeeds if the client implements
    // IEvsCameraStream v1.1.
    IEvsCameraStream_1_1 aStream_1_1 =
        IEvsCameraStream_1_1::castFrom(aStream).withDefault(nullptr);
    if (aStream_1_1 == nullptr) {
        ALOGI("Start a stream for v1.0 client.");
    } else {
        ALOGI("Start a stream for v1.1 client.");
    }

    // Start a video stream
    ...
}

Lệnh gọi lại notify()

EvsEvent được truyền qua lệnh gọi lại notify() và sau đó ứng dụng có thể xác định loại của lệnh gọi đó dựa trên giá trị phân biệt, như minh hoạ dưới đây:

Return<void> StreamHandler::notify(const EvsEvent& event) {
    ALOGD("Received an event id: %u", event.aType);
    // Handle each received event.
    switch(event.aType) {
        case EvsEventType::ERROR:
            // Do something to handle an error
            ...
            break;
        [More cases]
    }
    return Void();
}

Sử dụng BufferDesc

AHardwareBuffer_Desc là loại dữ liệu của Android NDK để biểu thị vùng đệm phần cứng gốc có thể liên kết với các đối tượng gốc EGL/OpenGL và Vulkan. Tệp này chứa hầu hết siêu dữ liệu bộ đệm từ BufferDesc EVS trước đó, do đó, sẽ thay thế siêu dữ liệu đó trong định nghĩa BufferDesc mới. Tuy nhiên, vì thuộc tính này được xác định là một mảng trong giao diện HIDL, nên bạn không thể trực tiếp lập chỉ mục các biến thành viên. Thay vào đó, bạn có thể truyền mảng dưới dạng một loại AHardwareBuffer_Desc, như minh hoạ dưới đây:

BufferDesc bufDesc = {};
AHardwareBuffer_Desc* pDesc =
    reinterpret_cast<AHardwareBuffer_Desc *>(&bufDesc.buffer.description);
pDesc->width  = mVideo.getWidth();
pDesc->height = mVideo.getHeight();
pDesc->layers = 1;
pDesc->format = mFormat;
pDesc->usage  = mUsage;
pDesc->stride = mStride;
bufDesc_1_1.buffer.nativeHandle = mBuffers[idx].handle;
bufDesc_1_1.bufferId = idx;