入力プラグインAPI

このガイドではWikitude SDK ネイティブの入力プラグインAPIの概念と制約について説明します。サンプルアプリケーションのソースコードは長くて複雑になるためコード全体は説明せずに、関連するソースコードを抜粋して説明します。入力プラグインAPIは、プラグインAPIを拡張したものなのでプラグインAPIを理解しておくことをお勧めします。

WIKITUDE SDK 入力プラグインAPIについて

入力プラグインAPIはWikitude SDK ネイティブの入力と出力を変更するために使用されます。入力の場合、任意のソースのカスタムフレームデータはWikitude SDK ネイティブ APIの入力処理として供給されます。一方、出力の場合は、Wikitude SDK ネイティブ APIの初期レンダリングの代わりにより高度な実装が可能です。

入力プラグインは、SDKの起動前、またはSDKの実行中に登録できます。 Wikitude SDKが起動する前に登録されている場合、内部Wikitudeカメラの実装はまったく開始されず、Wikitude SDKは最初から入力プラグインを使用し始めます。 実行時に入力プラグインが登録されている場合、内部Wikitude SDKカメラが最初に停止され、続いて新しく登録された入力プラグインに移行されます。

PLUGINクラス

class InputPlugin: public Plugin {
public:
    using InputFrameAvailableNotifier = std::function<int(long frameId, std::shared_ptr<unsigned char> frameData)>;

public:
    InputPlugin(std::string identifier_);
    virtual ~InputPlugin();

    virtual bool requestsInputFrameRendering();
    virtual bool requestsInputFrameProcessing();

    void notifyNewInputFrame(long frameId_, std::shared_ptr<unsigned char> inputFrame_, bool managedFromOutside_ = false);

    InputRenderSettings& getRenderSettings();
    InputFrameSettings& getFrameSettings();
    virtual void prepareRenderingOfInputFrame(long frameId_);

    virtual std::shared_ptr<unsigned char> getPresentableInputFrameData();

    virtual void internalError(const std::string& errorMessage);
    void setInputFrameAvailableNotifier(InputFrameAvailableNotifier newInputFrameAvailableNotifier);

private:
    InputFrameAvailableNotifier                     _newInputFrameAvailableNotifier;

    InputFrameRenderSettings                        _renderSettings;
    InputFrameSettings                              _frameSettings;
    std::unique_ptr<InputFrameBufferController>     _inputFrameBufferController;
};

InputPluginクラスはPluginクラスから派生されます。したがって、InputPluginは通常のプラグインと同様に処理ができます。そのため、プラグインのインスタンス化と登録は同じであるため、このガイドで説明を省略します。詳細については、プラグインAPIを参照してください。このガイドでは、新規機能の使用方法のみを紹介します。

カラースペース

getFrameSettings.setInputFrameColorSpace(wikitude::sdk::FrameColorSpace::YUV_420_NV21);
Wikitude SDK ネイティブでは、FrameColorSpace::RGB値に対してRGBフレームデータを受け取り、FrameColorSpace::YUV_420_NV21に対して4:2:0 NV21形式のYUVデータを受け取ります。前者では、フレームデータのサイズがframeWidth * frameHeight * 3バイトで、後者ではフレームデータのサイズがframeWidth * frameHeight * 3/2バイトです。

視野

getFrameSettings.setFrameFieldOfView(frameFieldOfView)

setFrameFieldOfView関数のパラメータは、カメラ撮影したフレームの水平視野角を示すfloat型である必要があります。この値により、Wikitudeコンピュータビジョンエンジンはフレーム内にターゲットを正常に認識およびトラッキングできます。視野の値はデバイスによって異なるので、代表値を確保するためにこの値をフレームのソースから直接クエリすることをお勧めします。入力画像や入力動画の場合、この値は対応するメタデータから認識できる必要があります。入力カメラストリームのために、この値は対応するカメラのAPIからアクセスしなければなりません。

フレームのサイズ

getFrameSettings.setInputFrameSize(inputFrameSize)

setInputFrameSize関数のパラメータは、入力画像の幅と高さ(ピクセル単位)を含むwikitude::sdk::Size<int>型である必要があります。この値は多くの場合で定数になるため、適切な値にハードコーディングしておくことができます。この値を入力画像/動画ファイルまたは入力カメラのいずれかからクエリすることをお勧めします。

デフォルトのフレームレンダリング

bool YUVFrameInputPlugin::requestsInputFrameRendering() {
    return false;
}

