インテル® INDE Media for Mobile チュートリアル – Android* 上での Unity3d* アプリケーションの高度なビデオ・キャプチャー
この記事は、インテル® デベロッパー・ゾーンに掲載されている「Intel® INDE Media for Mobile Tutorials – Advanced Video Capturing for Unity3d* Applications on Android*」の日本語参考訳です。
チュートリアル 1 の最後で、Unity* GUI レイヤーの問題に触れました。既存の GUI を多用する複雑な Unity* ゲームでは どうしたら良いのでしょうか? ここでは、そのための インテル® INDE Media for Mobile の高度な使用方法を示します。Unity* の無料バージョンを使って、フルスクリーン・イメージの後処理効果を使用しないアプローチを紹介します。
必要条件:
- Unity* 4.3.0
- Android* SDK
- Unity* ビデオ・キャプチャー・チュートリアル 1
最初に、チュートリアル 1 で説明したように、インテル® INDE Media for Mobile をゲームに統合します。統合手順は、チュートリアル 1 を参照してください。ここでは、すでに統合されていることを前提に、必要な変更に注目して説明します。
Capturing.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 com.intel.inde.mp.android.graphics.FrameBuffer; import com.intel.inde.mp.android.graphics.EglUtil; import android.os.Environment; import android.content.Context; import java.io.IOException; import java.io.File; public class Capturing { private static FullFrameTexture texture; private FrameBuffer frameBuffer; 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, int width, int height) { videoCapture = new VideoCapture(context, progressListener); frameBuffer = new FrameBuffer(EglUtil.getInstance()); frameBuffer.setResolution(new Resolution(width, height)); 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 beginCaptureFrame() { frameBuffer.bind(); } public void captureFrame(int textureID) { if (videoCapture == null) { return; } synchronized (videoCapture) { videoCapture.beginCaptureFrame(); texture.draw(textureID); videoCapture.endCaptureFrame(); } } public void endCaptureFrame() { frameBuffer.unbind(); int textureID = frameBuffer.getTextureId(); captureFrame(textureID); texture.draw(textureID); } public void stopCapturing() { if (videoCapture == null) { return; } synchronized (videoCapture) { if (videoCapture.isStarted()) { videoCapture.stop(); } } } }
いくつかの変更が行われているのが分かります。主な変更点として、frameBuffer メンバーには、適切なサイズの FrameBuffer を作成するため、コンストラクターに width と height パラメーターが追加されています。また、次の 3 つの新しいパブリックメソッドも追加されています: frameBufferTexture()、beginCaptureFrame()、および endCaptureFrame()。これらのメソッドの役割は、後で C# 側で明らかになります。
何も変更せずに VideoCapture.java ファイルを閉じます。パッケージ名に注意してください。このパッケージ名は、Unity* のプレーヤー設定にある Bundle Identifier と同じでなければなりません。マニフェスト・ファイルも忘れないでください。必要なすべての権限と機能を設定します。
これで、/Plugins/Android 以下に AndroidManifest.xml と Java* ファイルが作成されます。Apache* Ant* スクリプトを作成し、すべてをビルドします。詳しい手順は、チュートリアル 1 を参照してください。ディレクトリーに 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 bool finalizeFrame = false; 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 beginCaptureFrameMethodID = IntPtr.Zero; private static IntPtr endCaptureFrameMethodID = 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/penelope/Capturing"); // クラスのコンストラクターを探します constructorMethodID = AndroidJNI.GetMethodID(classID, "<init>", "(Landroid/content/Context;II)V"); // メソッドを登録します initCapturingMethodID = AndroidJNI.GetMethodID(classID, "initCapturing", "(IIII)V"); startCapturingMethodID = AndroidJNI.GetMethodID(classID, "startCapturing", "(Ljava/lang/String;)V"); beginCaptureFrameMethodID = AndroidJNI.GetMethodID(classID, "beginCaptureFrame", "()V"); endCaptureFrameMethodID = AndroidJNI.GetMethodID(classID, "endCaptureFrame", "()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, Screen.width, Screen.height }); 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 OnPreRender() { if (inProgress && Time.time > nextCapture) { finalizeFrame = true; nextCapture += 1.0f / videoFrameRate; BeginCaptureFrame(); } } public IEnumerator OnPostRender() { if (finalizeFrame) { finalizeFrame = false; yield return new WaitForEndOfFrame(); EndCaptureFrame(); } else { yield return null; } } 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 BeginCaptureFrame() { if (capturingObject == IntPtr.Zero) return; jvalue[] args = new jvalue[0]; AndroidJNI.CallVoidMethod(capturingObject, beginCaptureFrameMethodID, args); } private void EndCaptureFrame() { if (capturingObject == IntPtr.Zero) return; jvalue[] args = new jvalue[0]; AndroidJNI.CallVoidMethod(capturingObject, endCaptureFrameMethodID, args); } public void StopCapturing() { inProgress = false; if (capturingObject == IntPtr.Zero) return; jvalue[] args = new jvalue[0]; AndroidJNI.CallVoidMethod(capturingObject, stopCapturingMethodID, args); } }
これが最も大きな変更点ですが、ロジックは単純なものです。スクリーンサイズを Capturing.java コンストラクターに渡します。コンストラクターのシグネチャーが (Landroid/content/Context;II)V に変わっています。Java* で FrameBuffer を作成します。OnPreRender() は、カメラがシーンのレンダリングを開始する前に呼び出されます。ここで FrameBuffer をバインドします。シーンの実際のレンダリングはすべてオフスクリーンで行われます。OnPostRender() は、カメラがシーンのレンダリングを終了した後に呼び出されます。フレームの最後まで待ってから、デフォルトのオンスクリーンの FrameBuffer に切り替え、スクリーンに直接テクスチャーをコピーします (Capturing.java にある endCaptureFrame() メソッド)。Graphics.Blit() には Unity Pro* が必要なため、ここでは使用できません。同じテクスチャーを使用してフレームをキャプチャーします。
キャプチャー・アルゴリズムのゲーム・パフォーマンスへの影響を把握するため、単純な FPSCounter クラスを作成してみましょう。
using UnityEngine; using System.Collections; public class FPSCounter : MonoBehaviour { public float updateRate = 4.0f; // 1 秒あたり 4 回更新 private int frameCount = 0; private float nextUpdate = 0.0f; private float fps = 0.0f; private GUIStyle style = new GUIStyle(); void Start() { style.fontSize = 48; style.normal.textColor = Color.white; nextUpdate = Time.time; } void Update() { frameCount++; if (Time.time > nextUpdate) { nextUpdate += 1.0f / updateRate; fps = frameCount * updateRate; frameCount = 0; } } void OnGUI() { GUI.Label(new Rect(10, 110, 300, 100), "FPS: " + fps, style); } }
このスクリプトをシーンの任意のオブジェクトに追加します。
これだけです。それでは、Android* プラットフォーム向けにテスト・アプリケーションをビルドし実行してみましょう。キャプチャーしたビデオは、Android* デバイスの /mnt/sdcard/DCIM/ フォルダーに保存されます。
既知の問題:
- このアプローチでは、オフスクリーン・レンダリング (ドロップシャドウ、遅延シェーディング、フルスクリーンの後処理効果) をキャプチャーできません。