Camera2 和CameraX 从入门到精通 java实现
此文章我会用Camera2 和CameraX 分别实现预览拍照录制视频
目录
此文章我会用Camera2 和CameraX 分别实现预览拍照录制视频
Camera2
APP实现 Camera2 需要提前声明
相机方向
Camera层级架构
API2流程
废话不多说 实战开始
CameraX
CameraX api
CameraX demo
Camera2
APP实现 Camera2 需要提前声明
相机方向
相机相对于手机屏幕默认方向不一致:
前置摄像头(指向与显示屏方向相同的摄像头)的传感器相对于手机旋转 270 度(顺时针),以符合 Android 兼容性定义
手机的传感器是顺时针旋转的 左横屏就是90度
手机的传感器是顺时针旋转的 右横屏就是270度或者-90度
Camera层级架构
Camera 只是Android的一部分 所以框架层级整体跟Android一样为
Applications 应用层 -----------------------------对应camera APP
Framework层 -----------------------------对应Java Framework
Libraries 系统运行库层 -----------------------------对应Native Framework (CameraService)
Hardwre Abstraction layer HAL硬件抽象层-----------------------------对应Camera Provider
Linux Kernel 内核层 -----------------------------对应Camera Driver
Camera根据Android 架构从上至下可分为
1)Applications: 最上层的应用,编译后生成Camera APK;
2)Application Framework: 主要为Applications提供API;
3)JNI: 使Application Framework和Libraries可交互;
4)Libraries: 包括Camera Framework和Camera Service(camera service和camera client);
5)HAL: 硬件抽象层, 用来链接driver和 Camera Service;
6)Kernel: image sensor driver的实作.
API2流程
解释下来 其实就是CameraManager调用openCamera 下发open指令到底层,然后在CameraDevice.StateCallback 的onOpened 中 拿到底层返回来的CameraDevice 通过CameraDevice去创建Session(createCaptureSession) 创建Session是需要添加对应的surface 预览的surface 有TextureView SurfaceView 拍照对应的Surface 为ImageReader 然后在Session的状态回调的onConfigured里(CameraCaptureSession.StateCallback)拿到CameraCaptureSession对象 通过CameraCaptureSession 可以申请预览 setRepeatingRequest 或者是 拍照 Capture 录制视频 需要配置 MediaRecorder
废话不多说 实战开始
首先利用AndroidStudio 创建工程
Next
Finish
打开布局文件
一步步实现 就慢慢来,activity_main.xml布局文件代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"><TextureViewandroid:id="@+id/previewSurfaceView"android:layout_width="match_parent"android:layout_height="match_parent" /><ImageButtonandroid:id="@+id/takePictureButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="70dp"android:background="@drawable/shape_white_ring"android:src="@drawable/shape_take_photo" /></RelativeLayout>
看到我放了两个控件一个TextureView 用来预览的控件 SurfaceView 等等也可以 还有一个拍照按钮 ImageButton
首先我们实现预览 预览是一切的基础
package com.example.camera2demo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;public class MainActivity extends AppCompatActivity implements View.OnClickListener {//预览画面控件private TextureView mTextureView;//拍照按钮private ImageButton mTakePictureButton;//日志的tagprivate final String TAG = "Camera2Demo" ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化预览控件mTextureView = findViewById(R.id.previewSurfaceView);//初始化拍照按钮mTakePictureButton = findViewById(R.id.takePictureButton);}@Overrideprotected void onStart() {super.onStart();//设置拍照按钮监听mTakePictureButton.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.takePictureButton:takePicture();break;}}private void takePicture() {Log.d(TAG,"-----takePicture");}}
可以看到 就是在onCreate 里去 初始化 预览控件和拍照按钮 并在 onStart 里去设置按钮 监听
在点击时间里 写了空函数 takePicture 基本框架已经实现
要实现预览 我们是需要 申请Camera权限的 所以接下来我们先申请权限
Android 6.0 之前只需要在 AndroidManifest.xml 中加入 就OK 6.0 之后需要加入并且动态申请权限
<uses-permission android:name="android.permission.CAMERA" />
代码 块如下
package com.example.camera2demo;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;public class MainActivity extends AppCompatActivity implements View.OnClickListener {//预览画面控件private TextureView mTextureView;//拍照按钮private ImageButton mTakePictureButton;//日志的tagprivate final String TAG = "Camera2Demo";//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA",};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化预览控件mTextureView = findViewById(R.id.previewSurfaceView);//初始化拍照按钮mTakePictureButton = findViewById(R.id.takePictureButton);}@Overrideprotected void onResume() {super.onResume();//检查权限申请权限if (allPermissionsGranted()) {Log.d(TAG, "权限已授予");} else {Log.d(TAG, "申请权限");ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}@Overrideprotected void onStart() {super.onStart();//设置拍照按钮监听mTakePictureButton.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.takePictureButton:takePicture();break;}}private boolean allPermissionsGranted() {Log.d(TAG, "----- 检查权限");for (String permission : REQUIRED_PERMISSIONS) {if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {return false;}}return true;}//权限回调 申请完权限之后 返回的结果在这里接收@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);//这个1 是申请权限时的第三个参数if (requestCode == 1) {if (allPermissionsGranted()) {openCamera();} else {ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}}private void openCamera() {Log.d(TAG,"----------openCamera");}private void takePicture() {Log.d(TAG,"-----takePicture");}}
在刚刚 的基础之上加了三个函数 一个检查权限的函数 一个权限回调函数 一个 openCamera的空函数 权限获取之后我们就正式进入openCamera
package com.example.camera2demo;import static java.lang.String.valueOf;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;public class MainActivity extends AppCompatActivity implements View.OnClickListener {//预览画面控件private TextureView mTextureView;//拍照按钮private ImageButton mTakePictureButton;//日志的tagprivate final String TAG = "Camera2Demo";//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA",};//处理回调函数的线程private HandlerThread mCameraThread;private Handler mCameraHandler;//具体的相机设备private CameraDevice mCameraDevice;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化预览控件mTextureView = findViewById(R.id.previewSurfaceView);//初始化拍照按钮mTakePictureButton = findViewById(R.id.takePictureButton);//开启线程 用于处理回调函数 可开可不开startCameraThread();}@Overrideprotected void onResume() {super.onResume();//检查权限申请权限if (allPermissionsGranted()) {Log.d(TAG, "权限已授予");} else {Log.d(TAG, "申请权限");ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}@Overrideprotected void onStart() {super.onStart();//设置拍照按钮监听mTakePictureButton.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.takePictureButton:takePicture();break;}}private boolean allPermissionsGranted() {Log.d(TAG, "----- 检查权限");for (String permission : REQUIRED_PERMISSIONS) {if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {return false;}}return true;}//权限回调 申请完权限之后 返回的结果在这里接收@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);//这个1 是申请权限时的第三个参数if (requestCode == 1) {if (allPermissionsGranted()) {openCamera();} else {ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}}//开启线程 用于处理回调函数 可开可不开private void startCameraThread() {mCameraThread = new HandlerThread("CameraThread");mCameraThread.start();mCameraHandler = new Handler(mCameraThread.getLooper());}@SuppressLint("MissingPermission")private void openCamera() {Log.d(TAG, "----------openCamera");// 1.获取CameraManager 需要注意的是这里需要强转CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);// 2.通过CameraManager 获取相机IDtry {String cameraId[] = cameraManager.getCameraIdList();for (String s : cameraId) {Log.d(TAG, "--------- cameraId = " + s);}// 3. 获取 0 后置的相机信息CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");// 例如获取分辨率的信息 通过对应的KEY 获取对应信息StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);for (Size size : sizes) {Log.d(TAG,"size = "+size);}// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调// 第三个是处理回调的线程 这里需要检查权限cameraManager.openCamera(valueOf(0),cameraDeviceStateCallback,mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {}};private void takePicture() {Log.d(TAG, "-----takePicture");}}
这段代码 开了个线程来处理回调函数可以要可不要,如果选择在主线程执行就可以穿null,加入了 openCamera 的关键 步骤获取CameraManger 通过CameraManger 可以获取到相机ID 如下图
还可以获取相机的特征信息 比如支持的分辨率 如下图
最核心的是可以下发openCamera 指令
然后在回调里拿到 CameraDevice 拿到CameraDevice 就可以创建Session 接下来是创建Session 建立预览
package com.example.camera2demo;import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO;
import static java.lang.String.valueOf;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;import java.util.Arrays;public class MainActivity extends AppCompatActivity implements View.OnClickListener {//预览画面控件private TextureView mTextureView;//拍照按钮private ImageButton mTakePictureButton;//日志的tagprivate final String TAG = "Camera2Demo";//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA",};//处理回调函数的线程private HandlerThread mCameraThread;private Handler mCameraHandler;//具体的相机设备private CameraDevice mCameraDevice;//创建Session的构建者private CaptureRequest.Builder mCaptureRequestBuilder;//CameraCaptureSession是创建预览拍照 录像请求的关键 可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果private CameraCaptureSession mCameraCaptureSession;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化预览控件mTextureView = findViewById(R.id.previewSurfaceView);//设置预览控件的监听 防止surface 没有渲染好就打开相机了mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);//初始化拍照按钮mTakePictureButton = findViewById(R.id.takePictureButton);//开启线程 用于处理回调函数 可开可不开startCameraThread();}@Overrideprotected void onResume() {super.onResume();}@Overrideprotected void onStart() {super.onStart();//设置拍照按钮监听mTakePictureButton.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.takePictureButton:takePicture();break;}}private boolean allPermissionsGranted() {Log.d(TAG, "----- 检查权限");for (String permission : REQUIRED_PERMISSIONS) {if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {return false;}}return true;}//权限回调 申请完权限之后 返回的结果在这里接收@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);//这个1 是申请权限时的第三个参数if (requestCode == 1) {if (allPermissionsGranted()) {openCamera();} else {ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}}//开启线程 用于处理回调函数 可开可不开private void startCameraThread() {mCameraThread = new HandlerThread("CameraThread");mCameraThread.start();mCameraHandler = new Handler(mCameraThread.getLooper());}@SuppressLint("MissingPermission")private void openCamera() {Log.d(TAG, "----------openCamera");// 1.获取CameraManager 需要注意的是这里需要强转CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);// 2.通过CameraManager 获取相机IDtry {String cameraId[] = cameraManager.getCameraIdList();for (String s : cameraId) {Log.d(TAG, "--------- cameraId = " + s);}// 3. 获取 0 后置的相机信息CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");// 例如获取分辨率的信息 通过对应的KEY 获取对应信息StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);for (Size size : sizes) {Log.d(TAG, "size = " + size);}// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调// 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行 这里需要检查权限cameraManager.openCamera(valueOf(0), cameraDeviceStateCallback, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;createPreView();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {}};private void createPreView() {//创建预览 之前创建Session 创建Session 需要接收数据的Surface//下面就是获取SurfaceSurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();//设置SurfaceView的缓冲区分辨率 与手机用的分辨一致 就可以防止预览拉伸surfaceTexture.setDefaultBufferSize(4160, 1856);Surface previewSurface = new Surface(surfaceTexture);//如果是surfaceView 如下
// SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
// surfaceHolder.setFixedSize(1920,1080);
// surface = surfaceHolder.getSurface();try {//创建Session的构建者mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//将Surface 传递下去mCaptureRequestBuilder.addTarget(previewSurface);//通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//创建SessionmCameraDevice.createCaptureSession(Arrays.asList(previewSurface), cameraCaptureSessionStateCallback, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCameraCaptureSession = session;try {session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {}};//很多人写到这里觉得大功告成了 是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入 崩溃了 还找不到原因 这里是因为 我们openCamera时Surface//还么渲染好 导致的解决办法有两个//1TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {//检查权限申请权限if (allPermissionsGranted()) {Log.d(TAG, "权限已授予");openCamera();} else {Log.d(TAG, "申请权限");ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}@Overridepublic void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}};//2.在onCreate里添加下面这段
// mTextureView.post(new Runnable() {
// @Override
// public void run() {
// if (allPermissionsGranted()) {
// Log.d(TAG,"权限ok");
// startCamera();
// } else {
// Log.d(TAG,"申请权限");
// ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
// }
// }
// });private void takePicture() {Log.d(TAG, "-----takePicture");}}
至此简单的预览完成了
预览拍照代码
package com.example.camera2demo;import static java.lang.String.valueOf;import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;public class MainActivity extends AppCompatActivity implements View.OnClickListener {//预览画面控件private TextureView mTextureView;//拍照按钮private ImageButton mTakePictureButton;//日志的tagprivate final String TAG = "Camera2Demo";//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA","android.permission.WRITE_EXTERNAL_STORAGE","android.permission.READ_EXTERNAL_STORAGE",};//处理回调函数的线程private HandlerThread mCameraThread;private Handler mCameraHandler;//具体的相机设备private CameraDevice mCameraDevice;//创建Session的构建者private CaptureRequest.Builder mCaptureRequestBuilder;//CameraCaptureSession是创建预览拍照 录像请求的关键 可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果private CameraCaptureSession mCameraCaptureSession;private ImageReader mImageReader = null;protected ImageView mThumbnail;//用于处理拍照方向问题protected static final SparseIntArray ORIENTATION = new SparseIntArray();static {ORIENTATION.append(Surface.ROTATION_0, 90);//90 270 还没处理ORIENTATION.append(Surface.ROTATION_90, 0);ORIENTATION.append(Surface.ROTATION_180, 270);ORIENTATION.append(Surface.ROTATION_270, 180);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化预览控件mTextureView = findViewById(R.id.previewSurfaceView);//设置预览控件的监听 防止surface 没有渲染好就打开相机了mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);//初始化拍照按钮mTakePictureButton = findViewById(R.id.takePictureButton);mThumbnail = findViewById(R.id.thumbnail);mThumbnail.setOnClickListener(this);//开启线程 用于处理回调函数 可开可不开startCameraThread();//去掉导航栏getSupportActionBar().hide();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//透明状态栏getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//透明导航栏getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);}}@Overrideprotected void onResume() {super.onResume();}@Overrideprotected void onStart() {super.onStart();//设置拍照按钮监听mTakePictureButton.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.takePictureButton:takePicture();break;case R.id.thumbnail:gotoGallery();break;}}private boolean allPermissionsGranted() {Log.d(TAG, "----- 检查权限");for (String permission : REQUIRED_PERMISSIONS) {if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {return false;}}return true;}//权限回调 申请完权限之后 返回的结果在这里接收@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);//这个1 是申请权限时的第三个参数if (requestCode == 1) {if (allPermissionsGranted()) {openCamera();} else {ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}}//开启线程 用于处理回调函数 可开可不开private void startCameraThread() {mCameraThread = new HandlerThread("CameraThread");mCameraThread.start();mCameraHandler = new Handler(mCameraThread.getLooper());}@SuppressLint("MissingPermission")private void openCamera() {Log.d(TAG, "----------openCamera");// 1.获取CameraManager 需要注意的是这里需要强转CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);// 2.通过CameraManager 获取相机IDtry {String cameraId[] = cameraManager.getCameraIdList();for (String s : cameraId) {Log.d(TAG, "--------- cameraId = " + s);}// 3. 获取 0 后置的相机信息CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");// 例如获取分辨率的信息 通过对应的KEY 获取对应信息StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);for (Size size : sizes) {Log.d(TAG, "size = " + size);}// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调// 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行 这里需要检查权限cameraManager.openCamera(valueOf(0), cameraDeviceStateCallback, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;createPreView();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {}};private void createPreView() {initImageReader(); //拍照要初始化//创建预览 之前创建Session 创建Session 需要接收数据的Surface//下面就是获取SurfaceSurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();//设置SurfaceView的缓冲区分辨率 与手机用的分辨一致 就可以防止预览拉伸surfaceTexture.setDefaultBufferSize(1920, 1080);Surface previewSurface = new Surface(surfaceTexture);//如果是surfaceView 如下
// SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
// surfaceHolder.setFixedSize(1920,1080);
// surface = surfaceHolder.getSurface();try {//创建Session的构建者mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//将Surface 传递下去mCaptureRequestBuilder.addTarget(previewSurface);//通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//创建SessionmCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), cameraCaptureSessionStateCallback, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCameraCaptureSession = session;try {session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {}};//很多人写到这里觉得大功告成了 是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入 崩溃了 还找不到原因 这里是因为 我们openCamera时Surface//还么渲染好 导致的解决办法有两个//1TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {//检查权限申请权限if (allPermissionsGranted()) {Log.d(TAG, "权限已授予");openCamera();} else {Log.d(TAG, "申请权限");ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}showThumbnail();}@Overridepublic void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}};//2.在onCreate里添加下面这段
// mTextureView.post(new Runnable() {
// @Override
// public void run() {
// if (allPermissionsGranted()) {
// Log.d(TAG,"权限ok");
// startCamera();
// } else {
// Log.d(TAG,"申请权限");
// ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
// }
// }
// });//拍照 开始拍照之前得配置对应的Surfac 就是ImageReaderprotected void initImageReader() {Log.d("djh", "--------------------initImageReader");//四个参数分别是照片的分辨率 格式 最多获取几帧mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);//设置ImageReader监听,当有图像流数据可用时会回调onImageAvailablemImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader imageReader) {Toast.makeText(MainActivity.this, "图片已保存", Toast.LENGTH_SHORT).show();//获得Image 数据Image image = imageReader.acquireNextImage();//可以开启线程保存图片new Thread(new Runnable() {@Overridepublic void run() {//字节缓冲ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);String path = Environment.getExternalStorageDirectory() + "/DCIM/camera/myPicture"+ System.currentTimeMillis() + ".jpg";File imageFile = new File(path);FileOutputStream fos = null;try {fos = new FileOutputStream(imageFile);fos.write(data, 0, data.length);} catch (IOException e) {e.printStackTrace();} finally {if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}broadcast();image.close(); // 必须关闭 不然拍第二章会报错showThumbnail();}}}).start();}}, null);}private void takePicture() {Log.d(TAG, "-----takePicture");try {//拍照跟预览的区别就是Builder不一样回调不一样仅此而已CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mImageReader.getSurface());int rotation = getWindowManager().getDefaultDisplay().getRotation();captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));mCameraCaptureSession.stopRepeating();mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {super.onCaptureStarted(session, request, timestamp, frameNumber);try {session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null);} catch (CameraAccessException e) {e.printStackTrace();}}};// 通知广播刷新相册protected void broadcast() {Log.d("djh", "--------------------broadcast");String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);Uri uri = Uri.fromFile(new File(path));intent.setData(uri);MainActivity.this.sendBroadcast(intent);}//预览拍照完成显示缩略图protected void showThumbnail() {ArrayList<String> imageList = getImageFilePath();String path = imageList.get(imageList.size() - 1);if (path.contains("jpg")) {Bitmap bitmap = (Bitmap) BitmapFactory.decodeFile(path);//又是bitmap 为空 忘记给mImageView finViewByIdmThumbnail.setImageBitmap(bitmap);int rotation = getWindowManager().getDefaultDisplay().getRotation();mThumbnail.setRotation(ORIENTATION.get(rotation));} else {MediaMetadataRetriever retriever = new MediaMetadataRetriever();retriever.setDataSource(path);//取第一帧Bitmap bitmap = retriever.getFrameAtTime(1);mThumbnail.setImageBitmap(bitmap);}}//遍历系统相册protected ArrayList<String> getImageFilePath() {ArrayList<String> imageList = new ArrayList<>();File file = new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");File[] dirEpub = file.listFiles();if (dirEpub.length != 0) {for (int i = 0; i < dirEpub.length; i++) {String fileName = dirEpub[i].toString();imageList.add(fileName);Log.i("File", "File name = " + fileName);}}return imageList;}//跳转相册 (画廊)protected void gotoGallery() {ArrayList<String> temp = getImageFilePath();String lastPath = temp.get(temp.size() - 1);Uri uri = getMediaUriFromPath(this, lastPath);Intent intent = new Intent("com.android.camera.action.REVIEW", uri);intent.setData(uri);startActivity(intent);}@SuppressLint("Range")public Uri getMediaUriFromPath(Context context, String path) {Uri uri = null;if (path.contains("jpg")) {Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;Cursor cursor = context.getContentResolver().query(picUri,null,MediaStore.Images.Media.DISPLAY_NAME + "= ?",new String[]{path.substring(path.lastIndexOf("/") + 1)},null);if (cursor.moveToFirst()) {uri = ContentUris.withAppendedId(picUri,cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));}cursor.close();} else if (path.contains("mp4")) {Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;Cursor cursor = context.getContentResolver().query(mediaUri,null,MediaStore.Video.Media.DISPLAY_NAME + "= ?",new String[]{path.substring(path.lastIndexOf("/") + 1)},null);if (cursor.moveToFirst()) {uri = ContentUris.withAppendedId(mediaUri,cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));}cursor.close();}return uri;}}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"><TextureViewandroid:id="@+id/previewSurfaceView"android:layout_width="match_parent"android:layout_height="match_parent" /><ImageButtonandroid:id="@+id/takePictureButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="70dp"android:background="@drawable/shape_white_ring"android:src="@drawable/shape_take_photo" /><ImageViewandroid:id="@+id/thumbnail"android:layout_width="45dp"android:layout_height="55dp"android:layout_alignParentBottom="true"android:layout_marginLeft="50dp"android:layout_marginBottom="70dp" /><ImageButtonandroid:id="@+id/changeCamera"android:layout_width="45dp"android:layout_height="45dp"android:layout_alignParentRight="true"android:layout_alignParentBottom="true"android:layout_marginRight="50dp"android:layout_marginBottom="77dp"android:background="@drawable/camera_quick_switch" /></RelativeLayout>
再下来就是录像了
录像跟拍照很相似 只是 CaptureRequest.Builder对象不一样 需要配置一个MediaRecorder
预览拍照录制视频的完整代码
package com.example.camera2demo;import static java.lang.String.valueOf;import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;public class MainActivity extends AppCompatActivity implements View.OnClickListener {//预览画面控件private TextureView mTextureView;//拍照按钮private ImageButton mTakePictureButton;//日志的tagprivate final String TAG = "Camera2Demo";//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA","android.permission.WRITE_EXTERNAL_STORAGE","android.permission.READ_EXTERNAL_STORAGE","android.permission.RECORD_AUDIO",};//处理回调函数的线程private HandlerThread mCameraThread;private Handler mCameraHandler;//具体的相机设备private CameraDevice mCameraDevice;//创建Session的构建者private CaptureRequest.Builder mPreviewBuilder;//CameraCaptureSession是创建预览拍照 录像请求的关键 可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果private CameraCaptureSession mCameraCaptureSession;private ImageReader mImageReader = null;protected ImageView mThumbnail;//用于处理拍照方向问题protected static final SparseIntArray ORIENTATION = new SparseIntArray();static {ORIENTATION.append(Surface.ROTATION_0, 90);//90 270 还没处理ORIENTATION.append(Surface.ROTATION_90, 0);ORIENTATION.append(Surface.ROTATION_180, 270);ORIENTATION.append(Surface.ROTATION_270, 180);}private Button mPhotoMode;private Button mVideoMode;//拍照按钮private ImageButton mCapTureVideoButton;private MediaRecorder mMediaRecorder;private boolean bStop = false;private ImageButton mChangeCamera;private String mCameraId = "0";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化预览控件mTextureView = findViewById(R.id.previewSurfaceView);//设置预览控件的监听 防止surface 没有渲染好就打开相机了mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);//初始化拍照按钮mTakePictureButton = findViewById(R.id.takePictureButton);mThumbnail = findViewById(R.id.thumbnail);mThumbnail.setOnClickListener(this);//开启线程 用于处理回调函数 可开可不开startCameraThread();//去掉导航栏getSupportActionBar().hide();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//透明状态栏getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//透明导航栏getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);}mPhotoMode = findViewById(R.id.photoMode);mPhotoMode.setOnClickListener(this);mVideoMode = findViewById(R.id.videoMode);mVideoMode.setOnClickListener(this);mCapTureVideoButton = findViewById(R.id.captureVideoButton);mCapTureVideoButton.setOnClickListener(this);mChangeCamera = findViewById(R.id.changeCamera);mChangeCamera.setOnClickListener(this);}@Overrideprotected void onResume() {super.onResume();}@Overrideprotected void onStart() {super.onStart();//设置拍照按钮监听mTakePictureButton.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.takePictureButton:takePicture();break;case R.id.thumbnail:gotoGallery();break;case R.id.photoMode:mPhotoMode.setVisibility(View.GONE);mVideoMode.setVisibility(View.VISIBLE);mTakePictureButton.setVisibility(View.GONE);mCapTureVideoButton.setVisibility(View.VISIBLE);break;case R.id.videoMode:mVideoMode.setVisibility(View.GONE);mPhotoMode.setVisibility(View.VISIBLE);mTakePictureButton.setVisibility(View.VISIBLE);mCapTureVideoButton.setVisibility(View.GONE);break;case R.id.captureVideoButton:if (!bStop) {bStop = true;captureVideo();} else {bStop = false;stopVideo();}break;case R.id.changeCamera:changeCameraId();break;}}private boolean allPermissionsGranted() {Log.d(TAG, "----- 检查权限");for (String permission : REQUIRED_PERMISSIONS) {if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {return false;}}return true;}//权限回调 申请完权限之后 返回的结果在这里接收@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);//这个1 是申请权限时的第三个参数if (requestCode == 1) {if (allPermissionsGranted()) {openCamera();} else {ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}}}//开启线程 用于处理回调函数 可开可不开private void startCameraThread() {mCameraThread = new HandlerThread("CameraThread");mCameraThread.start();mCameraHandler = new Handler(mCameraThread.getLooper());}@SuppressLint("MissingPermission")private void openCamera() {Log.d(TAG, "----------openCamera");// 1.获取CameraManager 需要注意的是这里需要强转CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);// 2.通过CameraManager 获取相机IDtry {String cameraId[] = cameraManager.getCameraIdList();for (String s : cameraId) {Log.d(TAG, "--------- cameraId = " + s);}// 3. 获取 0 后置的相机信息CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");// 例如获取分辨率的信息 通过对应的KEY 获取对应信息StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);for (Size size : sizes) {Log.d(TAG, "size = " + size);}// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调// 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行 这里需要检查权限cameraManager.openCamera(mCameraId, cameraDeviceStateCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;createPreView();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {}};private void createPreView() {initImageReader(); //拍照要初始化configMediaRecorder();//录制视频需要初始化//创建预览 之前创建Session 创建Session 需要接收数据的Surface//下面就是获取SurfaceSurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();//设置SurfaceView的缓冲区分辨率 与手机用的分辨一致 就可以防止预览拉伸surfaceTexture.setDefaultBufferSize(1920, 1080);Surface previewSurface = new Surface(surfaceTexture);//如果是surfaceView 如下
// SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
// surfaceHolder.setFixedSize(1920,1080);
// surface = surfaceHolder.getSurface();try {//创建Session的构建者mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//将Surface 传递下去mPreviewBuilder.addTarget(previewSurface);mPreviewBuilder.addTarget(mMediaRecorder.getSurface());//通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//创建SessionmCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface(), mMediaRecorder.getSurface()), cameraCaptureSessionStateCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCameraCaptureSession = session;try {session.setRepeatingRequest(mPreviewBuilder.build(), null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {}};//很多人写到这里觉得大功告成了 是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入 崩溃了 还找不到原因 这里是因为 我们openCamera时Surface//还么渲染好 导致的解决办法有两个//1TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {//检查权限申请权限if (allPermissionsGranted()) {Log.d(TAG, "权限已授予");openCamera();} else {Log.d(TAG, "申请权限");ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);}//showThumbnail();}@Overridepublic void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}};//2.在onCreate里添加下面这段
// mTextureView.post(new Runnable() {
// @Override
// public void run() {
// if (allPermissionsGranted()) {
// Log.d(TAG,"权限ok");
// startCamera();
// } else {
// Log.d(TAG,"申请权限");
// ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
// }
// }
// });//拍照 开始拍照之前得配置对应的Surfac 就是ImageReaderprotected void initImageReader() {Log.d("djh", "--------------------initImageReader");//四个参数分别是照片的分辨率 格式 最多获取几帧mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);//设置ImageReader监听,当有图像流数据可用时会回调onImageAvailablemImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader imageReader) {Toast.makeText(MainActivity.this, "图片已保存", Toast.LENGTH_SHORT).show();//获得Image 数据Image image = imageReader.acquireNextImage();//可以开启线程保存图片new Thread(new Runnable() {@Overridepublic void run() {//字节缓冲ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);String path = Environment.getExternalStorageDirectory() + "/DCIM/camera/myPicture"+ System.currentTimeMillis() + ".jpg";File imageFile = new File(path);FileOutputStream fos = null;try {fos = new FileOutputStream(imageFile);fos.write(data, 0, data.length);} catch (IOException e) {e.printStackTrace();} finally {if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}broadcast();image.close(); // 必须关闭 不然拍第二章会报错//showThumbnail();}}}).start();}}, null);}private void takePicture() {Log.d(TAG, "-----takePicture");try {//拍照跟预览的区别就是Builder不一样回调不一样仅此而已CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mImageReader.getSurface());int rotation = getWindowManager().getDefaultDisplay().getRotation();if (mCameraId.equals("1")) {captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation) + 180);} else {captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));}mCameraCaptureSession.stopRepeating();mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {super.onCaptureStarted(session, request, timestamp, frameNumber);try {session.setRepeatingRequest(mPreviewBuilder.build(), null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}};// 通知广播刷新相册protected void broadcast() {Log.d("djh", "--------------------broadcast");String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);Uri uri = Uri.fromFile(new File(path));intent.setData(uri);MainActivity.this.sendBroadcast(intent);}//预览拍照完成显示缩略图protected void showThumbnail() {ArrayList<String> imageList = getImageFilePath();String path = imageList.get(imageList.size() - 1);if (path.contains("jpg")) {Bitmap bitmap = BitmapFactory.decodeFile(path);mThumbnail.setImageBitmap(bitmap);int rotation = getWindowManager().getDefaultDisplay().getRotation();mThumbnail.setRotation(ORIENTATION.get(rotation));} else if (path.contains("mp4")) {MediaMetadataRetriever retriever = new MediaMetadataRetriever();retriever.setDataSource(path);//获取第1帧Bitmap bitmap = retriever.getFrameAtTime(1);mThumbnail.setImageBitmap(bitmap);}}//遍历系统相册protected ArrayList<String> getImageFilePath() {ArrayList<String> imageList = new ArrayList<>();File file = new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");File[] dirEpub = file.listFiles();if (dirEpub.length != 0) {for (int i = 0; i < dirEpub.length; i++) {String fileName = dirEpub[i].toString();imageList.add(fileName);Log.i("File", "File name = " + fileName);}}return imageList;}//跳转相册 (画廊)protected void gotoGallery() {ArrayList<String> temp = getImageFilePath();String lastPath = temp.get(temp.size() - 1);Uri uri = getMediaUriFromPath(this, lastPath);Intent intent = new Intent("com.android.camera.action.REVIEW", uri);intent.setData(uri);startActivity(intent);}@SuppressLint("Range")public Uri getMediaUriFromPath(Context context, String path) {Uri uri = null;if (path.contains("jpg")) {Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;Cursor cursor = context.getContentResolver().query(picUri,null,MediaStore.Images.Media.DISPLAY_NAME + "= ?",new String[]{path.substring(path.lastIndexOf("/") + 1)},null);if (cursor.moveToFirst()) {uri = ContentUris.withAppendedId(picUri,cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));}cursor.close();} else if (path.contains("mp4")) {Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;Cursor cursor = context.getContentResolver().query(mediaUri,null,MediaStore.Video.Media.DISPLAY_NAME + "= ?",new String[]{path.substring(path.lastIndexOf("/") + 1)},null);if (cursor.moveToFirst()) {uri = ContentUris.withAppendedId(mediaUri,cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));}cursor.close();}return uri;}/*** 配置录制视频相关数据*/private void configMediaRecorder() {File file = new File(Environment.getExternalStorageDirectory() +"/DCIM/camera/myMp4" + System.currentTimeMillis() + ".mp4");if (file.exists()) {file.delete();}if (mMediaRecorder == null) {mMediaRecorder = new MediaRecorder();//如果是前置if (mCameraId.equals("1")) {mMediaRecorder.setOrientationHint(270);} else {mMediaRecorder.setOrientationHint(90);}//设置音频来源mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置视频来源mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);//设置输出格式mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AACmMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);//设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。mMediaRecorder.setVideoEncodingBitRate(8 * 1080 * 1920);//设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。mMediaRecorder.setVideoFrameRate(30);//Size size = getMatchingSize();mMediaRecorder.setVideoSize(1920, 1080);Surface surface = new Surface(mTextureView.getSurfaceTexture());mMediaRecorder.setPreviewDisplay(surface);mMediaRecorder.setOutputFile(file.getAbsolutePath());try {mMediaRecorder.prepare();} catch (IOException e) {e.printStackTrace();}}}private void captureVideo() {Log.d(TAG, "--------- captureVideo");mMediaRecorder.start();}private void stopVideo() {Log.d(TAG, "--------- stopVideo");mMediaRecorder.stop();broadcast();createPreView();}private void changeCameraId() {Log.d(TAG, "changeCamera: success");if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {Toast.makeText(this, "前置转后置", Toast.LENGTH_SHORT).show();mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);} else {Toast.makeText(this, "后置转前置", Toast.LENGTH_SHORT).show();mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);}mCameraDevice.close();openCamera();}}
xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"><TextureViewandroid:id="@+id/previewSurfaceView"android:layout_width="match_parent"android:layout_height="match_parent" /><ImageButtonandroid:id="@+id/takePictureButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="70dp"android:visibility="visible"android:background="@drawable/shape_white_ring"android:src="@drawable/shape_take_photo" /><ImageButtonandroid:id="@+id/captureVideoButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alagnParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="70dp"android:visibility="gone"android:background="@drawable/shape_white_ring"android:src="@drawable/shape_take_video" /><ImageViewandroid:id="@+id/thumbnail"android:layout_width="45dp"android:layout_height="55dp"android:layout_alignParentBottom="true"android:layout_marginLeft="50dp"android:layout_marginBottom="70dp" /><ImageButtonandroid:id="@+id/changeCamera"android:layout_width="45dp"android:layout_height="45dp"android:layout_alignParentRight="true"android:layout_alignParentBottom="true"android:layout_marginRight="50dp"android:layout_marginBottom="77dp"android:background="@drawable/camera_quick_switch" /><Buttonandroid:id="@+id/photoMode"android:layout_width="85dp"android:layout_height="40dp"android:text="Photo"android:layout_centerHorizontal="true"android:layout_alignParentBottom="true"android:visibility="visible"android:layout_marginBottom="160dp"/><Buttonandroid:id="@+id/videoMode"android:layout_width="85dp"android:text="Video"android:layout_height="40dp"android:visibility="gone"android:layout_centerHorizontal="true"android:layout_alignParentBottom="true"android:layout_marginBottom="160dp"/></RelativeLayout>
CameraX
用CameraX 时需要注意的是 build.gradle里面需要添加
//CameraXdef camerax_version = "1.0.0-alpha02"implementation "androidx.camera:camera-core:${camerax_version}"implementation "androidx.camera:camera-camera2:${camerax_version}"
CameraX api
CameraX 可以理解成将Camera2的API 进行了封装 使用变得更加简洁
实现预览 获取Camera权限之后 通过PreviewConfig去设置分辨率,画面比例,CameraId等等
再 new 一个 Preview 对象时将PreviewConfig对象传递下去,再利用bindTolifecycle 把Preview 对象 传递下去就完成预览了,相比Camera2是不是要简单很多呢
拍照也很简单 只需要用ImageCaptureConfig 配置拍照信息 比例 CameraId 拍照方式等等
再去new一个ImageCapture对象 将ImageCaptureConfig传递下去 再给 bindTolifecycle 多一个参数 这个参数传递ImageCapture对象
录像 VideoCaptureConfig
Quality.UHD
,适用于 4K 超高清视频大小 (2160p)Quality.FHD
,适用于全高清视频大小 (1080p)Quality.HD
,适用于高清视频大小 (720p)Quality.SD
,适用于标清视频大小 (480p)
CameraX demo 改天补上临时有事
Camera2 和CameraX 从入门到精通 java实现相关推荐
- 视频教程-Spring Cloud微服务--入门到精通-Java
Spring Cloud微服务--入门到精通 本系列课程由多位老师共同录制而成,旨在为想要学习Java的用户提供一套系统的成长方案. Java从入门到进阶 ¥59.00 立即订阅 扫码下载「CSDN程 ...
- 视频教程-springboot从入门到精通-Java
springboot从入门到精通 本人具有7年java开发经验,两年java教学经验,擅长java开发相关技术,能够熟掌握并应用目前主流web开发技术,如SSH,SSM等,数据库开发技术oracle, ...
- 视频教程-Spring框架快速入门到精通-Java
Spring框架快速入门到精通 十年项目开发经验,主要从事java相关的开发,熟悉各种mvc开发框架. 王振伟 ¥18.00 立即订阅 扫码下载「CSDN程序员学院APP」,1000+技术好课免费看 ...
- 视频教程-mybatis快速入门到精通-Java
mybatis快速入门到精通 十年项目开发经验,主要从事java相关的开发,熟悉各种mvc开发框架. 王振伟 ¥18.00 立即订阅 扫码下载「CSDN程序员学院APP」,1000+技术好课免费看 A ...
- 火爆B站的阿玮老师,他带着课程走来了(含4天入门到精通Java直播课)
他来啦.他来啦 终于把百万B站用户都爱的阿玮老师盼来啦! 此处应该有掌声 如果你是B站资深用户 相信早就耳闻阿玮老师的大名啦 阿玮老师在B站有多受欢迎,你往下康康~~ 这究竟是什么神仙老师呀 来啦就出 ...
- 最新《JPA入门到精通JAVA进阶项目实战》
『课程介绍』: 通过对本课程的学习,能够对JPA能够有全面的认识,简化现有Java EE和Java SE应用开发工作.用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从繁琐 ...
- Java快速入门到精通— Java break语句详解
所有流行的编程语言中都有循环语句.JAVA 中采用的循环语句与C语言中的循环语句相似,主要有 while.do-while 和 for! 那么在某些时候需要在某种条件出现时强行终止循环,而不是等到循环 ...
- java从入门到精通_想要开始学java?你要的java从入门到精通布列如下!
java从入门到精通,让我来告诉你! 毫无疑问,java是当下最火的编程语言之一.对于许多未曾涉足计算机编程的领域「小白」来说,深入地掌握java看似是一件十分困难的事.其实,只要掌握了科学的学习方法 ...
- 《Java 开发从入门到精通》—— 2.2 编写第一段Java程序
本节书摘来异步社区<Java 开发从入门到精通>一书中的第2章,第2.2节,作者: 扶松柏 , 陈小玉,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.2 编 ...
最新文章
- Zookeeper 的典型应用场景场景
- 百度智能云发布时空数据管理平台,打造一体化数据中台
- AQS理解之五—并发编程中AQS的理解
- SAP UI5 router的初始化逻辑
- JAVA面试题------------final 关键字是干什么用的?谈谈你的理解。
- 在Ubuntu Linux中获取上次访问的文件时间
- 将状态机模式实现为流处理器
- iphone viewdidLoad运行以及参数的传递。
- es6添加删除class_es6-class的基本用法
- h5 input 阴影_html5中input表单加边框,阴影效果
- linux 进程 D 状态,Linux 进程的 Uninterruptible sleep(D) 状态
- 计算机毕业设计Java宠物医院后台管理系统设计与实现(源码+系统+mysql数据库+lw文档)
- Devexpress 各版本中文语言包
- java实现冒泡算法
- ps-通道+高低频磨皮去斑
- android 出错信息为:Class 'Anonymous class derived from Handler' must either be declared abstract or imple
- 求x的n次方编程_C语言 用递归方法求X的n次方
- 名字解析/DNS服务
- cae计算机仿真分析技术,cae分析.doc
- 南宁市第二十六中学:教研路漫漫,花香伴我行