Wikitude SDKネイティブAPIによって入力フレームデータを処理するかどうかを示すブール値を提供するためにrequestsInputFrameRendering関数をオーバーライドすることができます。デフォルトで実装する場合はtrueを返し、フレームはWikitude SDK ネイティブAPIの内部レンダリングを使用してレンダリングされます。falseを返すように関数をオーバーライドした場合は、フレームがInputPluginによってレンダリングします。

デフォルトのフレーム処理

bool YUVFrameInputPlugin::requestsInputFrameProcessing() {
    return true;
}

Wikitudeコンピュータビジョンエンジンによって入力フレームデータを処理するかどうかを示すブール値を提供するためにrequestsInputFrameProcessing関数をオーバーライドすることができます。デフォルトで実装する場合はtrueを返し、フレームを処理します。普通のプラグインAPIのようにUpdate関数によってプラグインに認識結果が通知されます。falseを返すように関数をオーバーライドした場合は、InputPluginによってアルゴリズムが実行されます。

画像データを渡す

void notifyNewInputFrame(long frameId_, std::shared_ptr<unsigned char> inputFrame_, bool managedFromOutside_ = false);

実際の入力フレームデータをWikitude SDK ネイティブAPIに渡すためにnotifyNewInputFrame関数を呼び出す必要があります。この処理では、long型のユニークなフレーム識別子とstd::shared_ptrにラップされたフレームデータが必要となり、デフォルトのフレームキャッシングを行うかどうかを示すブール値を受け入れます。パラメータのデフォルト値はfalseで、Wikitude SDK ネイティブAPIのデフォルトのキャッシングを使用することを示します。独自のフレームのキャッシュ メカニズムを渡すには、この値をtrueに設定します。デフォルトのキャッシュ メカニズムは、スムーズな処理性能のためにメモリで最近の5つのフレームを保持します。ファイルリソースと入力ストリームデバイスだけがネイティブコードにアクセスできるので、このメソッドをネイティブコードから呼び出す必要があります。詳細については、Wikitude SDK ネイティブの「Custom Camera」サンプルのコードを参照してください。

レンダリングの設定

InputFrameRenderSettings& getRenderSettings();

InputFrameRenderSettingsのパラメータ化インスタンスを提供するためにgetRenderSettings関数を変更することができます。デフォルトで実装する場合は_renderSettingsのデフォルトメンバーを返します。WikitudeネイティブSDKにデフォルト値と異なるレンダリング設定を提供するには、_renderSettingsを返す前に変更します。

フレームのキャッシング

virtual void prepareRenderingOfInputFrame(long frameId_);

識別子のためにフレームを処理したときにprepareRenderingOfInputFrame関数が呼び出されます。この関数は、requestsInputFrameRendering関数がfalseを返し、requestsInputFrameProcessing関数がtrueを返すようにオーバーライドされた場合のみ呼び出されます。デフォルトで実装する場合は受信した識別子とフレーム・キャッシュからの古いフレームが示すフレームを解放します。デフォルト以外のフレームのキャッシュ メカニズムの場合は、このメソッドをオーバーライドします。-1の入力パラメータは、最新のフレームを識別します。

処理されたフレームのデータの受信

virtual std::shared_ptr<unsigned char> getPresentableInputFrameData();

デフォルトのフレーム・キャッシュから最後に処理されたフレームのデータを受信するためにgetPresentableInputFrameData関数を呼び出すことができます。レンダリングする現在のフレームデータを取得するようにfalseを返すためにrequestsInputFrameRendering関数がオーバーライドされる場合はこのメソッドを使用します。カスタムのフレームのキャッシュ メカニズムを使用する場合は、この関数は廃止されます。

エラー処理

virtual void internalError(const std::string& errorMessage);

入力プラグインに直接関連しないエラーがWikitude SDK ネイティブで発生したときはinternalError関数が呼び出されます。発生したエラーの詳細は入力パラメータによって提供します。

内部使用のみ

void setInputFrameAvailableNotifier(InputFrameAvailableNotifier newInputFrameAvailableNotifier);

setInputFrameAvailableNotifier関数は内部的にしか呼び出されません。

簡単な入力プラグイン

このサンプルは、フレームがWikitude SDKによってレンダリングされるカスタムカメラの実装を示しています。単純な入力プラグインの例は、C ++クラスであるSimpleInputPluginとJavaクラスであるSimpleInputPluginActivity、WikitudeCameraおよびWikitudeCamera2で構成されています。両方のWikitudeCameraクラスには、アンドロイドカメラAPIの基本的な実装が含まれています。クラスSimpleInputPluginActivityは、プラグインをSDKに登録し、ビューを処理します。SimpleInputPluginクラスはInputPluginから派生しています。

