入力プラグイン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([_camera fieldOfView]);
setFrameFieldOfView関数のパラメータは、カメラ撮影したフレームの水平視野角を示すfloat
型である必要があります。この値により、Wikitudeコンピュータビジョンエンジンはフレーム内にターゲットを正常に認識およびトラッキングできます。視野の値はデバイスによって異なるので、代表値を確保するためにこの値をフレームのソースから直接クエリすることをお勧めします。入力画像や入力動画の場合、この値は対応するメタデータから認識できる必要があります。入力カメラストリームのために、この値は対応するカメラのAPIからアクセスしなければなりません。
フレームのサイズ
getFrameSettings.setInputFrameSize({[_camera videoDimensions].width, [_camera videoDimensions].height});
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によってレンダリングされるカスタムカメラの実装を示しています。Simple Input Pluginの例は、C++クラスSimpleYUVInputPlugin
と、Objective-CクラスWTSimpleDeviceCamera
およびWTSimpleYUVInputCamera
で構成されています。WTSimpleDeviceCamera
クラスには、AVFoundationカメラAPIの簡単な実装が含まれています。WTSimpleYUVInputCamera
クラスは、WTSimpleDeviceCamera
クラスとSimpleYUVInputPlugin
クラスのインスタンスを作成します。さらに両方のインスタンスを接続することで、iOS SDKカメラを起動/停止し、フレームをC ++入力プラグインの実装に渡すことができます。
プラグインを登録するコードは、他のすべてのプラグインベースの例と同様に、WTAugmentedRealityViewController + PluginLoading.mm
ファイルに書かれています。このクラス拡張は、サンプルアプリケーション用に書かれたもので、他のプロジェクトで再利用する必要があるコードは含まれていません。
SimpleInputPlugin
クラスはInputPlugin
から派生し、Wikitude SDKの入力プラグインの最小限の実装を含みます。
カメラのライフサイクルは入力プラグインライフサイクルメソッドに結合されています。次のコードスニペットはこれを示しています。
void SimpleYUVInputPlugin::initialize() {
/* The initialize method is used to initialize the iOS SDK camera */
_initialized = [_camera initialize];
/* Once the camera was initialzed correctly, input frame related settings are adjusted */
if ( _initialized )
{
this->getFrameSettings().setInputFrameColorSpace(wikitude::sdk::FrameColorSpace::YUV_420_NV21);
this->getFrameSettings().setInputFrameSize({640, 480});
this->getFrameSettings().setFrameFieldOfView( [_camera fieldOfView] );
}
}
void SimpleYUVInputPlugin::pause() {
/* In case the SDK pauses (because the hosting application resignes active), also the iOS SDK camera is paused */
[_camera stopRunning];
_running = false;
}
void SimpleYUVInputPlugin::resume(unsigned int pausedTime_) {
/* Like in `pause()`, `resume()` is used to resume the iOS SDK camera */
if ( _initialized ) {
_running = [_camera startRunning];
}
}
void SimpleYUVInputPlugin::destroy() {
/* In case the plugin is destroyed (Which is called as soon as the plugin is unregistered and the hosting application destroyes the shared_ptr used to register the plugin), also the iOS SDK camera is released */
[_camera shutdown];
}
カメラフレームをObjective-C
から C++
に移動するために、AVCaptureVideoDataOutputSampleBufferDelegate
オブジェクトは、AVCaptureVideoDataOutput-setSampleBufferDelegate:queue
メソッドに内部的に渡される
WTSimpleDeviceCamera
を渡します。この初期化子に渡されます
WTSimpleYUVInputCamera
内のAVCaptureVideoDataOutputSampleBufferDelegate
の実装は、ImageBufferData
からユーザー定義メソッドvoid notifyNewImageBufferData(std::shared_ptr
を呼び出し、iOS SDK カメラフレームの表現を含むstd::shared_ptr
は、次のような WTSimpleYUVInputCamera
クラスで作成されます。
if ( kCVReturnSuccess == CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly) )
{
size_t frameDataSize = CVPixelBufferGetDataSize(imageBuffer);
std::shared_ptr<unsigned char> frameData = std::shared_ptr<unsigned char>(new unsigned char[frameDataSize], std::default_delete<unsigned char[]>());
std::memcpy(frameData.get(), CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0), frameDataSize);
_cameraInputPlugin->notifyNewImageBufferData(frameData);
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
}
新しいステートメントで作成されたメモリを解放するために、カスタムディレクタがstd ::shared_ptr
コンストラクタに渡されることに注意してください。
notifyNewImageBufferData
の実装は、入力プラグイン用に定義されたnotifyNewInputFrame
メソッドを呼び出します。 新しいフレームIDを生成し、Objective-C
で生成されたフレームデータを含むstd ::shared_ptr
を渡します。
void SimpleYUVInputPlugin::notifyNewImageBufferData(std::shared_ptr<unsigned char> imageBufferData) {
notifyNewInputFrame(++_frameId, imageBufferData);
}
ここで使用されている実装は、デモンストレーション用であることに注意してください。 プロダクションアプリケーションは、カメラアクセスとカメラフレームの配布を完全に違った方法で自由に実装できます。
カスタムカメラ
カスタムカメラの例は、統一されたユースケースの両方の原則を示しています。 カスタムカメラストリームが入力として提供され、カスタムレンダリングエフェクトがレンダリングされた出力を補強するために使用されます。
並行性
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
は対応する操作やLoad
とStore
関数によって設定や読み取りができます。
以下のコードは、アトミック操作が使用できないコードの例です。
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のコンテキストが使用できます。startRender
、endRender
、pause
、resume
関数を実行するときに有効なコンテキストが利用可能です。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」サンプルのソースコードを参照してください。