USBカメラから音&動画の同時キャプチャした〜いの第2弾です。いよいよ佳境に入ってきました。
UVCCameraTextureView
実はこのクラスはAudioVideoRecordingSampleともUSBCameraTest/USBCameraTest2ともかなり異なっています。
そもそも、USBCameraTest/USBCameraTest2ではUVCCameraTextureViewにはviewのサイズ調整以外の処理をしていませんでした。なのに不思議とプレビュー画面が表示できていたのです。この辺りの処理は内蔵カメラでの場合と同じで、表示する枠組み(TextureViewやSurfaceView)さえ用意しておけばほとんどの処理をカメラ処理側で行ってくれていたのです。と言うより同じになるように作ったんですけどね(^_^)
前回のUSBCameraTest2では、MediaCodecのキャプチャ用のSurfaceに対しても同じようにしてカメラ側で映像を書き込むための関数・メソッドを追加しました。
1 2 |
public void UVCCamera#startCapture(Surface surface); public void UVCCamera#stopCapture(); |
でも今回はこの関数・メソッドを使っていません。別に使ってもいいんだけど殆ど同じサンプルじゃ意味無いでしょ?AudioVideoRecordingSampleではGLSurfaceViewベースでしたが、今回はTextureViewベースでJava側でプレビュー用と録画用の2回描画するようにしました。
利点
AudioVideoRecordingSampleでは他に方法がなかったからですが、でも何でJava側でわざわざ描画しているんでしょう?
このサンプルでは実装していませんが、例えば白黒とかセピア色にするなどの画像の後処理・変換したり、他の画像や文字情報とかを書き込んで表示・保存したい場合もありますよね?
もちろんnative側でも個別に変換処理を追加すれば好きに変換できますけど、そうすると変換方法や変換の設定を追加するたびにnative側の共有ライブラリを変更しないと駄目になっちゃうし、そもそもそういう変換の必要の無い用途も有りますよね。無駄に共有ライブラリを肥大化させるのはメンテナンスや互換性の点で非効率的ですよね。
後はJava(SDK)とC/C++(NDK)の両方を使いこなせる人って少なく無いですか?libs下に共有ライブラリをコピーするのは簡単でも、nativeの共有ライブラリ側の中身をいちいち変更するのは面倒・出来ないと言う人も多いと思うんですよね。そこで、その気になればJava側だけで上のような変換処理もできるサンプルを提供することで、少しでも敷居を下げたいと思うんですよね。内蔵カメラ向けにも使いまわせるし、頑張ればARやバーコードの読込みとかも出来ますよ。
欠点
一方欠点はって言うと、やっぱり何と言ってもJavaで実行すると速度的に不利です。JITコンパイラもVMも優秀になってきて差はだいぶ縮まってきましたが、GCは避けて通れないし。でも最近の端末であれば、映像をJava側へ取り出さずに、OpenGL|ESでGPU内にテクスチャとして保持したまま処理する分には、気にしなくても良くなってきたかな〜って思います。
と言う事でソースです。じゃじゃ〜ん(^o^)/まずは描画部分からです。今回の記事は描画部分が殆どと言う話も有りますが
AudioVideoRecordingSampleでは描画にGLSurfaceViewの子クラスを使っていたので、SurfaceTextureの生成やプレビュー表示はGLSurfaceViewのレンダリングコンテキスト内で行っていました。でも、今回はTextureViewです。そのままではOpenGL|ESの描画ができません。標準だとUIスレッドのレンダリングコンテキスト内で描画が行われてしまうという話もあるらしいので、描画用のスレッドとハンドラーを作ってその中で専用のレンダリングコンテキストを生成して描画処理を行うようにしました。処理内容はAudioVideoRecordingSampleとほぼ同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
/** * render camera frames on this view on a private thread */ private static final class RenderHandler extends Handler implements SurfaceTexture.OnFrameAvailableListener { private static final int MSG_REQUEST_RENDER = 1; private static final int MSG_SET_ENCODER = 2; private static final int MSG_CREATE_SURFACE = 3; private static final int MSG_TERMINATE = 9; private RenderThread mThread; private boolean mIsActive = true; public static final RenderHandler createHandler(SurfaceTexture surface) { final RenderThread thread = new RenderThread(surface); thread.start(); return thread.getHandler(); } private RenderHandler(RenderThread thread) { mThread = thread; } public final void setVideoEncoder(MediaVideoEncoder encoder) { if (mIsActive) sendMessage(obtainMessage(MSG_SET_ENCODER, encoder)); } public final SurfaceTexture getPreviewTexture() { synchronized (mThread.mSync) { sendEmptyMessage(MSG_CREATE_SURFACE); try { mThread.mSync.wait(); } catch (InterruptedException e) { } return mThread.mPreviewSurface; } } public final void release() { if (mIsActive) { mIsActive = false; removeMessages(MSG_REQUEST_RENDER); removeMessages(MSG_SET_ENCODER); sendEmptyMessage(MSG_TERMINATE); } } @Override public final void onFrameAvailable(SurfaceTexture surfaceTexture) { if (mIsActive) sendEmptyMessage(MSG_REQUEST_RENDER); } @Override public final void handleMessage(Message msg) { if (mThread == null) return; switch (msg.what) { case MSG_REQUEST_RENDER: mThread.onDrawFrame(); break; case MSG_SET_ENCODER: mThread.setEncoder((MediaVideoEncoder)msg.obj); break; case MSG_CREATE_SURFACE: mThread.updatePreviewSurface(); break; case MSG_TERMINATE: Looper.myLooper().quit(); mThread = null; break; default: super.handleMessage(msg); } } |
長いのでちょっと分割っす。