onPostCreateof SimpleInputPluginActivityでは、C ++プラグイン(SimpleInputPlugin)がarchitectView.registerNativePluginsを使用してSDKに登録されます。

SimpleInputPluginActivityは、initNativeを使用して、impleInputPluginに渡し、SimpleInputPluginActivityメソッドをC ++から呼び出すために使用します。
FrameSizeは、SimpleInputPlugin.setFrameSizeを使用してピクセル単位で設定されます。FrameSizeは、プラグインの初期化(Plugin (Plugin::initialize))の前に設定する必要があります。

public void onPostCreate(final Bundle savedInstanceState) {

    // other onPostCreate code

    // register Plugin in the wikitude SDK and in the jniRegistration.cpp
    this.architectView.registerNativePlugins("wikitudePlugins", "simple_input_plugin", new PluginManager.PluginErrorCallback() {
        @Override
        public void onRegisterError(int errorCode, String errorMessage) {
            Log.v(TAG, "Plugin failed to load. Reason: " + errorMessage);
        }
    });

    // sets this activity in the plugin
    initNative();

    setFrameSize(FRAME_WIDTH, FRAME_HEIGHT);
}

SimpleInputPluginActivityへのポインタは、SimpleInputPluginに渡されます。

extern "C" JNIEXPORT void JNICALL
Java_com_wikitude_samples_SimpleInputPluginActivity_initNative(JNIEnv* env, jobject obj) {
    env->GetJavaVM(&pluginJavaVM);
    simpleInputPluginActivity = env->NewGlobalRef(obj);
}

最初の更新呼び出し中に、プラグインライフサイクルのSampleInputPluginActivityの同等のメソッドが格納されます。その後、C ++ - > Javaの接続が完全に確立されます。

void SimpleInputPlugin::update(const std::list<wikitude::sdk::RecognizedTarget>& recognizedTargets_) {
    if ( !_jniInitialized ) {
        JavaVMResource vm(pluginJavaVM);
        jclass simpleInputPluginActivityClass = vm.env->GetObjectClass(simpleInputPluginActivity);
        _pluginInitializedMethodId = vm.env->GetMethodID(simpleInputPluginActivityClass, "onInputPluginInitialized", "()V");
        _pluginPausedMethodId = vm.env->GetMethodID(simpleInputPluginActivityClass, "onInputPluginPaused", "()V");
        _pluginDestroyedMethodId = vm.env->GetMethodID(simpleInputPluginActivityClass, "onInputPluginDestroyed", "()V");

        _jniInitialized = true;

        callInitializedJNIMethod(_pluginInitializedMethodId);
        callInitializedJNIMethod(_pluginResumedMethodId);
    }
}

次のオーバーライドされたメソッドは、InputPluginの動作を指定します。

// Defines that the SDK should render the camera frame.
bool SimpleInputPlugin::requestsInputFrameRendering() {
    return true;
}
// Defines that the SDK should process the frames it gets from the InputPlugin.
bool SimpleInputPlugin::requestsInputFrameProcessing() {
    return true;
}
// Defines that the SDK allows other Plugins to use the frames provided by the InputPlugin
bool SimpleInputPlugin::allowsUsageByOtherPlugins() {
    return true;
}

プラグインライフのサイクル

// When initialize of the Plugin is called the FrameColorSpace of the InputPlugin, which can be YUV_420_NV21(default), YUV_420_YV12 or RGB, is set.
void SimpleInputPlugin::initialize() {
    getFrameSettings().setInputFrameColorSpace(wikitude::sdk::FrameColorSpace::YUV_420_NV21);
}
void SimpleInputPlugin::pause() {
    _running = false;
    callInitializedJNIMethod(_pluginPausedMethodId);
}
void SimpleInputPlugin::resume(unsigned int pausedTime_) {
    _running = true;
    callInitializedJNIMethod(_pluginResumedMethodId);
}
void SimpleInputPlugin::destroy() {
    callInitializedJNIMethod(_pluginDestroyedMethodId);
}

SimpleInputPluginは、カメラフレームを受け取るたびにSDKにそのことを通知します。

void SimpleInputPlugin::notifyNewImageBufferData(std::shared_ptr<unsigned char> imageBufferData_) {
    if ( _running ) {
        notifyNewInputFrame(++_frameId, imageBufferData_);
    }
}

