インテル® INDE Media for Mobile チュートリアル – Android* 上での Unity3d* アプリケーションのビデオ・キャプチャー
この記事は、インテル® デベロッパー・ゾーンに掲載されている「Intel® INDE Media for Mobile Tutorials – Video Capturing for Unity3d* Applications on Android*」の日本語参考訳です。
このチュートリアルは、インテル® INDE Media for Mobile を使用して、Android* 上の Unity* アプリケーションにビデオ・キャプチャー機能を追加する方法を説明します。
必要条件:
- Unity* 4.3.0 のキャプチャーは、フルスクリーン・イメージの後処理効果として動作します。後処理効果は Unity Pro* でのみ利用できます。
- Android* SDK
このチュートリアルでは、Android* 向けの Unity* プラグインを作成し、コンパイルします。早速、作業に取り掛かりましょう。
Unity* を起動し、新しいプロジェクトを作成します。Project の下に、/Plugins/ という名前のサブディレクトリーを作成し、その下に /Android/ という名前のサブディレクトリーを作成します。
https://www.izzz.us/article/intel-software-dev-products/intel-inde/ からインテル® INDE をダウンロードし、インストールします。インテル® INDE のインストール後に、Media for Mobile のダウンロードを選択し、インストールします。不明な点がある場合は、インテル® INDE のフォーラム (英語) を参照してください。
<Media for Mobile のインストール・フォルダー>/libs にある 2 つの jar ファイル (android-<version>.jar と domain-<version>.jar) を /Assets/Plugins/Android/ フォルダーにコピーします。
コピー先のフォルダーに、次のコードを含む Capturing.java という名前の Java* ファイルを作成します。
package com.intel.inde.mp.samples.unity; import com.intel.inde.mp.IProgressListener; import com.intel.inde.mp.domain.Resolution; import com.intel.inde.mp.android.graphics.FullFrameTexture; import android.os.Environment; import android.content.Context; import java.io.IOException; import java.io.File; public class Capturing { private static FullFrameTexture texture; private VideoCapture videoCapture; private IProgressListener progressListener = new IProgressListener() { @Override public void onMediaStart() { } @Override public void onMediaProgress(float progress) { } @Override public void onMediaDone() { } @Override public void onMediaPause() { } @Override public void onMediaStop() { } @Override public void onError(Exception exception) { } }; public Capturing(Context context) { videoCapture = new VideoCapture(context, progressListener); texture = new FullFrameTexture(); } public static String getDirectoryDCIM() { return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + File.separator; } public void initCapturing(int width, int height, int frameRate, int bitRate) { VideoCapture.init(width, height, frameRate, bitRate); } public void startCapturing(String videoPath) { if (videoCapture == null) { return; } synchronized (videoCapture) { try { videoCapture.start(videoPath); } catch (IOException e) { } } } public void captureFrame(int textureID) { if (videoCapture == null) { return; } synchronized (videoCapture) { videoCapture.beginCaptureFrame(); texture.draw(textureID); videoCapture.endCaptureFrame(); } } public void stopCapturing() { if (videoCapture == null) { return; } synchronized (videoCapture) { if (videoCapture.isStarted()) { videoCapture.stop(); } } } }
同じフォルダーに、次のコードを含む VideoCapture.java という名前の別の Java* ファイルを作成します。
package com.intel.inde.mp.samples.unity; import android.content.Context; import com.intel.inde.mp.*; import com.intel.inde.mp.android.AndroidMediaObjectFactory; import com.intel.inde.mp.android.AudioFormatAndroid; import com.intel.inde.mp.android.VideoFormatAndroid; import java.io.IOException; public class VideoCapture { private static final String TAG = "VideoCapture"; private static final String Codec = "video/avc"; private static int IFrameInterval = 1; private static final Object syncObject = new Object(); private static volatile VideoCapture videoCapture; private static VideoFormat videoFormat; private static int videoWidth; private static int videoHeight; private GLCapture capturer; private boolean isConfigured; private boolean isStarted; private long framesCaptured; private Context context; private IProgressListener progressListener; public VideoCapture(Context context, IProgressListener progressListener) { this.context = context; this.progressListener = progressListener; } public static void init(int width, int height, int frameRate, int bitRate) { videoWidth = width; videoHeight = height; videoFormat = new VideoFormatAndroid(Codec, videoWidth, videoHeight); videoFormat.setVideoFrameRate(frameRate); videoFormat.setVideoBitRateInKBytes(bitRate); videoFormat.setVideoIFrameInterval(IFrameInterval); } public void start(String videoPath) throws IOException { if (isStarted()) throw new IllegalStateException(TAG + " already started!"); capturer = new GLCapture(new AndroidMediaObjectFactory(context), progressListener); capturer.setTargetFile(videoPath); capturer.setTargetVideoFormat(videoFormat); AudioFormat audioFormat = new AudioFormatAndroid("audio/mp4a-latm", 44100, 2); capturer.setTargetAudioFormat(audioFormat); capturer.start(); isStarted = true; isConfigured = false; framesCaptured = 0; } public void stop() { if (!isStarted()) throw new IllegalStateException(TAG + " not started or already stopped!"); try { capturer.stop(); isStarted = false; } catch (Exception ex) { } capturer = null; isConfigured = false; } private void configure() { if (isConfigured()) return; try { capturer.setSurfaceSize(videoWidth, videoHeight); isConfigured = true; } catch (Exception ex) { } } public void beginCaptureFrame() { if (!isStarted()) return; configure(); if (!isConfigured()) return; capturer.beginCaptureFrame(); } public void endCaptureFrame() { if (!isStarted() || !isConfigured()) return; capturer.endCaptureFrame(); framesCaptured++; } public boolean isStarted() { return isStarted; } public boolean isConfigured() { return isConfigured; } }
重要: ここでは、com.intel.inde.mp.samples.unity というパッケージ名を使用しています。このパッケージ名は、Unity* のプレーヤー設定にある Bundle Identifier と同じでなければなりません。
また、C# スクリプトで前述の Java* クラスを呼び出す際にこの名前を使用する必要があります。名前が一致しない場合、VM はクラス定義を見つけることができず、起動時にクラッシュします。
テスト・アプリケーション用にいくつかの簡単な 3D 要素を設定する必要があります。もちろん、既存のプロジェクトにインテル® INDE Media for Mobile を統合することもできます。どちらの場合も、画面で何か動くものを用意します。
次に、ほかの Android* アプリケーションと同様にマニフェスト XML ファイルを設定する必要があります。マニフェスト・ファイルは、起動するアクティビティーとアクセス可能な関数をコンパイル時に指定します。ここでは、C:/Program Files (x86)/Unity/Editor/Data/PlaybackEngines/androidplayer にあるデフォルトの Unity* AndroidManifest.xml をベースにします。/Plugins/Android 以下に、次のコードを含む AndroidManifest.xml を作成します。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.intel.inde.mp.samples.unity" android:installLocation="preferExternal" android:theme="@android:style/Theme.NoTitleBar" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="18" /> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/> <!-- マイクの許可 --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- OpenGL* ES 2.0 以上が必要 --> <uses-feature android:glEsVersion="0x00020000" android:required="true"/> <application android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true"> <activity android:name="com.unity3d.player.UnityPlayerNativeActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="unityplayer.UnityActivity" android:value="true" /> <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" /> </activity> </application> </manifest>
次の行が重要です。
package="com.intel.inde.mp.samples.unity"
これで、/Plugins/Android 以下に AndroidManifest.xml と Java* ファイルを作成できました。 javac でのコンパイルで classpaths などを含む長いコマンドを使用する代わりに、Apache Ant* スクリプトを使用します。Ant* を使用することで、フォルダー作成、.exe 呼び出し、クラス作成用のスクリプトを簡単に作成できます。また、Ant* スクリプトは Eclipse* にインポートすることもできます。
注: ほかのクラスやライブラリーを使用している場合は、次の Ant* スクリプトを変更する必要があります (http://ant.apache.org/manual/ (英語) にあるドキュメントを参考にしてください)。この Ant* スクリプトは、このチュートリアル専用です。
/Plugins/Android/ 以下に、次のコードを含む build.xml ファイルを作成します。
<?xml version="1.0" encoding="UTF-8"?> <project name="UnityCapturing"> <!-- 使用する設定に合わせて変更してください --> <property name="sdk.dir" value="C:\Android\sdk"/> <property name="target" value="android-18"/> <property name="unity.androidplayer.jarfile" value="C:\Program Files (x86)\Unity\Editor\Data\PlaybackEngines\androiddevelopmentplayer\bin\classes.jar"/> <!-- ソース・ディレクトリー --> <property name="source.dir" value="\ProjectPath\Assets\Plugins\Android" /> <!-- .class ファイルの出力ディレクトリー --> <property name="output.dir" value="\ProjectPath\Assets\Plugins\Android\classes"/> <!-- 作成する jar ファイルの名前。クラスおよび AndroidManifest.xml 内の名前と 一致していなければなりません。--> <property name="output.jarfile" value="Capturing.jar"/> <!-- 出力ディレクトリーがない場合は作成します --> <target name="-dirs" depends="message"> <echo>Creating output directory: ${output.dir} </echo> <mkdir dir="${output.dir}" /> </target> <!-- このプロジェクトの .java ファイルを .class ファイルにコンパイルします --> <target name="compile" depends="-dirs" description="Compiles project's .java files into .class files"> <javac encoding="ascii" target="1.6" debug="true" destdir="${output.dir}" verbose="${verbose}" includeantruntime="false"> <src path="${source.dir}" /> <classpath> <pathelement location="${sdk.dir}\platforms\${target}\android.jar"/> <pathelement location="${source.dir}\domain-1.2.2415.jar"/> <pathelement location="${source.dir}\android-1.2.2415.jar"/> <pathelement location="${unity.androidplayer.jarfile}"/> </classpath> </javac> </target> <target name="build-jar" depends="compile"> <zip zipfile="${output.jarfile}" basedir="${output.dir}" /> </target> <target name="clean-post-jar"> <echo>Removing post-build-jar-clean</echo> <delete dir="${output.dir}"/> </target> <target name="clean" description="Removes output files created by other targets."> <delete dir="${output.dir}" verbose="${verbose}" /> </target> <target name="message"> <echo>Android Ant Build for Unity Android Plugin</echo> <echo> message: Displays this message.</echo> <echo> clean: Removes output files created by other targets. </echo> <echo> compile: Compiles project's .java files into .class files. </echo> <echo> build-jar: Compiles project's .class files into .jar file. </echo> </target> </project>
2 つのパス (source.dir と output.dir) と出力 jar ファイル名 (output.jarfile) は、実際に使用するものに変更する必要があります。
Ant* がインストールされていない場合は、Apache Ant* の Web サイト (英語) から入手できます。インストール後、実行 PATH に追加してください。Ant* を呼び出す前に、JAVA_HOME 環境変数を宣言し、Java* Development Kit (JDK) がインストールされているパスを指定します。PATH に <ant_home>/bin を追加することも忘れないでください。
Windows* のコマンドプロンプト (cmd.exe) を起動し、現在のディレクトリーを /Plugins/Android に変更して、次のコマンドでビルドスクリプトを起動します。
ant build-jar clean-post-jar
数秒後、ビルドに成功したことを示すメッセージが表示されます。
これで jar のコンパイルは完了です。ディレクトリーに Capturing.jar が作成されていることを確認します。
それでは、Unity* に移りましょう。次のコードを含む Capture.cs スクリプトを作成します。
using UnityEngine; using System.Collections; using System.IO; using System; [RequireComponent(typeof(Camera))] public class Capture : MonoBehaviour { public int videoWidth = 720; public int videoHeight = 1094; public int videoFrameRate = 30; public int videoBitRate = 3000; private string videoDir; public string fileName = "game_capturing-"; private float nextCapture = 0.0f; public bool inProgress { get; private set; } private AndroidJavaObject playerActivityContext = null; private static IntPtr constructorMethodID = IntPtr.Zero; private static IntPtr initCapturingMethodID = IntPtr.Zero; private static IntPtr startCapturingMethodID = IntPtr.Zero; private static IntPtr captureFrameMethodID = IntPtr.Zero; private static IntPtr stopCapturingMethodID = IntPtr.Zero; private static IntPtr getDirectoryDCIMMethodID = IntPtr.Zero; private IntPtr capturingObject = IntPtr.Zero; void Start() { if (!Application.isEditor) { // 最初に現在のアクティビティー・コンテキストを取得します using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { playerActivityContext = jc.GetStatic<AndroidJavaObject>("currentActivity"); } // クラスを探します IntPtr classID = AndroidJNI.FindClass("com/intel/inde/mp/samples/unity/Capturing"); // クラスのコンストラクターを探します constructorMethodID = AndroidJNI.GetMethodID(classID, "<init>", "(Landroid/content/Context;)V"); // メソッドを登録します initCapturingMethodID = AndroidJNI.GetMethodID(classID, "initCapturing", "(IIII)V"); startCapturingMethodID = AndroidJNI.GetMethodID(classID, "startCapturing", "(Ljava/lang/String;)V"); captureFrameMethodID = AndroidJNI.GetMethodID(classID, "captureFrame", "(I)V"); stopCapturingMethodID = AndroidJNI.GetMethodID(classID, "stopCapturing", "()V"); // 静的メソッドを登録し呼び出します getDirectoryDCIMMethodID = AndroidJNI.GetStaticMethodID(classID, "getDirectoryDCIM", "()Ljava/lang/String;"); jvalue[] args = new jvalue[0]; videoDir = AndroidJNI.CallStaticStringMethod(classID, getDirectoryDCIMMethodID, args); // キャプチャー・オブジェクトを作成します jvalue[] constructorParameters = AndroidJNIHelper.CreateJNIArgArray(new object [] { playerActivityContext }); IntPtr local_capturingObject = AndroidJNI.NewObject(classID, constructorMethodID, constructorParameters); if (local_capturingObject == IntPtr.Zero) { Debug.LogError("Can't create Capturing object"); return; } // キャプチャー・オブジェクトのグローバル参照を保持します capturingObject = AndroidJNI.NewGlobalRef(local_capturingObject); AndroidJNI.DeleteLocalRef(local_capturingObject); AndroidJNI.DeleteLocalRef(classID); } inProgress = false; nextCapture = Time.time; } void OnRenderImage(RenderTexture src, RenderTexture dest) { if (inProgress && Time.time > nextCapture) { CaptureFrame(src.GetNativeTextureID()); nextCapture += 1.0f / videoFrameRate; } Graphics.Blit(src, dest); } public void StartCapturing() { if (capturingObject == IntPtr.Zero) return; jvalue[] videoParameters = new jvalue[4]; videoParameters[0].i = videoWidth; videoParameters[1].i = videoHeight; videoParameters[2].i = videoFrameRate; videoParameters[3].i = videoBitRate; AndroidJNI.CallVoidMethod(capturingObject, initCapturingMethodID, videoParameters); DateTime date = DateTime.Now; string fullFileName = fileName + date.ToString("ddMMyy-hhmmss.fff") + ".mp4"; jvalue[] args = new jvalue[1]; args[0].l = AndroidJNI.NewStringUTF(videoDir + fullFileName); AndroidJNI.CallVoidMethod(capturingObject, startCapturingMethodID, args); inProgress = true; } private void CaptureFrame(int textureID) { if (capturingObject == IntPtr.Zero) return; jvalue[] args = new jvalue[1]; args[0].i = textureID; AndroidJNI.CallVoidMethod(capturingObject, captureFrameMethodID, args); } public void StopCapturing() { inProgress = false; if (capturingObject == IntPtr.Zero) return; jvalue[] args = new jvalue[0]; AndroidJNI.CallVoidMethod(capturingObject, stopCapturingMethodID, args); } }
このスクリプトをメインカメラに追加します。キャプチャーする前にビデオ・フォーマットを設定する必要があります。各パラメーターの役割は、その名前が示すとおりです。これらのパラメーターは、Unity* Editor GUI から変更できます。
ここでは、Start()、StartCapturing()、StopCapturing() メソッドの説明は省略します。Java* Native Interface (JNI) を使い慣れた皆さんはすでにご存知でしょう。ほかのメソッドを見てみましょう。 OnRenderImage() メソッドは、すべてのレンダリングがイメージの描画を完了した後に呼び出されます。入力イメージはソース・レンダリング・テクスチャーで、結果はデスティネーション・レンダリング・テクスチャーになります。シェーダーベースのフィルターを使用して最終イメージを処理し、変更できます。ここでは、単に Graphics.Blit() を呼び出してソーステクスチャーをそのままデスティネーション・レンダリング・テクスチャーにコピーします。その前に、ネイティブ (“ハードウェア”) テクスチャー・ハンドルを Capturing.java クラスの captureFrame() メソッドに渡しています。
StartCapturing() メソッドと StopCapturing() メソッドは public なので、ほかのスクリプトから呼び出すことができます。次のコードを含む CaptureGUI.cs という名前の C# スクリプトを作成します。
using UnityEngine; using System.Collections; public class CaptureGUI : MonoBehaviour { public Capture capture; private GUIStyle style = new GUIStyle(); void Start() { style.fontSize = 48; style.alignment = TextAnchor.MiddleCenter; } void OnGUI() { style.normal.textColor = capture.inProgress ? Color.red : Color.green; if (GUI.Button(new Rect(10, 200, 350, 100), capture.inProgress ? "[Stop Recording]" : "[Start Recording]", style)) { if (capture.inProgress) { capture.StopCapturing(); } else { capture.StartCapturing(); } } } }
このスクリプトをシーンの任意のオブジェクトに追加します。Capture.cs インスタンスを public capture メンバーに割り当てることも忘れないでください。
Unity* アプリケーションにビデオ・キャプチャー機能を追加するのに必要な作業はこれだけです。それでは、Android* プラットフォーム向けにテスト・アプリケーションをビルドし実行してみましょう。キャプチャーしたビデオは、Android* デバイスの /mnt/sdcard/DCIM/ フォルダーに保存されます。Capturing.java と VideoCapture.java コードのロジックを理解するには、こちらのチュートリアルが役立ちます。
既知の問題:
- このアプローチでは、Unity* GUI レイヤーはキャプチャーできません。OnPreRender() メソッドと OnPostRender() メソッドを使用する回避策がありますが、ドロップシャドウ、遅延シェーディング、フルスクリーンの後処理効果では動作しません。