-
-
Save woshidan/5443e4d0d779ffff036862d7010e14ef to your computer and use it in GitHub Desktop.
Camera2 Preview Rotation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
public class MainActivity extends Activity { | |
private CameraDevice mCameraDevice; | |
private TextureView mTextureView; | |
private Handler mBackgroundHandler = new Handler(); | |
private CameraCaptureSession mCaptureSession = null; | |
private CaptureRequest.Builder mPreviewRequestBuilder; | |
private CaptureRequest mPreviewRequest; | |
private ImageReader mImageReader; | |
// E/CameraDevice-0-LE: Surface with size (w=180, h=180) and format 0x21 is not valid, size not in valid set: [5248x3936, 5248x2952, 3840x2160, 3264x2448, 2048x1536, 1920x1080, 1280x720, 640x480, 480x320, 320x240] | |
// 09-10 01:32:02.632 6585-6585/com.example.woshidan.cameratest W/CameraDevice-JV-0: Stream configuration failed | |
private final int IMAGE_WIDTH = 320; | |
private final int IMAGE_HEIGHT = 240; | |
private final int MAX_IMAGES = 5; | |
private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { | |
// Surfaceから画像が利用できるようになった時に呼び出される | |
@Override | |
public void onImageAvailable(ImageReader reader) { | |
// Imageは各種コーデック(圧縮方法みたいなもの)で圧縮したりする、画像のByteBufferを扱うためのオブジェクト | |
Image image = reader.acquireLatestImage(); | |
// 何枚か画像を扱うことができて(?)、それぞれはPlanesに入っている | |
// この辺のコードは https://developer.android.com/things/training/doorbell/camera-input.html のサンプルより | |
ByteBuffer imageBuf = image.getPlanes()[0].getBuffer(); | |
final byte[] imageBytes = new byte[imageBuf.remaining()]; | |
imageBuf.get(imageBytes); | |
image.close(); | |
final Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); | |
MainActivity.this.runOnUiThread(new Runnable() { | |
@Override | |
public void run() { | |
ImageView imageView = (ImageView) findViewById(R.id.picture); | |
imageView.setImageBitmap(bitmap); | |
} | |
}); | |
} | |
}; | |
@TargetApi(Build.VERSION_CODES.M) | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
// ImageReaderインスタンスの初期化 | |
// MAX_IMAGES個だけ同時にImageオブジェクトが取得できる | |
mImageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT, ImageFormat.JPEG, MAX_IMAGES); | |
// imageAvailableListener -> 画像準備できた時反応するくん | |
// backgroundHandler -> Listenerが実行された時に呼び出されるHandler | |
// (に対応したLooperって形で処理を実行するバックグラウンドスレッドの指定) | |
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); | |
mTextureView = (TextureView) findViewById(R.id.texture); | |
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { | |
@Override | |
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { | |
// 先ほどのカメラを開く部分をメソッド化した | |
openCamera(); | |
} | |
@Override | |
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { | |
} | |
@Override | |
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { | |
return true; | |
} | |
@Override | |
public void onSurfaceTextureUpdated(SurfaceTexture surface) { | |
} | |
}); | |
Button capture = (Button) findViewById(R.id.button_capture); | |
capture.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
if(mTextureView.isAvailable()) { | |
copyPreview(); | |
} | |
} | |
}); | |
} | |
private void copyPreview() { | |
// ImageViewへ静止画を送るためのCaptureRequestを作る | |
// 静止画を送ってもらうためのリクエストのビルダーですよ | |
CaptureRequest.Builder copyPreviewRequestBuilder = null; | |
try { | |
copyPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
// 送り先はImageReaderにしてね | |
copyPreviewRequestBuilder.addTarget(mImageReader.getSurface()); | |
CaptureRequest copyPreviewRequest = copyPreviewRequestBuilder.build(); | |
// (プレビュー時にセッションは開いたままで、)追加で静止画送ってくれリクエストを送る | |
try { | |
mCaptureSession.capture(copyPreviewRequest, null, null); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
private void openCamera() { | |
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); | |
String selectedCameraId = ""; | |
try { | |
selectedCameraId = manager.getCameraIdList()[0]; | |
// https://github.com/googlesamples/android-Camera2Basic/blob/5dad16c103715b5e7e3c001cc5f6067f8d23f29e/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L499 | |
// あたりにあるのですが、顔用カメラを使いたくないなどがあれば、CameraCharacteristicsを経由して確認可能 | |
// CameraCharacteristics characteristics | |
// = manager.getCameraCharacteristics(selectedCameraId); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
try { | |
manager.openCamera(selectedCameraId, mStateCallback, mBackgroundHandler); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { | |
@Override | |
public void onOpened(CameraDevice cameraDevice) { | |
mCameraDevice = cameraDevice; | |
createCameraPreviewSession(); | |
} | |
@Override | |
public void onDisconnected(CameraDevice cameraDevice) { | |
cameraDevice.close(); | |
mCameraDevice = null; | |
} | |
@Override | |
public void onError(CameraDevice cameraDevice, int error) { | |
cameraDevice.close(); | |
mCameraDevice = null; | |
} | |
}; | |
private void createCameraPreviewSession() { | |
// Landscapeのアプリの場合、カメラの向きとプレビューの向きがずれるっぽい | |
// https://stackoverflow.com/questions/34536798/android-camera2-preview-is-rotated-90deg-while-in-landscape | |
Matrix rotate = new Matrix(); | |
rotate.postRotate(270); // デバイスの向きとアプリの向きの差分から決めると良い | |
// 関連: https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L520-L541 | |
rotate.postTranslate(0, mTextureView.getWidth()); | |
rotate.postScale((1.0f * IMAGE_WIDTH / IMAGE_HEIGHT) * (mTextureView.getWidth() / mTextureView.getHeight()), (1.0f * IMAGE_HEIGHT / IMAGE_WIDTH) * (mTextureView.getWidth() / mTextureView.getHeight())); | |
mTextureView.setTransform(rotate); | |
SurfaceTexture texture = mTextureView.getSurfaceTexture(); | |
texture.setDefaultBufferSize(IMAGE_WIDTH, IMAGE_HEIGHT); // 自分の手元のデバイスで決めうちしてます | |
Surface surface = new Surface(texture); | |
try { | |
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
mPreviewRequestBuilder.addTarget(surface); | |
mPreviewRequest = mPreviewRequestBuilder.build(); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
try { | |
mCameraDevice.createCaptureSession(Arrays.asList(surface , mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { | |
@Override | |
public void onConfigured(CameraCaptureSession session) { | |
// カメラがcloseされている場合 | |
if (null == mCameraDevice) { | |
return; | |
} | |
mCaptureSession = session; | |
try { | |
session.setRepeatingRequest(mPreviewRequest, null, null); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void onConfigureFailed(CameraCaptureSession session) { | |
} | |
}, null); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment