學習資料:
2017 月 12 月 16 號
補充一個學習資料
20170513 16:25
注意權限,注意權限,注意權限
5.0在配置文件申明一下就好,6.0以上的系統需要考慮動態權限問題
評論里2個同學說因為權限問導致了黑屏,排查代碼卻沒有發現問題,對剛剛接觸安卓不久的同學造成困擾,還耽擱了不少時間,抱歉了
具體的相機設置、預覽控件大小以及樣張的參數之類的,需要根據自己的手機、系統做些相應調整。我測試手機是5.1系統,使用博客中的代碼只是簡單地拍了個照片測試了下,除了照片樣張質量一般外,暫時沒有發現其他問題。我并沒有在項目中實際開發過拍照相關的功能,這里我也是簡單地學習下。若哪位同學發現代碼里的問題,請留言指出,免得造成誤導浪費時間
Android 5.0(21)之后,android.hardware.Camera
被廢棄(下面稱為Camera1
),還有一個android.graphics.Camera
,這個android.graphics.Camera
不是用來照相的,是用來處理圖像的,可以做出3D
的圖像效果之類的,之前的Camera1
則由android.hardware.Camera2
來代替
Camera2
支持RAW
輸出,可以調節曝光,對焦模式,快門等,功能比原先Camera
強大
1. Camera1使用
使用步驟:
- 調用
Camera.open()
,打開相機,默認為后置,可以根據攝像頭ID
來指定打開前置還是后置 - 調用
Camera.getParameters()
得到一個Camera.Parameters
對象 - 使用
步驟2
得到的Camera.Parameters
對象,對拍照參數進行設置 - 調用
Camera.setPreviewDispaly(SurfaceHolder holder)
,指定使用哪個SurfaceView
來顯示預覽圖片 - 調用
Camera.startPreview()
方法開始預覽取景 - 調用
Camera.takePicture()
方法進行拍照 - 拍照結束后,調用
Camera.stopPreview()
結束取景預覽,之后再replease()
方法釋放資源
這幾個步驟從瘋狂Android講義
中學到
1.1 簡單使用
使用SurfaceView
進行取景的預覽,點擊屏幕進行拍照,用ImageView
來展示拍的照片
想買關于操作系統和C的書看,知乎很多人推薦這兩本,就買了。然而,深入理解操作系統買早了,啃不動,看不懂
布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surface_view_camera2_activity"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_show_camera2_activity"
android:layout_width="180dp"
android:layout_height="320dp"
android:visibility="gone"
android:layout_centerInParent="true"
android:scaleType="centerCrop" />
</RelativeLayout>
Activity
代碼:
public class CameraActivity extends AppCompatActivity implements View.OnClickListener {
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private ImageView iv_show;
private int viewWidth, viewHeight;//mSurfaceView的寬和高
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
initView();
}
/**
* 初始化控件
*/
private void initView() {
iv_show = (ImageView) findViewById(R.id.iv_show_camera2_activity);
//mSurfaceView
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view_camera2_activity);
mSurfaceHolder = mSurfaceView.getHolder();
// mSurfaceView 不需要自己的緩沖區
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// mSurfaceView添加回調
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) { //SurfaceView創建
// 初始化Camera
initCamera();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView銷毀
// 釋放Camera資源
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
}
}
});
//設置點擊監聽
mSurfaceView.setOnClickListener(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (mSurfaceView != null) {
viewWidth = mSurfaceView.getWidth();
viewHeight = mSurfaceView.getHeight();
}
}
/**
* SurfaceHolder 回調接口方法
*/
private void initCamera() {
mCamera = Camera.open();//默認開啟后置
mCamera.setDisplayOrientation(90);//攝像頭進行旋轉90°
if (mCamera != null) {
try {
Camera.Parameters parameters = mCamera.getParameters();
//設置預覽照片的大小
parameters.setPreviewFpsRange(viewWidth, viewHeight);
//設置相機預覽照片幀數
parameters.setPreviewFpsRange(4, 10);
//設置圖片格式
parameters.setPictureFormat(ImageFormat.JPEG);
//設置圖片的質量
parameters.set("jpeg-quality", 90);
//設置照片的大小
parameters.setPictureSize(viewWidth, viewHeight);
//通過SurfaceView顯示預覽
mCamera.setPreviewDisplay(mSurfaceHolder);
//開始預覽
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 點擊回調方法
*/
@Override
public void onClick(View v) {
if (mCamera == null) return;
//自動對焦后拍照
mCamera.autoFocus(autoFocusCallback);
}
/**
* 自動對焦 對焦成功后 就進行拍照
*/
Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {//對焦成功
camera.takePicture(new Camera.ShutterCallback() {//按下快門
@Override
public void onShutter() {
//按下快門瞬間的操作
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {//是否保存原始圖片的信息
}
}, pictureCallback);
}
}
};
/**
* 獲取圖片
*/
Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
final Bitmap resource = BitmapFactory.decodeByteArray(data, 0, data.length);
if (resource == null) {
Toast.makeText(CameraActivity.this, "拍照失敗", Toast.LENGTH_SHORT).show();
}
final Matrix matrix = new Matrix();
matrix.setRotate(90);
final Bitmap bitmap = Bitmap.createBitmap(resource, 0, 0, resource.getWidth(), resource.getHeight(), matrix, true);
if (bitmap != null && iv_show != null && iv_show.getVisibility() == View.GONE) {
mCamera.stopPreview();
iv_show.setVisibility(View.VISIBLE);
mSurfaceView.setVisibility(View.GONE);
Toast.makeText(CameraActivity.this, "拍照", Toast.LENGTH_SHORT).show();
iv_show.setImageBitmap(bitmap);
}
}
};
}
權限:
<uses-permission android:name="android.permission.CAMERA" />
在獲得圖片后,想要顯示的效果是照片是豎直顯示,resource
顯示的卻是逆時針旋轉了90°
,照片是橫著的,就使用matrix.setRotate(90)
進行旋轉
2. Camera2
這里引用了管道的概念將安卓設備和攝像頭之間聯通起來,系統向攝像頭發送 Capture 請求,而攝像頭會返回 CameraMetadata。這一切建立在一個叫作 CameraCaptureSession 的會話中。
- CameraManaer 攝像頭管理器,用于檢測攝像頭,打開系統攝像頭,調用
CameraManager.getCameraCharacteristics(String)
可以獲取指定攝像頭的相關特性 - CameraCharacteristics 攝像頭的特性
- CameraDevice 攝像頭,類似
android.hardware.Camera
也就是Camera1
的Camera
- CameraCaptureSession 這個對象控制攝像頭的預覽或者拍照,
setRepeatingRequest()
開啟預覽,capture()
拍照,CameraCaptureSession
提供了StateCallback、CaptureCallback兩個接口來監聽CameraCaptureSession
的創建和拍照過程。 - CameraRequest和CameraRequest.Builder,預覽或者拍照時,都需要一個
CameraRequest
對象。CameraRequest表示一次捕獲請求,用來對z照片的各種參數設置,比如對焦模式、曝光模式等。CameraRequest.Builder用來生成CameraRequest對象。
2.1 簡單使用
使用的依然是SurfaceView
來進行展示預覽
主要思路:
- 獲得攝像頭管理器
CameraManager mCameraManager
,mCameraManager.openCamera()
來打開攝像頭 - 指定要打開的攝像頭,并創建
openCamera()
所需要的CameraDevice.StateCallback stateCallback
- 在
CameraDevice.StateCallback stateCallback
中調用takePreview()
,這個方法中,使用CaptureRequest.Builder
創建預覽需要的CameraRequest
,并初始化了CameraCaptureSession
,最后調用了setRepeatingRequest(previewRequest, null, childHandler)
進行了預覽 - 點擊屏幕,調用
takePicture()
,這個方法內,最終調用了capture(mCaptureRequest, null, childHandler)
- 在
new ImageReader.OnImageAvailableListener(){}
回調方法中,將拍照拿到的圖片進行展示
代碼:
public class Camera2Activity extends AppCompatActivity implements View.OnClickListener {
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
///為了使照片豎直顯示
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private ImageView iv_show;
private CameraManager mCameraManager;//攝像頭管理器
private Handler childHandler, mainHandler;
private String mCameraID;//攝像頭Id 0 為后 1 為前
private ImageReader mImageReader;
private CameraCaptureSession mCameraCaptureSession;
private CameraDevice mCameraDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
initVIew();
}
/**
* 初始化
*/
private void initVIew() {
iv_show = (ImageView) findViewById(R.id.iv_show_camera2_activity);
//mSurfaceView
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view_camera2_activity);
mSurfaceView.setOnClickListener(this);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.setKeepScreenOn(true);
// mSurfaceView添加回調
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) { //SurfaceView創建
// 初始化Camera
initCamera2();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView銷毀
// 釋放Camera資源
if (null != mCameraDevice) {
mCameraDevice.close();
Camera2Activity.this.mCameraDevice = null;
}
}
});
}
/**
* 初始化Camera2
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void initCamera2() {
HandlerThread handlerThread = new HandlerThread("Camera2");
handlerThread.start();
childHandler = new Handler(handlerThread.getLooper());
mainHandler = new Handler(getMainLooper());
mCameraID = "" + CameraCharacteristics.LENS_FACING_FRONT;//后攝像頭
mImageReader = ImageReader.newInstance(1080, 1920, ImageFormat.JPEG,1);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在這里處理拍照得到的臨時照片 例如,寫入本地
@Override
public void onImageAvailable(ImageReader reader) {
mCameraDevice.close();
mSurfaceView.setVisibility(View.GONE);
iv_show.setVisibility(View.VISIBLE);
// 拿到拍照照片數據
Image image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);//由緩沖區存入字節數組
final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (bitmap != null) {
iv_show.setImageBitmap(bitmap);
}
}
}, mainHandler);
//獲取攝像頭管理
mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
//打開攝像頭
mCameraManager.openCamera(mCameraID, stateCallback, mainHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 攝像頭創建監聽
*/
private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {//打開攝像頭
mCameraDevice = camera;
//開啟預覽
takePreview();
}
@Override
public void onDisconnected(CameraDevice camera) {//關閉攝像頭
if (null != mCameraDevice) {
mCameraDevice.close();
Camera2Activity.this.mCameraDevice = null;
}
}
@Override
public void onError(CameraDevice camera, int error) {//發生錯誤
Toast.makeText(Camera2Activity.this, "攝像頭開啟失敗", Toast.LENGTH_SHORT).show();
}
};
/**
* 開始預覽
*/
private void takePreview() {
try {
// 創建預覽需要的CaptureRequest.Builder
final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 將SurfaceView的surface作為CaptureRequest.Builder的目標
previewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
// 創建CameraCaptureSession,該對象負責管理處理預覽請求和拍照請求
mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③
{
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
if (null == mCameraDevice) return;
// 當攝像頭已經準備好時,開始顯示預覽
mCameraCaptureSession = cameraCaptureSession;
try {
// 自動對焦
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 打開閃光燈
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 顯示預覽
CaptureRequest previewRequest = previewRequestBuilder.build();
mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Toast.makeText(Camera2Activity.this, "配置失敗", Toast.LENGTH_SHORT).show();
}
}, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 點擊事件
*/
@Override
public void onClick(View v) {
takePicture();
}
/**
* 拍照
*/
private void takePicture() {
if (mCameraDevice == null) return;
// 創建拍照需要的CaptureRequest.Builder
final CaptureRequest.Builder captureRequestBuilder;
try {
captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 將imageReader的surface作為CaptureRequest.Builder的目標
captureRequestBuilder.addTarget(mImageReader.getSurface());
// 自動對焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 自動曝光
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 獲取手機方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
// 根據設備方向計算設置照片的方向
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
//拍照
CaptureRequest mCaptureRequest = captureRequestBuilder.build();
mCameraCaptureSession.capture(mCaptureRequest, null, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
布局代碼以及權限與Camera1
中一樣,效果一樣
預覽時,是將mSurfaceHolder.getSurface()
作為目標
顯示拍照結果時,是將mImageReader.getSurface()
作為目標
3. 最后
Camera2
的功能很強大,暫時也只是學習了最基本的思路
住的地方,沒有桌子,于是坐地上,趴在床上敲代碼,腰疼。逛淘寶買桌子去
感謝極客學院和腎虛將軍的學習資料
共勉 : )