前段時間CameraX的Beta版釋出了,這幾天有時間也來嘗試一下。Beta版本是對外測試版本,意味著它已經走出實驗室走向生產,API的呼叫基本穩定不會大改了,bug也會更少可以用於生成環境。
之前使用Camera1和Camera2開發相機功能的時候需要呼叫非常複雜的API,而且由於Android手機的碎片化嚴重,不同手機對相機功能的支援度也不一樣,因此很多做相機相關應用的公司都會封裝自己的相機庫來簡化相機的使用步驟和處理相容性問題。
CameraX其實就是Google開發的一個用來簡化相機開發時候API的呼叫和處理各種相容性問題的庫。最多相容到Android 5.0,底層呼叫的也是Camera2,不過比Camera2用起來更簡單,而且可以繫結生命週期,從而可以自動的處理相機的開啟釋放等工作。
下面開始來嘗試吧
新增依賴
dependencies { // CameraX 核心庫使用 camera2 實現 implementation "androidx.camera:camera-camera2:1.0.0-beta03" // 可以使用CameraView implementation "androidx.camera:camera-view:1.0.0-alpha10" // 可以使用供應商擴充套件 implementation "androidx.camera:camera-extensions:1.0.0-alpha10" //camerax的生命週期庫 implementation "androidx.camera:camera-lifecycle:1.0.0-beta03" }
如果想要使用CameraX拍照非常簡單,只需要配置不同的使用狀態,然後繫結到生命週期中即可。比如預覽需要設定預覽相關的狀態,拍照需要設定拍照相關的狀態,錄製影片需要設定錄製相關的狀態。
配置狀態
預覽配置:Preview用於相機預覽的時候顯示預覽畫面。
Preview preview = new Preview.Builder() //設定寬高比 .setTargetAspectRatio(screenAspectRatio) //設定當前螢幕的旋轉 .setTargetRotation(rotation) .build();
照相配置:ImageCapture 用於拍照,並將圖片儲存
ImageCapture imageCapture = new ImageCapture.Builder() //最佳化捕獲速度,可能降低圖片質量 .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) //設定寬高比 .setTargetAspectRatio(screenAspectRatio) //設定初始的旋轉角度 .setTargetRotation(rotation) .build();
錄製影片設定:VideoCapture 用來錄製影片和儲存影片,寬高比和解析度設定一個就可以了,不要同時設定否則報錯。根據實際的需求來設定,如果對寬高比要求高就設定寬高比,反之就設定解析度
VideoCapture videoCapture = new VideoCaptureConfig.Builder() //設定當前旋轉 .setTargetRotation(rotation) //設定寬高比 .setTargetAspectRatio(screenAspectRatio) //解析度 //.setTargetResolution(resolution) //影片幀率 越高影片體積越大 .setVideoFrameRate(25) //bit率 越大影片體積越大 .setBitRate(3 * 1024 * 1024) .build();
繫結到生命週期:ProcessCameraProvider 是一個單例類,可以把相機的生命週期繫結到任何LifecycleOwner類中。AppCompatActivity和Fragment都是LifecycleOwner
//Future表示一個非同步的任務,ListenableFuture可以監聽這個任務,當任務完成的時候執行回撥 ListenableFuturecameraProviderFuture = ProcessCameraProvider.getInstance(this); ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); //重新繫結之前必須先取消繫結 cameraProvider.unbindAll(); Camera camera = cameraProvider.bindToLifecycle(CameraActivity.this, cameraSelector,preview,imageCapture,videoCapture);
OK預覽,照相,錄影片的配置和繫結到生命週期的工作就完成了
預覽的時候需要顯示到一個View控制元件上吧,CameraX中提供了一個PreviewView用來顯示預覽畫面。其內部封裝了TextureView和SurfaceView,可以根據不同的模式來選擇其內部使用TextureView還是SurfaceView來顯示。
xml中新增PreviewView,並在程式碼中將其附加到前面創建出來的Preview這個例項上
preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera .getCameraInfo()));
這樣當我們進入該頁面的時候就可以看到相機的預覽效果呢,接下來就是執行拍照和錄製的功能了
執行拍照錄影
拍照:
//建立圖片儲存的檔案地址 File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(), System.currentTimeMillis() + ".jpeg"); ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build(); mImageCapture.takePicture(outputFileOptions,mExecutorService , new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { Uri savedUri = outputFileResults.getSavedUri(); if(savedUri == null){ savedUri = Uri.fromFile(file); } outputFilePath = file.getAbsolutePath(); onFileSaved(savedUri); } @Override public void onError(@NonNull ImageCaptureException exception) { Log.e(TAG, "Photo capture failed: "+exception.getMessage(), exception); } }); //將前面儲存的檔案新增到媒體中 private void onFileSaved(Uri savedUri) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri)); } String mimeTypeFromExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap .getFileExtensionFromUrl(savedUri.getPath())); MediaScannerConnection.scanFile(getApplicationContext(), new String[]{new File(savedUri.getPath()).getAbsolutePath()}, new String[]{mimeTypeFromExtension}, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { Log.d(TAG, "Image capture scanned into media store: $uri"+uri); } }); PreviewActivity.start(this, outputFilePath, !takingPicture); }
呼叫ImageCapture的takePicture方法來拍照
傳入一個檔案地址用來儲存拍好的照片
onImageSaved方法是照片已經拍好並存好之後的回撥
onFileSaved方法中將前面儲存的檔案新增到媒體中,最後跳轉到預覽介面。
錄影片:
//建立影片儲存的檔案地址 File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(), System.currentTimeMillis() + ".mp4"); mVideoCapture.startRecording(file, Executors.newSingleThreadExecutor(), new VideoCapture.OnVideoSavedCallback() { @Override public void onVideoSaved(@NonNull File file) { outputFilePath = file.getAbsolutePath(); onFileSaved(Uri.fromFile(file)); } @Override public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) { Log.i(TAG,message); } });
videoCapture.stopRecording();
使用VideoCapture的startRecording方法來錄影片
傳入一個File檔案用來儲存影片,
錄製完成之後回撥onVideoSaved方法,並返回該檔案的例項。
呼叫onFileSaved方法將前面儲存的檔案新增到媒體中,最後跳轉到預覽介面。
到達錄製時間的時候,需要呼叫videoCapture.stopRecording();方法來停止錄影。
到這裡使用CameraX拍照和錄製影片的功能都能完成了,是不是非常簡單。下面來點題外的,自定義一個View,實現點選拍照,長按錄影的效果。效果如下:
程式碼:
public class RecordView extends View implements View.OnLongClickListener, View.OnClickListener { private static final int PROGRESS_INTERVAL = 100; private int mBgColor; private int mStrokeColor; private int mStrokeWidth; private int mDuration; private int mWidth; private int mHeight; private int mRadius; private int mProgressValue; private boolean isRecording; private RectF mArcRectF; private Paint mBgPaint, mProgressPaint; private OnRecordListener mOnRecordListener; private long mStartRecordTime; public void setOnRecordListener(OnRecordListener onRecordListener) { mOnRecordListener = onRecordListener; } public RecordView(Context context) { this(context, null); } public RecordView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public RecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordView); mBgColor = typedArray.getColor(R.styleable.RecordView_bg_color, Color.WHITE); mStrokeColor = typedArray.getColor(R.styleable.RecordView_stroke_color, Color.RED); mStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.RecordView_stroke_width, SizeUtils.dp2px(5)); mDuration = typedArray.getInteger(R.styleable.RecordView_duration, 10); mRadius = typedArray.getDimensionPixelOffset(R.styleable.RecordView_radius, SizeUtils.dp2px(40)); typedArray.recycle(); mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBgPaint.setStyle(Paint.Style.FILL); mBgPaint.setColor(mBgColor); mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mProgressPaint.setStyle(Paint.Style.STROKE); mProgressPaint.setColor(mStrokeColor); mProgressPaint.setStrokeWidth(mStrokeWidth); setEvent(); } private void setEvent() { Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); mProgressValue++; postInvalidate(); if (mProgressValue < mDuration*10) { sendEmptyMessageDelayed(0, PROGRESS_INTERVAL); } else { finishRecord(); } } }; setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_DOWN){ mStartRecordTime = System.currentTimeMillis(); handler.sendEmptyMessage(0); }else if(event.getAction() == MotionEvent.ACTION_UP){ long duration = System.currentTimeMillis() - mStartRecordTime; //是否大於系統設定的最小長按時間 if(duration > ViewConfiguration.getLongPressTimeout()){ finishRecord(); } handler.removeCallbacksAndMessages(null); isRecording = false; mStartRecordTime = 0; mProgressValue = 0; postInvalidate(); } return false; } }); setOnClickListener(this); setOnLongClickListener(this); } private void finishRecord() { if(mOnRecordListener!=null){ mOnRecordListener.onFinish(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = w; mArcRectF = new RectF(mStrokeWidth / 2f, mStrokeWidth / 2f, mWidth - mStrokeWidth / 2f, mHeight - mStrokeWidth / 2f); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius, mBgPaint); if (isRecording) { canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius/10f*11, mBgPaint); float sweepAngle = 360f * mProgressValue / (mDuration*10); Log.i("sweepAngle",sweepAngle+""); canvas.drawArc(mArcRectF, -90, sweepAngle, false, mProgressPaint); } } @Override public boolean onLongClick(View v) { isRecording = true; if(mOnRecordListener!=null){ mOnRecordListener.onRecordVideo(); } return true; } @Override public void onClick(View v) { if(mOnRecordListener!=null){ mOnRecordListener.onTackPicture(); } } public interface OnRecordListener { void onTackPicture(); void onRecordVideo(); void onFinish(); } }
實現起來也非常簡單,首先繪製一個圓,監聽該View的點選和長按事件,長按的時候在根據總錄製時長和當前錄製時間算出需要繪製的角度,就可以在圓上面繪製進度了。
最後透過介面將點選 長按和錄製完成的事件返回,跟前面的拍照,錄製,錄製完成的程式碼結合起來就完成上面的效果了。
CameraView
如果覺得前面的初始化還不夠簡單,那麼可以使用CameraX提供的CameraView了,這裡面將PreviewView,Preview,ImageCapture,VideoCapture等都封裝起來了,而且還能實現縮放,裁剪,旋轉等功能,使用起來更加簡單。
首先xml檔案中新增CameraView
然後在Activity中例項化CameraView,直接繫結當前生命週期就可以了。
mBtnCameraSwitch = findViewById(R.id.camera_switch_button); mCameraView.bindToLifecycle(this);
只需兩句話就完成了前面的初始工作。然後就可以愉快的拍照和錄製影片了。
拍照和錄製的程式碼跟前面一樣只不過全都是透過CamerView物件來呼叫 mCameraView.takePicture
, mCameraView.startRecording
,呼叫之前需要透過 mCameraView.setCaptureMode(CameraView.CaptureMode.IMAGE)
來切換當前的模式是拍照還是錄影。
將前面的自定義的RecordView加入佈局檔案中,跟CameraView的拍照、錄影程式碼一結合,很快就能實現跟前面一樣的效果了。
圖片分析
CameraX還提供了影象分析功能,它提供了可供 CPU 訪問以執行影象處理、計算機視覺或機器學習推斷的影象,可以無縫的訪問緩衝區,一般用不到但功能很強大。建立一個圖片分析器然後繫結宣告週期即可。
mImageAnalysis = new ImageAnalysis.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation) .build(); mImageAnalysis.setAnalyzer(mExecutorService, new ImageAnalysis.Analyzer() { @Override public void analyze(@NonNull ImageProxy image) { } }); cameraProvider.bindToLifecycle(CameraActivity.this, cameraSelector,mPreview,mImageCapture,mVideoCapture,mImageAnalysis);
供應商擴充套件
供應商擴充套件程式:CameraX提供了外部擴充套件的API,可以直接對接手機產商,如果該手機廠商實現了CameraX的擴充套件程式,就可以使用VamerX的擴充套件API直接呼叫這些效果比如:美顏、DHR、夜間、自動等模式。
因為不是所有的手機廠商都支援擴充套件程式,所以在使用擴充套件的時候需要判斷一下該手機是否支援,支援才新增。
給預覽介面設定外部擴充套件,需要 Preview.Builder
和 CameraSelector cameraSelector)
兩個引數
private void setPreviewExtender(Preview.Builder builder, CameraSelector cameraSelector) { AutoPreviewExtender extender = AutoPreviewExtender.create(builder); if(extender.isExtensionAvailable(cameraSelector)){ extender.enableExtension(cameraSelector); } BokehPreviewExtender bokehPreviewExtender = BokehPreviewExtender.create(builder); if(bokehPreviewExtender.isExtensionAvailable(cameraSelector)){ bokehPreviewExtender.enableExtension(cameraSelector); } HdrPreviewExtender hdrPreviewExtender = HdrPreviewExtender.create(builder); if(hdrPreviewExtender.isExtensionAvailable(cameraSelector)){ hdrPreviewExtender.enableExtension(cameraSelector); } BeautyPreviewExtender beautyPreviewExtender = BeautyPreviewExtender.create(builder); if(beautyPreviewExtender.isExtensionAvailable(cameraSelector)){ beautyPreviewExtender.enableExtension(cameraSelector); } NightPreviewExtender nightPreviewExtender = NightPreviewExtender.create(builder); if(nightPreviewExtender.isExtensionAvailable(cameraSelector)){ nightPreviewExtender.enableExtension(cameraSelector); } }
給拍攝的圖片設定外部擴充套件,,需要 ImageCapture.Builder
和 CameraSelector cameraSelector)
兩個引數
private void setImageCaptureExtender(ImageCapture.Builder builder, CameraSelector cameraSelector) { AutoImageCaptureExtender autoImageCaptureExtender = AutoImageCaptureExtender.create(builder); if (autoImageCaptureExtender.isExtensionAvailable(cameraSelector)) { autoImageCaptureExtender.enableExtension(cameraSelector); } BokehImageCaptureExtender bokehImageCaptureExtender = BokehImageCaptureExtender.create(builder); if(bokehImageCaptureExtender.isExtensionAvailable(cameraSelector)){ bokehImageCaptureExtender.enableExtension(cameraSelector); } HdrImageCaptureExtender hdrImageCaptureExtender = HdrImageCaptureExtender.create(builder); if(hdrImageCaptureExtender.isExtensionAvailable(cameraSelector)){ hdrImageCaptureExtender.enableExtension(cameraSelector); } BeautyImageCaptureExtender beautyImageCaptureExtender = BeautyImageCaptureExtender.create(builder); if(beautyImageCaptureExtender.isExtensionAvailable(cameraSelector)){ beautyImageCaptureExtender.enableExtension(cameraSelector); } NightImageCaptureExtender nightImageCaptureExtender = NightImageCaptureExtender.create(builder); if(nightImageCaptureExtender.isExtensionAvailable(cameraSelector)){ nightImageCaptureExtender.enableExtension(cameraSelector); } }
[bom485332 ] JetPack開發中使用CameraX完成拍照和拍影片功能已經有264次圍觀