以下のメソッドは、入力フレームに関する必要な情報を設定します。

extern "C" JNIEXPORT void JNICALL
Java_com_wikitude_samples_SimpleInputPluginActivity_setFrameSize(JNIEnv* env, jobject obj, jint frameWidth, jint frameHeight) {
    SimpleInputPlugin::instance->getFrameSettings().setInputFrameSize({frameWidth, frameHeight});
}
extern "C" JNIEXPORT void JNICALL

Java_com_wikitude_samples_SimpleInputPluginActivity_setCameraFieldOfView(JNIEnv* env, jobject obj, jfloat fieldOfView) {
    SimpleInputPlugin::instance->getFrameSettings().setFrameFieldOfView(fieldOfView);
}

カスタムカメラ

カスタムカメラの例は、統一されたユースケースの両方の原則を示しています。 カスタムカメラストリームが入力として提供され、カスタムレンダリングエフェクトがレンダリングされた出力を補強するために使用されます。

並行性

InputPluginを実装する場合、プラグインのコールバック関数はWikitude SDK ネイティブAPIによって同時に呼び出されるので、競合状態が発生しないようにする必要があります。これに対して、このサンプルでは、アトミック操作と排他制御の2つの方法が提供されています。

入力プラグインAPIの機能を十分に利用するためには、さまざまな非同期で呼び出されるメンバ関数からデータを収集し、メンバ変数として格納し、一括して使用します。

以下のコードは、「Custom Camera」サンプルから抽出したコードの例です。

void YUVFrameInputPlugin::surfaceChanged(wikitude::sdk::Size<int> renderSurfaceSize_, wikitude::sdk::Size<float> cameraSurfaceScaling_, wikitude::sdk::DeviceOrientation deviceOrientation_) {
    // some orientation handling code here
    _surfaceInitialized.store(true);
}
void YUVFrameInputPlugin::startRender() {
    // some early exit code here
    render();
}
void YUVFrameInputPlugin::render() {
    // some early exit code here
    if (!_surfaceInitialized.load()) {
        return;
    }
    // lots of OpenGL code here
}
#include <atomic>
std::atomic_bool _surfaceInitialized;

surfaceChanged関数とstartRender関数が同時に呼び出されます。これはsurfaceChanged関数から設定されたRender関数内のブール値に依存し、ブール値の読み取りと書き込みが非アトミックである場合は競合状態が発生します。この場合は、アトミック操作がC++における標準Cライブラリによって提供されるように組み込みデータ型の使用をお勧めします。これらのstd::atomicsは対応する操作やLoadStore関数によって設定や読み取りができます。

以下のコードは、アトミック操作が使用できないコードの例です。

void YUVFrameInputPlugin::update(const std::list<wikitude::sdk::RecognizedTarget>& recognizedTargets_) {
    // platform specific intialization code here
    { // mutex auto release scope
        std::lock_guard<std::mutex> lock(_currentlyRecognizedTargetsMutex);
        _currentlyRecognizedTargets = std::list<wikitude::sdk::RecognizedTarget>(recognizedTargets_);
    }
}
void YUVFrameInputPlugin::startRender() {
    // some early exit code here
    render();
}
void YUVFrameInputPlugin::render() {
    // early returns and lots of OpenGL code here
    { // mutex auto release scope
    std::unique_lock<std::mutex> lock(_currentlyRecognizedTargetsMutex);
        if (!_currentlyRecognizedTargets.empty()) {
            const wikitude::sdk::RecognizedTarget targetToDraw = _currentlyRecognizedTargets.front();
            // early unlock to minimize locking duration
            lock.unlock();
            // lots of OpenGL code here
        }
    }
}

Update関数とstartRender関数が同時に呼び出されます。これは、Render関数内で非同期型関数によって設定されたデータに依存します。std::listのオブジェクトはstd::atomicsを使用してアトミックに設定できないので、std::mutexを使用します。コードに示したように、ミューテックスを正常に解放するためにstd::lock_guardおよびstd::unique_lockのようなRAIIスタイルのミューテックロックを使用します。

OpenGLのコンテキスト

プラグインの実行時に有効なOpenGLのコンテキストが使用できます。startRenderendRenderpauseresume関数を実行するときに有効なコンテキストが利用可能です。startRenderおよびendRender関数では、レンダリングに関連するすべての関数呼び出しが含まれます。OpenGLコンテキストは、アプリケーションを一時停止するときに破壊され、アプリケーションを再開するときに再作成されるので、pauseおよびresume関数はOpenGL関連のリソースを解放または取得するために使用されます。したがって、以前に取得したすべてのOpenGLのハンドルは無効になり、再取得する必要があります。

