USB Webカメラからの動画キャプチャの2回目です。
今回はJava/nativeのそれぞれのJNI関連部分とnativeコードをメインにしたいと思います。
JNI関連・・・Java側
UVCカメラとのアクセス用のクラスUVCCameraに少しだけメソッドを追加します。
何がしたいかと言うと、前回の記事に載せたSurfaceEncoderから取得したSurfaceへ、native側からカメラのフレームデータを書き込みたいのです。そのためにはnative側へSurfaceを引き渡せるようにする必要があります。
と言う事で早速コードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * start movie capturing(this should call while previewing) * @param surface */ public void startCapture(Surface surface) { if (mCtrlBlock != null && surface != null) { nativeSetCaptureDisplay(mNativePtr, surface); } else throw new NullPointerException("startCapture"); } /** * stop movie capturing */ public void stopCapture() { if (mCtrlBlock != null) { nativeSetCaptureDisplay(mNativePtr, null); } } private static final native int nativeSetCaptureDisplay(long id_camera, Surface surface); |
これ以外は今までのUVCCamera.javaと変わりません。
既に似たようなメソッドがUVCCameraには既に有りましたよね。これです。名前が違うだけですることは大して変わりません。
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 |
/** * set preview surface with SurfaceHolder * you can use SurfaceHolder came from SurfaceView/GLSurfaceView * @param holder */ public void setPreviewDisplay(SurfaceHolder holder) { nativeSetPreviewDisplay(mNativePtr, holder.getSurface()); } /** * set preview surface with SurfaceTexture. * this method require API >= 14 * @param texture */ public void setPreviewTexture(SurfaceTexture texture) { // API >= 11 final Surface surface = new Surface(texture); // XXX API >= 14 nativeSetPreviewDisplay(mNativePtr, surface); } /** * set preview surface with Surface * @param Surface */ public void setPreviewDisplay(Surface surface) { nativeSetPreviewDisplay(mNativePtr, surface); } private static final native int nativeSetPreviewDisplay(long id_camera, Surface surface); |
こっちは、ヘルパーメソッドが3種も有りますが、キャプチャ用のSurfaceをセットする方はSurface以外が引数に来る可能性は無いので、ヘルパーメソッドも1つだけです。
あっそうそう、毎度のことながら異常系の処理はかなり省略しているので必要に応じて実装してください。
次はnative側・・・と言いたいところですがその前に、解説はしませんがMainActivityでの処理をまるっと載せておきましょう。
MainActivity
キャプチャ関連以外で違うのは主に、SurfaceTextureListenerを実装したぐらいであとは以前のと殆ど一緒です。180行目辺り以降が主にキャプチャ処理になります。
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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
public class MainActivity extends Activity { // for thread pool private static final int CORE_POOL_SIZE = 1; // initial/minimum threads private static final int MAX_POOL_SIZE = 4; // maximum threads private static final int KEEP_ALIVE_TIME = 10; // time periods while keep the idle thread protected static final ThreadPoolExecutor EXECUTER = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); private static final int CAPTURE_STOP = 0; private static final int CAPTURE_PREPARE = 1; private static final int CAPTURE_RUNNING = 2; // for accessing USB and USB camera private USBMonitor mUSBMonitor; private UVCCamera mUVCCamera; private UVCCameraTextureView mUVCCameraView; // for open&start / stop&close camera preview private ToggleButton mCameraButton; // for start & stop movie capture private ImageButton mCaptureButton; private int mCaptureState = 0; private Surface mPreviewSurface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); mCameraButton = (ToggleButton)findViewById(R.id.camera_button); mCameraButton.setOnCheckedChangeListener(mOnCheckedChangeListener); mCaptureButton = (ImageButton)findViewById(R.id.capture_button); mCaptureButton.setOnClickListener(mOnClickListener); mUVCCameraView = (UVCCameraTextureView)findViewById(R.id.UVCCameraTextureView1); mUVCCameraView.setAspectRatio(640 / 480.f); mUVCCameraView.setSurfaceTextureListener(mSurfaceTextureListener); mUSBMonitor = new USBMonitor(this, mOnDeviceConnectListener); } @Override public void onResume() { super.onResume(); mUSBMonitor.register(); if (mUVCCamera != null) mUVCCamera.startPreview(); updateItems(); } @Override public void onPause() { if (mUVCCamera != null) { stopCapture(); mUVCCamera.stopPreview(); } mUSBMonitor.unregister(); super.onPause(); } @Override public void onDestroy() { if (mUVCCamera != null) { mUVCCamera.destroy(); } super.onDestroy(); } private final OnCheckedChangeListener mOnCheckedChangeListener = new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked && mUVCCamera == null) { CameraDialog.showDialog(MainActivity.this); } else if (mUVCCamera != null) { mUVCCamera.destroy(); mUVCCamera = null; } updateItems(); } }; private final OnClickListener mOnClickListener = new OnClickListener() { @Override public void onClick(View v) { if (mCaptureState == CAPTURE_STOP) { startCapture(); } else { stopCapture(); } } }; private final OnDeviceConnectListener mOnDeviceConnectListener = new OnDeviceConnectListener() { @Override public void onAttach(UsbDevice device) { Toast.makeText(MainActivity.this, "USB_DEVICE_ATTACHED", Toast.LENGTH_SHORT).show(); } @Override public void onConnect(UsbDevice device, final UsbControlBlock ctrlBlock, boolean createNew) { if (mUVCCamera != null) mUVCCamera.destroy(); mUVCCamera = new UVCCamera(); EXECUTER.execute(new Runnable() { @Override public void run() { mUVCCamera.open(ctrlBlock); if (mPreviewSurface != null) { mPreviewSurface.release(); mPreviewSurface = null; } final SurfaceTexture st = mUVCCameraView.getSurfaceTexture(); if (st != null) mPreviewSurface = new Surface(st); mUVCCamera.setPreviewDisplay(mPreviewSurface); mUVCCamera.startPreview(); } }); } @Override public void onDisconnect(UsbDevice device, UsbControlBlock ctrlBlock) { // XXX you should check whether the comming device equal to camera device that currently using if (mUVCCamera != null) { mUVCCamera.close(); if (mPreviewSurface != null) { mPreviewSurface.release(); mPreviewSurface = null; } } } @Override public void onDettach(UsbDevice device) { Toast.makeText(MainActivity.this, "USB_DEVICE_DETACHED", Toast.LENGTH_SHORT).show(); } }; /** * to access from CameraDialog * @return */ public USBMonitor getUSBController() { return mUSBMonitor; } //********************************************************************** private final SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { if (mPreviewSurface != null) { mPreviewSurface.release(); mPreviewSurface = null; } return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { if (mEncoder != null && mCaptureState == CAPTURE_RUNNING) mEncoder.frameAvailable(); } }; private Encoder mEncoder; /** * start capturing */ private final void startCapture() { if (mEncoder == null && (mCaptureState == CAPTURE_STOP)) { mCaptureState = CAPTURE_PREPARE; EXECUTER.execute(new Runnable() { @Override public void run() { final String path = getCaptureFile(Environment.DIRECTORY_MOVIES, ".mp4"); if (!TextUtils.isEmpty(path)) { mEncoder = new SurfaceEncoder(path); mEncoder.setEncodeListener(mEncodeListener); try { mEncoder.prepare(); } catch (IOException e) { mCaptureState = CAPTURE_STOP; } } else throw new RuntimeException("Failed to start capture."); } }); updateItems(); } } /** * stop capture if capturing */ private final void stopCapture() { if (mEncoder != null) { mEncoder.stopRecording(); mEncoder = null; } } /** * callbackds from Encoder */ private final EncodeListener mEncodeListener = new EncodeListener() { @Override public void onPreapared(Encoder encoder) { mUVCCamera.startCapture(((SurfaceEncoder)encoder).getInputSurface()); mCaptureState = CAPTURE_RUNNING; } @Override public void onRelease(Encoder encoder) { mUVCCamera.stopCapture(); mCaptureState = CAPTURE_STOP; updateItems(); } }; private void updateItems() { this.runOnUiThread(new Runnable() { @Override public void run() { mCaptureButton.setVisibility(mCameraButton.isChecked() ? View.VISIBLE : View.INVISIBLE); mCaptureButton.setColorFilter(mCaptureState == CAPTURE_STOP ? 0 : 0xffff0000); } }); } /** * create file path for saving movie / still image file * @param type Environment.DIRECTORY_MOVIES / Environment.DIRECTORY_DCIM * @param ext .mp4 / .png * @return return null if can not write to storage */ private static final String getCaptureFile(String type, String ext) { final File dir = new File(Environment.getExternalStoragePublicDirectory(type), "USBCameraTest"); dir.mkdirs(); // create directories if they do not exist if (dir.canWrite()) { return (new File(dir, getDateTimeString() + ext)).toString(); } return null; } private static final SimpleDateFormat sDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US); private static final String getDateTimeString() { final GregorianCalendar now = new GregorianCalendar(); return sDateTimeFormat.format(now.getTime()); } } |
解説しないと言っときながらあれですけど、1つだけ大事なことを忘れてました。112-119行目です。
以前のサンプルではこの部分を
1 |
mUVCCamera.setPreviewTexture(mUVCCameraView.getSurfaceTexture()); |
としていましたので、プレビューを一旦停止してからしてからもう一度プレビューを行おうとしても表示できませんでした。(一旦アプリを終了させれば出来ます)
native側でANativeWindow_releaseを呼び出して参照を開放すればSurfaceも開放されるみたいなことを何処かで読んだように思ったんですけど、Java側で明示的に開放しないとうまく行きませんでした。
次こそは本当にnative側です。と言っても、さっきのUVCCamera.javaに一個しかネイティブメソッドを追加してないことからわかるようにJNI周りは超しょぼいです。