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