JetPack开发中使用CameraX完成拍照和拍视频功能

前段时间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可以监听这个任务,当任务完成的时候执行回调
 ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
  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这个实例上

<androidx.camera.view.PreviewView
 android:id="@+id/view_finder"
 android:layout_width="match_parent"
 android:layout_height="match_parent" />

 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

<androidx.camera.view.CameraView
 android:id="@+id/view_finder"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 />

然后在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.BuilderCameraSelector 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.BuilderCameraSelector 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);
 }
 }

demo地址: 地址链接

总结

到此这篇关于JetPack之使用CameraX完成拍照和拍视频的文章就介绍到这了,更多相关JetPack使用CameraX拍照和拍视频内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JetPack开发中使用CameraX完成拍照和拍视频功能

    前段时间CameraX的Beta版发布了,这几天有时间也来尝试一下.Beta版本是对外测试版本,意味着它已经走出实验室走向生产,API的调用基本稳定不会大改了,bug也会更少可以用于生成环境. 之前使用Camera1和Camera2开发相机功能的时候需要调用非常复杂的API,而且由于Android手机的碎片化严重,不同手机对相机功能的支持度也不一样,因此很多做相机相关应用的公司都会封装自己的相机库来简化相机的使用步骤和处理兼容性问题. CameraX其实就是Google开发的一个用来简化相机开发

  • Android开发中RecyclerView模仿探探左右滑动布局功能

    我在此基础上优化了部分代码, 添加了滑动回调, 可自定义性更强. 并且添加了点击按钮左右滑动的功能. 据说无图都不敢发文章了. 看图: 1:这种功能, 首先需要自己管理布局 继承 RecyclerView.LayoutManager , 显示自己管理布局, 比如最多显示4个view, 并且都是居中显示. 底部的View还需要进行缩放,平移操作. public class OverLayCardLayoutManager extends RecyclerView.LayoutManager { p

  • Android实现拍照和录制视频功能

    本文实例为大家分享了Android实现拍照和录制视频功能的具体代码,供大家参考,具体内容如下 文档中的Camera 要使用Camera,首先我们先看一下文档中是怎么介绍的.相对于其他绝大多数类,文档对Camera的介绍还是比较详尽的,包含了使用过程中所需要的步骤说明,当然,这也表明了它在实际使用中的繁琐. 首先,需要在AndroidManifest.xml中声明以下权限和特性: <uses-permission android:name="android.permission.CAMERA

  • .net开发中批量删除记录时实现全选功能的具体方法

    1 . JS实现全选 往页面上拖一个GridView,设置好数据源,并为GridView添加一个模板列,往模板列里添加一个chekcbox,比如下面的代码 复制代码 代码如下: <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID"    DataSourceID="SqlD

  • iOS开发中使用CoreLocation框架处理地理编码的方法

    一.简介 1.在移动互联网时代,移动app能解决用户的很多生活琐事,比如 (1)导航:去任意陌生的地方 (2)周边:找餐馆.找酒店.找银行.找电影院 2.在上述应用中,都用到了地图和定位功能,在iOS开发中,要想加入这2大功能,必须基于2个框架进行开发 (1)Map Kit :用于地图展示 (2)Core Location :用于地理定位 3.两个热门专业术语 (1)LBS :Location Based Service(基于定位的服务) (2)SoLoMo :Social Local Mobi

  • Android 开发手机(三星)拍照应用照片旋转问题解决办法

    Android 开发手机(三星)拍照应用照片旋转问题解决办法 最近解决了一个令我头疼好久的问题,就是三星手机拍照图片旋转的问题,项目中有上传图片的功能,那么涉及到拍照,从相册中选择图片,别的手机都ok没有问题,唯独三星的手机拍照之后,你会很清楚的看到会把照片旋转一下,然后你根据路径找到的图片就是已经被旋转的了,解决办法终于被我找到了.我们可以根据图片的路径读取照片exif(Exchangeable Image File 可交换图像文件)信息中的旋转角度 根据调试,可以清楚的发现三星手机拍照的图片

  • Android开发中ImageView的scaletype属性用法分析

    本文实例讲述了Android开发中ImageView的scaletype属性用法.分享给大家供大家参考,具体如下: ImageView的属性android:scaleType,即 ImageView.setScaleType(ImageView.ScaleType).android:scaleType是控制图片如何 resized/moved来匹对ImageView的size.ImageView.ScaleType / android:scaleType值的意义区别: CENTER /cente

  • java微信开发中的地图定位功能

    页面代码: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+&q

  • Android开发中关于组件导出的风险及防范

    前言 近年来,移动APP存在一个非常的重要的问题就是安全问题,造成的后果有可能是用户的隐私泄露和财产损失等,对于一款成熟的APP或者是金融银行类APP,这无疑是最致命的,所以对APP进行有效的防范也是很有必要. 近段时间,公司安排了某安全公司对我们的APP进行了全方面的安全测试,根据文档检测结果看,整体上看还是很安全的,其中有一项就是组件导出风险,接下来我们说说四大组件.组件导出必要性.风险以及如何防范. 一.四大组件 从事Android开发,我们都知道Android有四大组件, 分别是: 活动

  • Android开发中使用颜色矩阵改变图片颜色,透明度及亮度的方法

    本文实例讲述了Android开发中使用颜色矩阵改变图片颜色,透明度及亮度的方法.分享给大家供大家参考,具体如下: 一.如图 二.代码实现 public class ColorImageActivity extends Activity { private ImageView mImageView; private SeekBar mSBRed,mSBGreen,mSBBlue,mSBAlpha,mSBLight; //修改后的图片 private Bitmap mModBitmap; //画布

随机推荐