tdnki @ ウィキ
CameraPreview
最終更新:
tdnki
-
view
1. カメラプレビューの表示
プレビュー画像に使用されるNV21フォーマットは、輝度のみのデータを取り出しやすいこと、
最終的な画像処理にも輝度しか使用しない予定であることから、モノクロのプレビュー表示をゴールとする。
最終的な画像処理にも輝度しか使用しない予定であることから、モノクロのプレビュー表示をゴールとする。
1-1. Permissionを付加する
AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA"/>
1-2. Activityの向きを固定する
端末の回転を考慮したくないので、Orientationはlandscape固定とする。
AndroidManifest.xml
<activity android:name="com.example.comicfinder.MainActivity" android:label="@string/app_name" android:screenOrientation="landscape" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
1-3. フルスクリーン
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView()より先に呼ぶこと。 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); }
1-4. Cameraオブジェクトの取得と破棄
MainActivity.java
@Override protected void onResume() { super.onResume(); camera = Camera.open(); if (camera == null) { finish(); return; } } @Override protected void onPause() { super.onPause(); if (camera != null) { camera.release(); } }
1-5. プレビュー表示用Surfaceの準備
activity_main.xml
<SurfaceView android:id="@+id/surface" android:layout_width="match_parent" android:layout_height="match_parent" />
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); surface = (SurfaceView)findViewById(R.id.surface); }
1-6. プレビュー画像の取得
プレビューを表示する方法は大きく2通り。
A) Camera#setPreviewDisplay()で描画用のSurfaceを渡して、描画までの一切を依頼する。プレビュー画像には触れない。
B) 用意したバッファにCamera#addCallbackBuffer()でプレビュー画像を書き込んでもらい、(任意の加工をして)描画する。
B) 用意したバッファにCamera#addCallbackBuffer()でプレビュー画像を書き込んでもらい、(任意の加工をして)描画する。
今回はプレビュー画像を編集したいため、Bを選択する。
この場合は、描画処理まで自分で責任を持たなければいけない。
プレビュー画像書き込み依頼は非同期であり、Camera#setPreviewCallbackWithBuffer()により指定したcallbackが呼ばれるのを待つことになる。
この場合は、描画処理まで自分で責任を持たなければいけない。
プレビュー画像書き込み依頼は非同期であり、Camera#setPreviewCallbackWithBuffer()により指定したcallbackが呼ばれるのを待つことになる。
MainActivity.java
@Override protected void onResume() { super.onResume(); camera = Camera.open(); if (camera == null) { finish(); return; } Camera.Parameters params = camera.getParameters(); // プレビューフォーマットをNV21に設定 if (ImageFormat.NV21 != params.getPreviewFormat()) { if (params.getSupportedPreviewFormats().contains(ImageFormat.NV21)) { finish(); return; } params.setPreviewFormat(ImageFormat.NV21); } // プレビューサイズを(横幅が)最も大きいものに設定 for (Size s : params.getSupportedPreviewSizes()) { if (params.getPreviewSize().width < s.width) { params.setPreviewSize(s.width, s.height); } } camera.setParameters(params); previewSize = params.getPreviewSize(); // プレビューのrawデータ。8bit * (画サイズ) * 1.5 byte[] previewBuffer = new byte[previewSize.width * previewSize.height * 3 / 2]; // 描画用バッファ。32bit * (画サイズ) previewBufferRgba = new int[previewSize.width * previewSize.height]; camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { updateFrame(data); } }); camera.startPreview(); camera.addCallbackBuffer(previewBuffer); } @Override protected void onPause() { super.onPause(); if (camera != null) { camera.stopPreview(); camera.release(); } }
1-7. 描画画像の作成
プレビュー画像のbyte列(NV21)をもとに、intの配列(RGBA)を書き込んで描画する。
NV21には、初めのwidth*heightピクセルに輝度が詰まっているので、これをそのまま読み出してRGBにコピーすればよい。
NV21には、初めのwidth*heightピクセルに輝度が詰まっているので、これをそのまま読み出してRGBにコピーすればよい。
MainActivity.java
private void updateFrame(byte[] data) { for (int k = 0; k < previewSize.width * previewSize.height; k++) { int lum = data[k] & 0xff; previewBufferRgba[k] = (0xff << 24) | (lum << 16) | (lum << 8) | lum; } Canvas c = surface.getHolder().lockCanvas(); if (c != null) { try { c.drawBitmap(previewBufferRgba, 0, previewSize.width, 0, 0, previewSize.width, previewSize.height, true, null); } finally { surface.getHolder().unlockCanvasAndPost(c); } } camera.addCallbackBuffer(data); }
1-8. ディスプレイサイズに合わせてストレッチ
float scale = Math.max((float)surface.getWidth() / previewSize.width, (float)surface.getHeight() / previewSize.height); c.scale(scale, scale); c.drawBitmap(previewBufferRgba, 0, previewSize.width, 0, 0, previewSize.width, previewSize.height, true, null);
変化していない公算が高い値を、除算を使って毎フレーム求めるのもどうかと思うが、
プレビューサイズ決定とSurfaceのサイズ確定を待ち合わせてcacheするのも面倒なので、この実装とする。
scaleの計算自体は1msecを切っており、パフォーマンスへの影響はほぼない。
プレビューサイズ決定とSurfaceのサイズ確定を待ち合わせてcacheするのも面倒なので、この実装とする。
scaleの計算自体は1msecを切っており、パフォーマンスへの影響はほぼない。
1-9. 目標達成
モノクロのプレビューを表示することができた。
プレビュー表示に成功したエリエールタワー
ここまでのソース
ComicFinder.zip