Android相機不使用SurfaceView預覽,使用ImageView預覽
剛接觸Android三個月,以下內容可能存在錯誤,歡迎指正。
以下在Android5. 1 ,API22機器下除錯的。
之前參考網上的資源,很多使用的surfaceView控制元件預覽相機,所以就用的是surfaceview。但是我的需求不僅是預覽,還需要經常性的拍照,然後在程式碼裡處理圖片,而takePicture期間有一段的卡頓時間(拍照的過程)。
後來,硬體工程師讓做一個關於雙攝的app。要求是能同時在四個地方預覽同一個攝像頭,並能切換兩個攝像頭像這樣(1代表第一個攝像頭,2代表第二個攝像頭,畫面填充了ImageView控制元件,有拉伸現象)
發現提到了一個Camera的介面: Camera.PreviewCallback。說明是這樣的: Camera.PreviewCallback Called as preview frames are displayed. This callback is invoked on the event thread open(int) was called from.
接口裡面有一個回撥方法:onPreviewFrame(byte[] data, Camera camera),在預覽幀顯示的時候呼叫。
然後在這個方法裡實現對拍到的內容data轉換成bitmap就行了。然後把這個bitmap顯示在需要的ImageView上就行了。目測來看效果還闊以。具體原始碼角度我沒有分析(還沒有那個水平),待後期分析。
注意:不能把data簡單的轉換成btmap,否則得到的bitmap為null。這是因為data格式是YUV的,需要進行資料轉換。
下面的程式碼是另起一個類,實現PreviewCallback介面:只需要看這部分程式碼就行
public class CameraPreviewCallback implements Camera.PreviewCallback { private ImageView imageView; public CameraPreviewCallback(ImageView imageView) { this.imageView = imageView; } @Override public void onPreviewFrame(byte[] data, Camera camera) { //格式的轉換 Camera.Size previewSize = camera.getParameters().getPreviewSize(); YuvImage image = new YuvImage(data, ImageFormat.NV21, previewSize.width, previewSize.height, null); ByteArrayOutputStream stream = new ByteArrayOutputStream(); image.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 80, stream); Constant.FACE_BITMAP = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size()); //在指定的imageView上顯示預覽的圖片 imageView.setImageBitmap(Constant.FACE_BITMAP); } }
MainActivity程式碼:主要是不需要寫surfaceHolder等,只設置了下預覽回撥。
(相機預覽只是實現了。但相機的正確處理還沒有完全弄明白,可能寫的有很多不足的地方)
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private ImageView center = null; private ImageView right; private ImageView left; private ImageView up; private ImageView down; // byte[] buffer=new byte[]; private Boolean isPause = false; private Boolean isSwitch = false; private Camera mCamera1; private Camera mCamera2; private TextView tip_left; //切換攝像頭 private TextView tip_right; //暫停/開始預覽 private long firstTime = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); supportRequestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); findViews(); setListener(); init(); //initSurface(); Log.e("*************", "onCreate呼叫了"); } @Override protected void onStart() { Log.e("*************", "onStart呼叫了"); super.onStart(); } private void setListener() { tip_left.setOnClickListener(this); tip_right.setOnClickListener(this); } private void findViews() { center = findViewById(R.id.center); left = findViewById(R.id.left); up = findViewById(R.id.up); down = findViewById(R.id.down); right = findViewById(R.id.right); tip_left = findViewById(R.id.tip_left); tip_right = findViewById(R.id.tip_right); } //拍照出錯的回撥 Camera.ErrorCallback callback = new Camera.ErrorCallback() { @Override public void onError(int i, Camera camera) { switch (i) { case Camera.CAMERA_ERROR_SERVER_DIED: Log.d("SERVER_DIED", "CAMERA_ERROR_SERVER_DIED"); break; case Camera.CAMERA_ERROR_UNKNOWN: Log.d("UNKNOWN", "CAMERA_ERROR_UNKNOWN"); break; } } }; private void init() { initCamera(); //開啟攝像頭 mCamera1.setPreviewCallback(new MyPreviewCallback(new ImageView[]{right, left, up, down})); mCamera2.setPreviewCallback(new MyPreviewCallback(new ImageView[]{center})); mCamera1.setErrorCallback(callback); mCamera2.setErrorCallback(callback); //設定第一次開啟app時,中間的ImageView預覽攝像頭1的內容 } //初始化攝像頭 private void initCamera() { //如果存在攝像頭 if (checkCameraHardware(getApplicationContext())) { //獲取攝像頭(首選前置,無前置選後置) try { if (openFacingFrontCamera()) { Log.e("Demo", "openCameraSuccess"); } else { Log.e("Demo", "openCameraFailed"); } } catch (Exception e) { Log.e("Demo", "autoFocus/openFacingFrontCamera函式異常:"); } } } //也有用open(id)開啟攝像頭的,這裡只是參考下。 //得到後置攝像頭 private boolean openFacingFrontCamera() { //嘗試開啟前置攝像頭,因為我用的是兩個後攝。下面這個for其實沒有執行 Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); for (int camIdx = 0, cameraCount = Camera.getNumberOfCameras(); camIdx < cameraCount; camIdx++) { Camera.getCameraInfo(camIdx, cameraInfo); if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { try { if (camIdx == 0) mCamera1 = Camera.open(camIdx); //這兩個攝像頭是後置攝像頭 if (camIdx == 1) mCamera2 = Camera.open(camIdx); } catch (RuntimeException e) { return false; } } } //如果開啟前置失敗(無前置)則開啟後置 if (mCamera1 == null) { for (int camIdx = 0, cameraCount = Camera.getNumberOfCameras(); camIdx < cameraCount; camIdx++) { Camera.getCameraInfo(camIdx, cameraInfo); if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { try { if (camIdx == 0) mCamera1 = Camera.open(camIdx); //這兩個攝像頭是後置攝像頭 if (camIdx == 1) mCamera2 = Camera.open(camIdx); } catch (RuntimeException e) { return false; } } } } try { mCamera1.startPreview(); mCamera2.startPreview(); } catch (Exception e) { e.printStackTrace(); } return true; } //判斷是否存在攝像頭 private boolean checkCameraHardware(Context context) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { // 裝置存在攝像頭 return true; } else { // 裝置不存在攝像頭 Toast.makeText(context, "未檢測到攝像頭", Toast.LENGTH_SHORT).show(); return false; } } //對焦並拍照 private void autoFocus() { mCamera1.startPreview(); //自動對焦 mCamera1.autoFocus(myAutoFocus); mCamera2.startPreview(); //自動對焦 mCamera2.autoFocus(myAutoFocus); } //自動對焦回撥函式(空) private Camera.AutoFocusCallback myAutoFocus = new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { } }; @Override public void onClick(View v) { switch (v.getId()) { case R.id.tip_left: //控制預覽畫面展示到不同的控制元件上 mCamera1.startPreview(); mCamera2.startPreview(); if (!isSwitch) { //isSwitch==false camera1->中間 mCamera1.setPreviewCallback(new MyPreviewCallback(new ImageView[]{center})); mCamera2.setPreviewCallback(new MyPreviewCallback(new ImageView[]{right, left, up, down})); } else { //isSwitch==true camera1->四周 mCamera1.setPreviewCallback(new MyPreviewCallback(new ImageView[]{right, left, up, down})); mCamera2.setPreviewCallback(new MyPreviewCallback(new ImageView[]{center})); } isSwitch = !isSwitch; //以下if作用是解決:當處於暫停狀態時,點選切換畫面時, if (isPause) { //本身畫面當處於暫停狀態時 tip_right.setText("點選暫停預覽"); } isPause = !isPause; break; case R.id.tip_right: //控制預覽暫停/開始 if (!isPause) { tip_right.setText("點選開始預覽"); mCamera1.stopPreview(); mCamera2.stopPreview(); } else { if (isSwitch) { mCamera1.setPreviewCallback(new MyPreviewCallback(new ImageView[]{center})); mCamera2.setPreviewCallback(new MyPreviewCallback(new ImageView[]{right, left, up, down})); } else { mCamera1.setPreviewCallback(new MyPreviewCallback(new ImageView[]{right, left, up, down})); mCamera2.setPreviewCallback(new MyPreviewCallback(new ImageView[]{center})); } tip_right.setText("點選暫停預覽"); mCamera1.startPreview(); mCamera2.startPreview(); } isPause = !isPause; break; } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { if (System.currentTimeMillis() - firstTime > 2000) { Toast.makeText(this, "再按一次退出程式", Toast.LENGTH_SHORT).show(); firstTime = System.currentTimeMillis(); return true; } else { if (mCamera1 != null) { mCamera1.stopPreview(); mCamera1.unlock(); mCamera1.release(); mCamera1 = null; } if (mCamera2 != null) { mCamera2.stopPreview(); mCamera2.unlock(); mCamera2.release(); mCamera2 = null; } //finish(); System.exit(0); } return true; } return super.onKeyDown(keyCode, event); } @Override protected void onDestroy() { super.onDestroy(); if (mCamera1 != null) { mCamera1.stopPreview(); mCamera1.unlock(); mCamera1.release(); mCamera1 = null; } if (mCamera2 != null) { mCamera2.stopPreview(); mCamera2.unlock(); mCamera2.release(); mCamera2 = null; } } }