イベントとフレームの通知メカニズム

前回の車外表示システム(EVS)のリリースでは、IEvsCameraStream インターフェースが、キャプチャされた動画フレームのみを配信する単一のコールバック メソッドを定義していました。これは EVS サービス クライアントの実装を簡素化する一方で、クライアントがストリーミング インシデントを特定し、適切に処理することを困難にしていました。EVS 開発の利便性のため、AOSP にはストリーミング イベントを配信する追加のコールバックが含まれるようになりました。

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

このメソッドは、次の 3 つのフィールドで構成される EvsEventDesc を提供します。

  • イベントのタイプ。
  • イベントの発生元を識別する文字列。
  • イベント情報を格納する、32 ビット単語データ x 4。
/**
 * 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;
};

さらに、EVS と他の Android グラフィカル コンポーネントとの間のグラフィック バッファの記述にばらつきが生じないよう、android.hardware.graphics.common@1.2 のインターフェースからインポートされた HardwareBuffer を使用するように BufferDesc を再定義しました。HardwareBuffer には HardwareBufferDescription が含まれています。これは、Android NDKAHardwareBuffer_Desc に対する HIDL の等価物であり、バッファ ハンドルが含まれます。

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

注: HardwareBufferDescription は 32 ビット単語 10 個の配列として定義されます。AHardwareBuffer_Desc 型としてキャストし、その内容を入力できます。

EvsEventDesc は、複数のストリーミング イベントをリストする enum EvsEventType と、デベロッパーが追加情報を格納できる 32 ビット単語のペイロードの構造体です。たとえば、デベロッパーはストリーミング エラーイベントのエラーコードを格納できます。

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

フレーム配信

新しい BufferDesc により、IEvsCameraStream にはフレームとストリーミング イベントをサービス実装から受信する新しいコールバック メソッドも導入されます。

/**
 * 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 will deliver 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);
};

フレーム コールバック メソッドの新しいバージョンは、複数のバッファ記述子を処理するように設計されています。したがって、EVS カメラの実装では、複数のソースを管理する場合、1 回の呼び出しで複数のフレームを転送できます。

また、null フレームの送信でストリームの終了を通知する以前のプロトコルは非推奨になり、STREAM_STOPPED イベントに置き換わりました。

イベント通知シーケンスの図

図 1. イベント通知シーケンスの図

イベントとフレームの通知メカニズムの使用

クライアントによって実装された IEvsCameraStream のバージョンの特定

このサービスでは、ダウンキャストを試行して、クライアントが実装した受信 IEvsCameraStream インターフェースのバージョンを識別できます。

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 will succeed 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
    ...
}

notify() コールバック

EvsEventnotify() コールバックを通じて渡されます。クライアントは以下のように、弁別子に基づいてタイプを識別できます。

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

BufferDesc の使用

AHardwareBuffer_Desc は、EGL/OpenGL と Vulkan プリミティブにバインドできるネイティブ ハードウェア バッファを表す Android NDK のデータ型です。これには以前の EVS BufferDesc のバッファ メタデータのほとんどが含まれているため、新しい定義の BufferDesc に置き換わります。ただし、これは HIDL インターフェースの配列として定義されているため、メンバー変数を添え字で直接操作することはできません。代わりに、次のように、配列を AHardwareBuffer_Desc の型としてキャストできます。

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;