近日在Google Play上開發3D照相機, 可使用紅藍眼鏡或VR觀看拍攝後的3D立體照片, 所以將相關資訊撰寫於此系列之中. 若需下載app, 請於Google Play搜尋Immersive 3D Camera, 或點選如下網址
免費版 : https://play.google.com/store/apps/details?id=net.ddns.mahaljsp.immersivecameraad
付費版 : https://play.google.com/store/apps/details?id=net.ddns.mahaljsp.immersivecamera
Camera API
Camera API在Android 5.0之前使用Camera API 1 實作. 但到了Android 5.0, 大輻的改版使用Camera API 2實作, 增加了製作的困難度, 但也增加了許多不可能達成的功能.
顯示介面
要將照相機所傳入的畫面顯示於Android 裝置, 可以使用SurfaceView 或GLSurfaceView二種元件. 這二種元件在本人的手機上都可達到每秒60frame的速度. 若只是簡單的顯示基本功能, 使用SurfaceView即可. 但若要於每個frame加上濾鏡或其他不同演算法功能時, 建議使用GLSurfaceView, 將每個frame的圖形交由OpenGL來處理. 本章節使用Camera API 2及SurfaceView 說明最基本的寫法
Layout設定
將整個Layout加上一個SurfaceView即可
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.administrator.cameratest1.MainActivity"> <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout>
權限設定
使用照相機, 當然免不了要把圖片儲存於SDCard, 而操作照相機及SDCard都是屬於危險權限, 所以在取得權限上, 又要分為Android 6.0之前及之後的不同要求.
Android 6.0之前
只需於AndroidManifest.xml裏加上如下紅色字体之權限即可
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator.cameratest1"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.FLASHLIGHT" /> <uses-permission android:name="android.permission.CAMERA" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:screenOrientation="landscape" android:theme="@style/Theme.AppCompat.Light.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Android 6.0及之後
Android 6.0及以後的版本, 除了上述的設定外, 尚需於Java code撰寫取得權限的程式碼, 如下
public class MainActivity extends AppCompatActivity{ CameraEngine cameraEngine; private final int REQUEST_PERMISSION=100; SurfaceView surfaceView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ); setContentView(R.layout.activity_main); surfaceView=(SurfaceView)findViewById(R.id.surfaceView); cameraEngine=new CameraEngine(this); processPermission(); } private void processPermission(){ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){ int hasPermission1=checkSelfPermission(Manifest.permission.CAMERA); int hasPermission2=checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); if(hasPermission1!= PackageManager.PERMISSION_GRANTED || hasPermission2!= PackageManager.PERMISSION_GRANTED){ requestPermissions( new String[]{ Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION); return; } else{ openCamera(); } } else{ openCamera(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if(requestCode==REQUEST_PERMISSION){ if (grantResults[0]==PackageManager.PERMISSION_GRANTED && grantResults[1]==PackageManager.PERMISSION_GRANTED){ openCamera(); } else{ Toast.makeText(this, "No permission", Toast.LENGTH_LONG).show(); AlertDialog.Builder dialog=new AlertDialog.Builder(this); dialog.setTitle("Immersive 3D Camera") .setMessage(R.string.permissionInfo) .setPositiveButton(R.string.button_exit, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { finish(); } }).show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } public void openCamera(){ cameraEngine.openCamera(); surfaceView.getHolder().addCallback(new CameraSurfaceViewCallback(cameraEngine)); } }
SurfaceView Callback
SurfaceView是照相機顯示畫面的元件, 但SurfaceView 是由另一個執行緒來負責掌控, 所以並不是在MainActivity的onCreate()中就可以Ready的. 因此必須在MainActivity取得照相機權限後, 再把Camera打開, 待SurfaceView Ready後, 才把SurfaceView傳入Camera.
那MainActivity如何得知SurfaceView己經Ready了呢? 此時在MainActivity就要加入surfaceView.addCallback(cameraSurfaceViewCallback) 來監控SurfaceView的狀況了, 當SurfaceView ready, 就回調 cameraSurfaceViewCallback物件.
CameraSurfaceViewCallback需實作SurfaceHolder.Callback介面, 並實作其方法. 當SurfaceView ready, 就先執行 public void surfaceCreated(SurfaceHolder holder) 方法. 因為就可以在此方法利用setPreview() 把SurfaceView傳給Camera
CameraSurfaceViewCallback程式碼如下 :
public class CameraSurfaceViewCallback implements SurfaceHolder.Callback { CameraEngine cameraEngine; public CameraSurfaceViewCallback(CameraEngine cameraEngine){ this.cameraEngine=cameraEngine; } @Override public void surfaceCreated(SurfaceHolder holder) { Log.d("Thomas", "surface created"); cameraEngine.setPreview(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} @Override public void surfaceDestroyed(SurfaceHolder holder) {} }
CameraEngine
接下來就是照相機的重頭戲了, 這個Camera引擎有如下重點
1. 由系統服務取得照相機管理器 cameraManager
2. 由管理器取得裝置上的照相機屬性 characteristics
3. 建立HandlerThread執行緒及Handler來執行一直重複取得影像的工作
4. 開啟照相機
5. 開始預覽
@TargetApi(Build.VERSION_CODES.LOLLIPOP) public class CameraEngine { CameraManager cameraManager; CameraCharacteristics characteristics; int cameraId=0; CameraDevice cameraDevice; CameraCaptureSession cameraCaptureSession; HandlerThread handlerThread; Handler handler; Context context; SurfaceHolder holder; CaptureRequest.Builder previewBuilder; public CameraEngine(Context context){ this.context=context; cameraManager=(CameraManager)context.getSystemService(Context.CAMERA_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { try { characteristics= cameraManager.getCameraCharacteristics(String.valueOf(cameraId)); } catch (CameraAccessException e) { e.printStackTrace(); } } handlerThread=new HandlerThread("Camera2"); handlerThread.start(); handler=new Handler(handlerThread.getLooper()); } @SuppressWarnings("MissingPermission") public void openCamera(){ try { cameraManager.openCamera(String.valueOf(cameraId), cameraOpenCallback, handler); } catch (CameraAccessException e) { e.printStackTrace(); } } public void setPreview(SurfaceHolder holder){ this.holder=holder; try { previewBuilder=cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); previewBuilder.addTarget(holder.getSurface()); cameraDevice.createCaptureSession(Arrays.asList(holder.getSurface()), cameraPreviewCallback, handler); } catch (CameraAccessException e) { e.printStackTrace(); } } private CameraDevice.StateCallback cameraOpenCallback=new CameraDevice.StateCallback(){ @Override public void onOpened(@NonNull CameraDevice camera) { cameraDevice=camera; } @Override public void onDisconnected(@NonNull CameraDevice camera) {} @Override public void onError(@NonNull CameraDevice camera, int error) {} }; private CameraCaptureSession.StateCallback cameraPreviewCallback=new CameraCaptureSession.StateCallback(){ @Override public void onConfigured(@NonNull CameraCaptureSession session) { cameraCaptureSession=session; previewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE); try { cameraCaptureSession.setRepeatingRequest(previewBuilder.build(), null, handler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) {} }; }
完整程式碼下載 : CameraTest1.rar