🧲

[Android][Kotlin][Vuforia][AR]Vuforiaで動画再生ARを実装してみた。

に公開

次記事」                          「前記事

関連記事
やっと出来た!!ってドヤってたら、Vuforiaが最新バージョン(11.4.4)をリリースしてた。orz.
前記事の意味よ。頑張ったのに。。。この記事も意味ないかもやし。

Abstruct

  • 動画再生ARをVuforiaでつくってみた。昔はサンプルコードあったんだけどな。
  • とそのコード説明

背景

意気揚々とを書こうと思っとったけど、11.4.4がリリースされとうし。公式サンプルあるんじゃね?とう思う今日この頃。まぁでも何かの役に立つかもやけん、残しときます。公式わかりにくいし。
まぁ結局はココの記事とここの記事の組み合わせでなんとかなった。けど大変だった。OpenGLはデバッグがムズい。。。

準備

  • android端末
  • 開発用PC(Android Studioのインストールは済ませておく。)
  • android端末をつないで、デバッグができる様にしておく。
  • SDKとサンプルコードはgithubに一緒にコミットしたんで不要。
  • Vuforiaのアカウント作成 前回の説明の準備1をやっておく。
  • Vuforiaのライセンスキー取得 前回の説明の準備2をやって取得しておく。
     ↑このへんは変わらないね。

ビルドする。

1. 下記に作成したコードをコミットしたのでそれをDL。

https://github.com/aaaa1597/AndKot-VuforiaVideoPlaybackSample

2. Android Studioでプロジェクトを開く。

cloneしたAndKot-VuforiaVideoPlaybackを開く。

3. 認証情報を設定した後、ビルドする。

Vuforiaのライセンス画面を開いて license Key をコピー。

            ↓
Android Studioで、"AppController.cpp"を開き、licenseKeyに、準備2でコピーしたライセンスを貼り付けます。
そして、ビルド。

※もし、この手順を飛ばしたら、アプリ起動時に下記エラーが発生します。

4. マーカを印刷する。

マーカは、vuforia-sample-android-11-3-4.zipを解凍して、vuforia-sample-android-11-3-4/vuforia-sample-11-3-4/Media配下にあります。
target_stones_A4.pdfを印刷しましょう。

↑この画像ですね

5. 動かしてみる。

Android Studioをビルドして、スマホで実行。いきなりカメラ画面になります。
印刷したマーカをカメラで映すと、サンプルコードで表示される物体(s)とは別に足元の四角形に動画が表示されます。
Done!!

いろいろ分かってきた。

今回の動画AR実装のためにサンプルコードをコードリーディングしてて、いろいろ分かってきたので、まとめときます。

GLESRenderer.h/.cpp

OpenGL系の処理を実装しているファイル。今回の修正で動画再生用のテクスチャ、ポリゴン処理を追加している。
↓動画描画のための変数定義

GLESRenderer.h(67-101)
public:
    /* Screen size and video size */
    float _vViewWidth = 0.0f;
    float _vViewHeight = 0.0f;
    float _vVideoWidth = 0.0f;
    float _vVideoHeight = 0.0f;

    /* For video playback rendering */
    GLuint _vTextureId = 0;
    GLuint _vProgram = 0;
    GLint _vaPosition = -1;
    GLint _vaTexCoordLoc = -1;
    GLint _vuProjectionMatrixLoc = -1;
    GLint _vuSamplerOES = -1;

↓描画処理のプロトタイプ宣言

GLESRenderer.h(43-44)
    /* Render a bounding box augmentation on an Video PlayBack */
    void renderVideoPlayback(VuMatrix44F& projectionMatrix, VuMatrix44F& modelViewMatrix, VuMatrix44F& scaledModelViewMatrix);

↓初期化処理(シェーダコンパイルとか、シェーダ変数設定とか)

GLESRenderer.h(21-26)
    /* Setup for Video PlayBack rendering */
    _vProgram = GLESUtils::createProgramFromBuffer(VERTEX_SHADER, FRAGMENT_SHADER);
    _vaPosition = glGetAttribLocation(_vProgram, "a_Position");
    _vaTexCoordLoc = glGetAttribLocation(_vProgram, "a_TexCoord");
    _vuProjectionMatrixLoc = glGetUniformLocation(_vProgram, "u_ProjectionMatrix");
    _vuSamplerOES = glGetUniformLocation(_vProgram, "u_SamplerOES");

MainActivity.kt

mp4動画をフレーム毎にテクスチャとして切り出して、OpenGLのテクスチャデータとして渡している部分。(実際はExoPlayerがやってくれててかなり実装が楽!)今回はres/raw配下の動画を読込んでるけどWeb上(URL)の動画も再生できるらしい。音声は何もせずとも流れてくれる。ExoPlayer便利。
↓メンバ変数定義