以下のコードは、「Custom Camera」から抽出したコードの例です。

void YUVFrameInputPlugin::pause() {
    releaseFramebufferObject();
    releaseFrameTextures();
    releaseVertexBuffers();
    releaseShaderProgram();
    _renderingInitialized.store(false);
    // some additional code here
}
void YUVFrameInputPlugin::startRender() {
    if (!_renderingInitialized.load()) {
        _renderingInitialized.store(setupRendering());
    }
    render();
}

以前に作成したすべてのOpenGLのリソースを解放し、_renderingInitializedフラグをfalseに設定すると、レンダリング環境はレンダリングループを次回実行するときに再初期化されます。

デバイスの向き

デバイスの向きを検討しながらOpenGLを使用してInputPluginに入力フレームのレンダリングを示します。正しい向きのフレームレンダリングを実現するためにいくつかの方法がありますが、カスタム頂点シェーダ内に必要な変換をマトリックスとして適用することをお勧めします。

以下のコードは、「Custom Camera」から抽出したもので、フルスクリーンクワッドに適用するマトリックスを構成する方法を示します。

void YUVFrameInputPlugin::surfaceChanged(wikitude::sdk::Size<int> renderSurfaceSize_, wikitude::sdk::Size<float> cameraSurfaceScaling_, wikitude::sdk::DeviceOrientation deviceOrientation_) {
    wikitude::sdk::Matrix4 scaleMatrix;
    scaleMatrix.scale(cameraSurfaceScaling_.width, cameraSurfaceScaling_.height, 1.0f);
    switch (deviceOrientation_)
    {
        case wikitude::sdk::DeviceOrientation::DeviceOrientationPortrait:
        {
            wikitude::sdk::Matrix4 rotationToPortrait;
            rotationToPortrait.rotateZ(270.0f);
            _orientationMatrix = rotationToPortrait;
            break;
        }
        case wikitude::sdk::DeviceOrientation::DeviceOrientationPortraitUpsideDown:
        {
            wikitude::sdk::Matrix4 rotationToUpsideDown;
            rotationToUpsideDown.rotateZ(90.0f);
            _orientationMatrix = rotationToUpsideDown;
            break;
        }
        case wikitude::sdk::DeviceOrientation::DeviceOrientationLandscapeLeft:
        {
            wikitude::sdk::Matrix4 rotationToLandscapeLeft;
            rotationToLandscapeLeft.rotateZ(180.0f);
            _orientationMatrix = rotationToLandscapeLeft;
            break;
        }
        case wikitude::sdk::DeviceOrientation::DeviceOrientationLandscapeRight:
        {
            _orientationMatrix.identity();
            break;
        }
    }
    _modelMatrix = scaleMatrix * _orientationMatrix;
    // some synchronization code here
}
attribute vec3 vPosition;
attribute vec2 vTexCoords;
varying mediump vec2 fTexCoords;
uniform mat4 uModelMatrix;
void main(void)
{
    gl_Position = uModelMatrix * vec4(vPosition, 1.0);
    fTexCoords = vTexCoords;
}";
struct Vertex
{
    GLfloat position[3];
    GLfloat texCoord[2];
};
Vertex _vertices[4];
_vertices[0] = (Vertex){{1.0f, -1.0f, 0}, {1.0f, 0.0f}};
_vertices[1] = (Vertex){{1.0f, 1.0f, 0}, {1.0f, 1.0f}};
_vertices[2] = (Vertex){{-1.0f, 1.0f, 0}, {0.0f, 1.0f}};
_vertices[3] = (Vertex){{-1.0f, -1.0f, 0}, {0.0f, 0.0f}};

surfaceChanged関数に構成したマトリックスは均一パラメータとして頂点シェーダに供給され、入力頂点を変換するために使用されます。FBOにレンダリングしたものに応じて追加のマトリックスが必要になる場合があります。この場合は、以下のコードのようにsurfaceChanged関数を変更することで、Y軸が修正できます。

wikitude::sdk::Matrix4 scaleMatrix;
_fboCorrectionMatrix.scale(1.0f, -1.0f, 1.0f);
// same device orientation code here as depicted above
_modelMatrix = scaleMatrix * _orientationMatrix * _fboCorrectionMatrix;

入力プラグインの完全な実装については、「Custom Camera」サンプルのソースコードを参照してください。