MainActivity.kt(55-57)
    private var surfaceTexture: SurfaceTexture? = null
    private var exoPlayer: ExoPlayer? = null
    private var isFrameAvailable = false

↓テクスチャ初期化 jni(c++)側の関数をコールしている。

MainActivity.kt(99-108)
        val textureId = initVideoTexture()
        if (textureId < 0)
            throw RuntimeException("Failed to create native texture")
        surfaceTexture = SurfaceTexture(textureId)
        CoroutineScope(Dispatchers.Main).launch {
            initExoPlayer(this@MainActivity)
        }

        nativeOnSurfaceChanged(width, height)

↓ExoPlayerの初期化 texid → SurfaceTexture → Surface → ExoPlayerの順番でテクスチャを設定する。

MainActivity.kt(177-201)
    private fun initExoPlayer(context: Context) {
        exoPlayer = ExoPlayer.Builder(context).build().apply {
            val surface = Surface(surfaceTexture)
            setVideoSurface(surface)

            /* Specify the video file located in res/raw. */
            val uri = "android.resource://${context.packageName}/${R.raw.vuforiasizzlereel}"
            Log.d("aaaaa", "uri=$uri")
            val mediaItem = MediaItem.fromUri(uri)
            setMediaItem(mediaItem)

            repeatMode = Player.REPEAT_MODE_ONE /* Loop Playback. */
            playWhenReady = true /* Start playback immediately. */

            addListener(object : Player.Listener {
                override fun onVideoSizeChanged(videoSize: VideoSize) {
                    /* Pass the video size to the C++ side. */
                    nativeSetVideoSize(videoSize.width, videoSize.height)
                }
            })

            prepare()
        }
    }

onDrawFrame() ここで次フレームを要求している。

MainActivity.kt(113-116)
            override fun onDrawFrame(gl: GL10) {
                if (mVuforiaStarted) {
                    ・
                    ・
                    surfaceTexture?.updateTexImage()
                    ・
                    ・
            }

Shaders.h

GLES3だからシェーダーは必要。テスクチャサンプラには、samplerExternalOES型を指定するのがポイント。でないとテクスチャは表示されない。

Shaders.h(14-31)
static const char* VERTEX_SHADER =
        "attribute vec4 a_Position;\n"
        "attribute vec2 a_TexCoord;\n"
        "uniform mat4 u_ProjectionMatrix;\n"
        "varying vec2 v_TexCoord;\n"
        "void main() {\n"
        "  gl_Position = u_ProjectionMatrix * a_Position;\n"
        "  v_TexCoord = a_TexCoord;\n"
        "}\n";

static const char* FRAGMENT_SHADER =
        "#extension GL_OES_EGL_image_external : require\n"
        "precision mediump float;\n"
        "varying vec2 v_TexCoord;\n"
        "uniform samplerExternalOES u_SamplerOES;\n"
        "void main() {\n"
        "  gl_FragColor = texture2D(u_SamplerOES, v_TexCoord);\n"
        "}\n";

VuforiaWrapper.cpp

OpenGLのテクスチャ初期化と画面サイズ、動画サイズ設定処理

VuforiaWrapper.cpp(440-471)
extern "C"
JNIEXPORT jint JNICALL
Java_com_aaa_vuforiavideoplaybacksample_VuforiaWrapperKt_initVideoTexture(JNIEnv *env, jclass clazz) {
    gWrapperData.renderer._vTextureId = -1;
    glGenTextures(1, &gWrapperData.renderer._vTextureId);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, gWrapperData.renderer._vTextureId);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
    return static_cast<jint>(gWrapperData.renderer._vTextureId);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_aaa_vuforiavideoplaybacksample_VuforiaWrapperKt_nativeOnSurfaceChanged(JNIEnv *env,
                                                                                jclass clazz,
                                                                                jint width,
                                                                                jint height) {
    gWrapperData.renderer._vViewWidth = static_cast<float>(width);
    gWrapperData.renderer._vViewHeight= static_cast<float>(height);
    glViewport(0, 0, width, height);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_aaa_vuforiavideoplaybacksample_VuforiaWrapperKt_nativeSetVideoSize(JNIEnv *env,
                                                                            jclass clazz,
                                                                            jint width,
                                                                            jint height) {
    gWrapperData.renderer._vVideoWidth = static_cast<float>(width);
    gWrapperData.renderer._vVideoHeight= static_cast<float>(height);
}

出来た!! これで動作する様になった。

2025.9.10 アスペクト比を考慮した動画再生をするように修正。
2025.9.13 スマホ回転させると、ExoPlayerが多重起動するのを修正。

                              「誰かの役に立つと思って」

Discussion