当前位置:   article > 正文

Android多媒体功能开发(14)——Camera2框架_android camera2

android camera2

从Android5.0开始,引入了一套Camera2框架控制相机实现拍照和录像等功能,相关的类定义在android.hardware.camera2包中。原有的android.hardware包中的Camera类降级使用,因为其功能少,灵活性差,满足不了日益复杂的相机功能需求。

Camera2框架的相机模型被设计成一个管道,使用相机时需要先和相机设备建立一个会话,通过该会话向相机发送请求,相机将图像数据保存到配置好的Surface,Surface就是存放图像数据的缓冲区。请求分为单次请求、重复请求和多次请求三种。例如,实现预览功能需要发送一个重复请求,相当于不断向相机发送预览请求,相机就会不断把预览图像数据存入预览组件的图像缓冲区。而实现拍照功能可以发送一个单次请求,相机会将图像数据存入一个缓冲区,应用再将图像数据保存到图片文件即可。这些请求是被放在一个队列中顺序执行的,因此不会发生两个请求同时执行的冲突。

这种管道模型的请求和响应都是异步的,所以Camera2框架大量采用回调方法。这样使得代码的执行不够线性,刚开始学习时不容易理解掌握。下面我们就具体介绍Camera2框架的使用方法,以及用到的概念和类。

一、相机权限和特性
为了在安装应用前就确认设备上有相机,需要在配置文件Manifest.xml中配置相机特性。这样,如果设备上没有相机就无法安装应用。使用相机必须在配置文件Manifest.xml中注册相机权限android.permission.CAMERA,声明应用需要相机权限。代码是:

  1. <uses-feature android:name="android.hardware.camera" android:required="true"/>
  2. <uses-permission android:name="android.permission.CAMERA" />

Android6.0以上系统需要应用运行时进行动态权限申请,所以在使用相机前需要检查权限,如果没有许可就进行询问。主要代码是:

  1. if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  2. ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.CAMERA}, 101);
  3. return;
  4. }

还可以配置更多特性要求,例如必须有支持自动对焦的相机等。

二、开关相机
Camera2框架使用流程的起点是CameraManager,这是一个负责管理相机的系统服务,通过它来获取相机设备和信息。获取CameraManager的代码如下:

CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

接着需要获取相机列表,每个相机用一个String类型的id表示,后面通过id可以获取对应的相机设备。所有相机id的列表保存在一个String数组中,设备上有几个相机就在数组中几个元素。代码如下:

String[] cameraIdList = cameraManager.getCameraIdList();

接下来可以根据相机id获取相机特性,相机特性保存在一个CameraCharacteristics对象中。一项属性表示为一个键值对,所以该对象是一个键值对的集合。下面的代码通过遍历所有相机的特性找到后置相机的id,再通过CameraCharacteristics对象获取相机支持的拍照分辨率大小:

  1. String backCameraId = null;
  2. for(String cameraId:cameraIdList){
  3. CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
  4. if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)==CameraCharacteristics.LENS_FACING_BACK) {
  5. mCameraId = cameraId;
  6. supportedSizes = cameraCharacteristics .get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(ImageReader.class);
  7. break;
  8. }
  9. }

接下来就可以调用 CameraManager.openCamera() 方法开启相机,代码如下: 

cameraManager.openCamera(mCameraId, stateCallback, mBackgroundHandler);

openCamera()方法有三个参数,第一个参数是cameraId,这里使用上一步获取到的后置相机的id。第二个参数是处理结果的回调对象,和事件处理机制中的监听器一样。openCamera()方法是异步执行的,调用以后方法马上返回,继续执行后续代码,打开相机是否成功的结果要过一段时间才返回,返回后调用回调对象的对应方法。第三个参数是执行回调方法的线程,可以是主线程,也可以是后台线程。回调对象需要在调用openCamera()方法之前创建,示例代码如下:

  1. CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
  2. @Override
  3. public void onOpened(@NonNull CameraDevice cameraDevice) {
  4. mCameraDevice = cameraDevice;
  5. // 回调函数的代码在子线程中执行,所以不能直接发出Toast消息,只能通过主线程发出
  6. runOnUiThread(new Runnable() {
  7. @Override
  8. public void run() {
  9. Toast.makeText(getBaseContext(), "Camera opened", Toast.LENGTH_SHORT).show();
  10. }
  11. });
  12. }
  13. @Override
  14. public void onDisconnected(@NonNull CameraDevice cameraDevice) {
  15. mCameraDevice = null;
  16. }
  17. @Override
  18. public void onError(@NonNull CameraDevice cameraDevice, int i) {
  19. cameraDevice.close();
  20. mCameraDevice = null;
  21. }
  22. };

后台线程也需要在调用openCamera()方法之前调用,启动和停止后台线程的代码如下:

  1. private void startBackgroundThread() {
  2. mBackgroundThread = new HandlerThread("CameraBackground");
  3. mBackgroundThread.start();
  4. mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
  5. }
  6. private void stopBackgroundThread() {
  7. mBackgroundThread.quitSafely();
  8. try {
  9. mBackgroundThread.join();
  10. mBackgroundThread = null;
  11. mBackgroundHandler = null;
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }

相机不再使用以后需要关闭,否则会一直占用相机资源,使得其他使用相机的应用无法使用。关闭相机比较合适的时机是在Activity的onPause()方法中。关闭相机的代码如下:

mCameraDevice.close();


三、创建CaptureSession会话
打开相机设备以后,要控制相机还要建立CaptureSession会话。代码如下:

mCameraDevice.createCaptureSession(surfaceList, sessionStateCallback, mBackgroundHandler);

mCameraDevice是上一步打开相机的结果回调中传回的相机对象,创建CameraSession会话需要调用它的createCaptureSession()方法,方法有三个参数。第一个参数是Surface列表,即用于接收图像数据的内存缓冲区,比如用于预览的Surface,用于拍照的Surface,这些Surface必须在这里准备好,否则后面预览或拍照时无法使用。创建预览和拍照两个Surface的列表的代码如下:

  1. // 用Texture组件预览,其Surface在监听器中获得
  2. TextureView previewTexture = new TextureView(this);
  3. previewTexture.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
  4. @Override
  5. public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
  6. mPreviewSurface = new Surface(surfaceTexture); // 得到用于预览的Surface
  7. }
  8. });
  9. // 用ImageReader保存拍照数据,其Surface通过getSurface()方法获得
  10. int width = supportedSizes[0].getWidth();
  11. int height = supportedSizes[0].getHeight();
  12. ImageReader mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 5);
  13. // 创建Surface列表
  14. List<Surface> surfaceList = new ArrayList<Surface>();
  15. surfaceList.add(mPreviewSurface);
  16. surfaceList.add(mImageReader.getSurface());

createCaptureSession()方法的第二个参数是结果回调对象,是否创建成功的结果要过一段时间才返回,返回后调用回调对象的对应方法。创建成功后,返回CameraCaptureSession对象,通过这个对象向相机发送各种操作请求。回调对象需要在调用createCaptureSession()方法之前创建,代码如下:

  1. CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() {
  2. @Override
  3. public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
  4. mCameraCaptureSession = cameraCaptureSession;
  5. }
  6. };

createCaptureSession()方法的第三个参数是执行回调方法的线程,可以是主线程,也可以是后台线程,和前面openCamera()方法相同。

相机用完以后需要关闭CaptureSession会话,代码如下:

mCameraCaptureSession.close();

四、开启和停止预览
通过CaptureSession会话,可以向相机发送请求,例如开启和停止预览。预览本质上是不断重复执行的Capture操作,每一次Capture都会把相机数据输出到对应的Surface上,显示出预览画面。使用的是CameraCaptureSession对象的setRepeatingRequest方法,具体代码如下:

  1. CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  2. builder.addTarget(mPreviewSurface);
  3. CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback(){};
  4. mCameraCaptureSession.setRepeatingRequest(builder.build(), captureCallback, mBackgroundHandler);

方法的第一个参数是CaptureRequest请求对象,保存预览的各种设置参数,通过Builder模式创建并使用预览模板。因为相机的参数非常多,所以提供了模板以简化代码,预览使用预览模板生成请求,拍照使用拍照模板生成请求。然后把预览Surface加入,这样相机就会把图像数据保存到Surface中。

方法的第二个参数是结果回调对象,请求的结果要过一段时间才返回,返回后调用回调对象的对应方法。这个回调对象没有做任何处理。

方法的第三个参数是执行回调方法的线程,可以是主线程,也可以是后台线程,和前面openCamera()、createCaptureSession()方法相同。

关闭预览只要停止该重复请求就行,代码如下:

mCameraCaptureSession.stopRepeating();

五、拍照
拍照通过单次Capture实现,可以用ImageReader对象作为接收照片的Surface。使用CameraCaptureSession对象的capture()方法,代码如下:

  1. CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
  2. builder.addTarget(mPreviewSurface);
  3. builder.addTarget(mImageReader.getSurface());
  4. CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {};
  5. mCameraCaptureSession.capture(builder.build(), captureCallback, mBackgroundHandler);

方法的第一个参数是CaptureRequest请求对象,保存拍照的各种设置参数,通过Builder模式创建并使用拍照模板。然后把预览Surface和ImageReader的Surface都加入,这样相机会同时把图像数据保存到两个Surface,拍照的同时不中断预览。

方法的第二个参数是结果回调对象,请求的结果要过一段时间才返回,返回后调用回调对象的对应方法。这个回调对象没有做任何处理。

方法的第三个参数是执行回调方法的线程,可以是主线程,也可以是后台线程,和前面openCamera()、createCaptureSession()、setRepeatingRequest()方法相同。

ImageReader接收到图像数据后,其OnImageAvailableListener监听器的onImageAvailable方法被调用,在此方法中可以将图像数据保存到文件,代码如下:

  1. ImageReader.OnImageAvailableListener listener = new ImageReader.OnImageAvailableListener() {
  2. @Override
  3. public void onImageAvailable(ImageReader imageReader) {
  4. Image image = imageReader.acquireNextImage(); // 取出一个图像
  5. saveImage(image, picFile); // 将图像保存到文件
  6. }
  7. };
  8. mImageReader.setOnImageAvailableListener(listener, mBackgroundHandler); // 设置监听器,拍照完成后会执行上面的方法

将图像保存到文件的代码如下:

  1. void saveImage(Image mImage, File mFile){
  2. ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
  3. byte[] bytes = new byte[buffer.remaining()];
  4. buffer.get(bytes);
  5. FileOutputStream output = null;
  6. try {
  7. output = new FileOutputStream(mFile);
  8. output.write(bytes);
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. } finally {
  12. mImage.close();
  13. if (null != output) {
  14. try {
  15. output.close();
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. }


六、示例
根据Camera2框架的使用方法,我们编写了一个例子,将每一步操作用一个按钮来控制,以便演示Camera2框架的使用步骤。

运行时,首先点击"OPEN CAMERA"按钮,执行打开相机代码,并在回调方法中保存打开的CameraDevice对象。

然后点击"CREATE SESSION"按钮,执行创建CaptureSession会话的代码,并在回调方法中保存创建的CaptureSession。

接着点击"START PREVIEW"按钮,执行发送重复Capture请求开启预览的代码。

这三步须依次完成,每一步都需要等待上一步的结果在回调方法中成功返回。这个例子中是分在三个按钮中手动完成,可以等待上一步结果返回后再点击按钮,代码看起来分步骤执行,比较线性,容易理解。如果想让这三步自动依次执行,可以将每一步的调用放到上一步的回调方法中执行,但是这样代码是多层回调嵌套,不容易理解。

预览画面出现后可以点击"CAPTURE"按钮,执行发送单次Capture请求拍照的代码。完成后可以再依次点击"STOP PREVIEW"、"CLOSE SESSION"、"CLOSE CAMERA"按钮释放资源。

"VIEW"按钮调用可以查看图片的界面查看拍摄的照片,以验证拍摄是否正确完成。"CAMERAVIEW"按钮启动另一个Activity,该Activity中使用了一个封装好的控件,可以实现Camera2框架预览和拍照功能。使用封装好的控件代码非常简单,而且该控件的代码相对比较完善,解决了类似于照片方向、自动闪光灯等设置问题。我们的示例代码中为了突出主要代码,便于理解,将这些细节问题都忽略了。

例子的完整代码如下:

  1. import androidx.annotation.NonNull;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.core.app.ActivityCompat;
  4. import androidx.core.content.FileProvider;
  5. import android.Manifest;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.content.pm.PackageManager;
  9. import android.graphics.ImageFormat;
  10. import android.graphics.SurfaceTexture;
  11. import android.hardware.camera2.CameraAccessException;
  12. import android.hardware.camera2.CameraCaptureSession;
  13. import android.hardware.camera2.CameraCharacteristics;
  14. import android.hardware.camera2.CameraDevice;
  15. import android.hardware.camera2.CameraManager;
  16. import android.hardware.camera2.CaptureRequest;
  17. import android.media.Image;
  18. import android.media.ImageReader;
  19. import android.net.Uri;
  20. import android.os.Build;
  21. import android.os.Bundle;
  22. import android.os.Handler;
  23. import android.os.HandlerThread;
  24. import android.util.Log;
  25. import android.util.Size;
  26. import android.view.Surface;
  27. import android.view.TextureView;
  28. import android.view.View;
  29. import android.widget.Button;
  30. import android.widget.LinearLayout;
  31. import android.widget.Toast;
  32. import java.io.File;
  33. import java.util.ArrayList;
  34. import java.util.List;
  35. public class MainActivity extends AppCompatActivity {
  36. File picFile; // 保存照片的文件
  37. Uri picUri; // 照片文件的uri,查看图片时使用
  38. TextureView previewTexture; // 预览组件
  39. Surface mPreviewSurface; // 预览Surface
  40. private String mCameraId; // 后置相机id
  41. private HandlerThread mBackgroundThread; // 处理回调结果的后台线程
  42. private Handler mBackgroundHandler; // 处理回调结果的handler
  43. private CameraDevice mCameraDevice; // 相机设备对象
  44. private Size[] supportedSizes; // 相机支持的拍照分辨率大小
  45. private CameraCaptureSession mCameraCaptureSession; // 相机会话
  46. private ImageReader mImageReader; // 拍照时接收图像数据并保存到文件
  47. @Override
  48. protected void onCreate(Bundle savedInstanceState) {
  49. super.onCreate(savedInstanceState);
  50. LinearLayout ll = new LinearLayout(this);
  51. ll.setOrientation(LinearLayout.VERTICAL);
  52. setContentView(ll);
  53. LinearLayout line1 = new LinearLayout(this);
  54. line1.setOrientation(LinearLayout.HORIZONTAL);
  55. ll.addView(line1);
  56. LinearLayout line2 = new LinearLayout(this);
  57. line2.setOrientation(LinearLayout.HORIZONTAL);
  58. ll.addView(line2);
  59. LinearLayout line3 = new LinearLayout(this);
  60. line3.setOrientation(LinearLayout.HORIZONTAL);
  61. ll.addView(line3);
  62. LinearLayout line4 = new LinearLayout(this);
  63. line4.setOrientation(LinearLayout.HORIZONTAL);
  64. ll.addView(line4);
  65. previewTexture = new TextureView(this);
  66. ll.addView(previewTexture);
  67. previewTexture.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
  68. @Override
  69. public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
  70. mPreviewSurface = new Surface(surfaceTexture); // 得到用于预览的Surface
  71. }
  72. @Override
  73. public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
  74. }
  75. @Override
  76. public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
  77. return false;
  78. }
  79. @Override
  80. public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
  81. }
  82. });
  83. picFile = new File(getExternalFilesDir(null), "picture.jpg");
  84. // 定义多媒体文件的uri,在应用之间传递文件时需要用uri
  85. if (Build.VERSION.SDK_INT >= 24) { // Android 7 以后不能直接用file uri分享文件,要使用FileProvider
  86. String fileProviderAuthority = getPackageName() + ".fileProvider"; // FileProvider的名字,FileProvider在应用配置文件AndroidManifest中定义
  87. // 格式为:content://com.zzk.a1510camera2.fileProvider/testdir/picture.jpg, testdir是res/file_paths/file_paths.xml中定义的目录别名
  88. picUri = FileProvider.getUriForFile(MainActivity.this, fileProviderAuthority, picFile);
  89. } else { // Android 7 以前可以直接用file uri分享文件
  90. // 格式为:file:///storage/emulated/0/Android/data/com.zzk.a1510camera2/files/picture.jpg
  91. picUri = Uri.fromFile(picFile);
  92. }
  93. Button btnOpenCamera = new Button(this);
  94. btnOpenCamera.setText("Open Camera");
  95. line1.addView(btnOpenCamera);
  96. btnOpenCamera.setOnClickListener(new View.OnClickListener() {
  97. @Override
  98. public void onClick(View view) {
  99. try {
  100. openCamera();
  101. } catch (CameraAccessException e) {
  102. e.printStackTrace();
  103. }
  104. }
  105. });
  106. Button btnCloseCamera = new Button(this);
  107. btnCloseCamera.setText("Close Camera");
  108. line1.addView(btnCloseCamera);
  109. btnCloseCamera.setOnClickListener(new View.OnClickListener() {
  110. @Override
  111. public void onClick(View view) {
  112. closeCamera();
  113. }
  114. });
  115. Button btnCreateSession = new Button(this);
  116. btnCreateSession.setText("Create Session");
  117. line2.addView(btnCreateSession);
  118. btnCreateSession.setOnClickListener(new View.OnClickListener() {
  119. @Override
  120. public void onClick(View view) {
  121. try {
  122. createCaptureSession();
  123. } catch (CameraAccessException e) {
  124. e.printStackTrace();
  125. }
  126. }
  127. });
  128. Button btnCloseSession = new Button(this);
  129. btnCloseSession.setText("Close Session");
  130. line2.addView(btnCloseSession);
  131. btnCloseSession.setOnClickListener(new View.OnClickListener() {
  132. @Override
  133. public void onClick(View view) {
  134. closeCaptureSession();
  135. }
  136. });
  137. Button btnStartPreview = new Button(this);
  138. btnStartPreview.setText("Start Preview");
  139. line3.addView(btnStartPreview);
  140. btnStartPreview.setOnClickListener(new View.OnClickListener() {
  141. @Override
  142. public void onClick(View view) {
  143. try {
  144. startPreview();
  145. } catch (CameraAccessException e) {
  146. e.printStackTrace();
  147. }
  148. }
  149. });
  150. Button btnStopPreview = new Button(this);
  151. btnStopPreview.setText("Stop Preview");
  152. line3.addView(btnStopPreview);
  153. btnStopPreview.setOnClickListener(new View.OnClickListener() {
  154. @Override
  155. public void onClick(View view) {
  156. try {
  157. stopPreview();
  158. } catch (CameraAccessException e) {
  159. e.printStackTrace();
  160. }
  161. }
  162. });
  163. Button btnCapture = new Button(this);
  164. btnCapture.setText("Capture");
  165. line4.addView(btnCapture);
  166. btnCapture.setOnClickListener(new View.OnClickListener() {
  167. @Override
  168. public void onClick(View view) {
  169. try {
  170. capture();
  171. } catch (CameraAccessException e) {
  172. e.printStackTrace();
  173. }
  174. }
  175. });
  176. Button btnView = new Button(this);
  177. btnView.setText("View");
  178. line4.addView(btnView);
  179. btnView.setOnClickListener(new View.OnClickListener() {
  180. @Override
  181. public void onClick(View arg0) {
  182. if (picFile.exists()) {
  183. Intent intent = new Intent(Intent.ACTION_VIEW);
  184. intent.setDataAndType(picUri, "image/*");
  185. intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予对方读取该文件的权限
  186. startActivity(intent);
  187. }
  188. }
  189. });
  190. Button btnCameraView = new Button(this);
  191. btnCameraView.setText("CameraView");
  192. line4.addView(btnCameraView);
  193. btnCameraView.setOnClickListener(new View.OnClickListener() {
  194. @Override
  195. public void onClick(View arg0) {
  196. Intent intent = new Intent(MainActivity.this, CameraActivity.class);
  197. startActivity(intent);
  198. }
  199. });
  200. }
  201. /**
  202. * 打开后置相机,将设备保存到mCameraDevice中
  203. * @throws CameraAccessException
  204. */
  205. private void openCamera() throws CameraAccessException {
  206. // 获取CameraManager
  207. CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  208. // 获取相机列表
  209. String[] cameraIdList = cameraManager.getCameraIdList();
  210. // 获取后置相机id
  211. mCameraId = null;
  212. for(String cameraId:cameraIdList){
  213. CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId); // 获取相机特性
  214. if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)==CameraCharacteristics.LENS_FACING_BACK) { // 后置相机
  215. mCameraId = cameraId;
  216. // 拍照支持的分辨率
  217. supportedSizes = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageReader.class);
  218. break;
  219. }
  220. }
  221. // 打开相机结果的回调函数(监听器)
  222. CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
  223. @Override
  224. public void onOpened(@NonNull CameraDevice cameraDevice) {
  225. mCameraDevice = cameraDevice;
  226. // 回调函数的代码在子线程中执行,所以不能直接发出Toast消息,只能通过主线程发出
  227. runOnUiThread(new Runnable() {
  228. @Override
  229. public void run() {
  230. Toast.makeText(getBaseContext(), "Camera opened", Toast.LENGTH_SHORT).show();
  231. }
  232. });
  233. }
  234. @Override
  235. public void onDisconnected(@NonNull CameraDevice cameraDevice) {
  236. mCameraDevice = null;
  237. }
  238. @Override
  239. public void onError(@NonNull CameraDevice cameraDevice, int i) {
  240. cameraDevice.close();
  241. mCameraDevice = null;
  242. }
  243. };
  244. // 启动处理返回结果的后台线程
  245. if(mBackgroundHandler==null) startBackgroundThread();
  246. // 打开相机需要的权限检查
  247. if (ActivityCompat.checkSelfPermission(getBaseContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  248. ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, 101);
  249. return;
  250. }
  251. // 打开相机
  252. cameraManager.openCamera(mCameraId, stateCallback, mBackgroundHandler);
  253. }
  254. /**
  255. * 关闭相机
  256. */
  257. private void closeCamera(){
  258. if(mCameraDevice!=null) {
  259. mCameraDevice.close();
  260. mCameraDevice = null;
  261. Toast.makeText(getBaseContext(), "Camera closed", Toast.LENGTH_SHORT).show();
  262. }
  263. }
  264. private void startBackgroundThread() {
  265. mBackgroundThread = new HandlerThread("CameraBackground");
  266. mBackgroundThread.start();
  267. mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
  268. }
  269. private void stopBackgroundThread() {
  270. mBackgroundThread.quitSafely();
  271. try {
  272. mBackgroundThread.join();
  273. mBackgroundThread = null;
  274. mBackgroundHandler = null;
  275. } catch (InterruptedException e) {
  276. e.printStackTrace();
  277. }
  278. }
  279. /**
  280. * 创建CaptureSession
  281. */
  282. private void createCaptureSession() throws CameraAccessException {
  283. // 创建ImageReader
  284. createImageReader();
  285. // 接收图像数据的Surface
  286. List<Surface> surfaceList = new ArrayList<Surface>();
  287. surfaceList.add(mPreviewSurface);
  288. surfaceList.add(mImageReader.getSurface());
  289. // 处理创建结果的回调函数(监听器)
  290. CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() {
  291. @Override
  292. public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
  293. mCameraCaptureSession = cameraCaptureSession;
  294. // 回调函数的代码在子线程中执行,所以不能直接发出Toast消息,只能通过主线程发出
  295. runOnUiThread(new Runnable() {
  296. @Override
  297. public void run() {
  298. Toast.makeText(getBaseContext(), "CaptureSession created", Toast.LENGTH_SHORT).show();
  299. }
  300. });
  301. }
  302. @Override
  303. public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
  304. }
  305. };
  306. // 创建CameSession
  307. mCameraDevice.createCaptureSession(surfaceList, sessionStateCallback, mBackgroundHandler);
  308. }
  309. /**
  310. * 创建ImageReader
  311. */
  312. private void createImageReader(){
  313. if(mImageReader==null) {
  314. // 创建ImageReader,最多保存5张图片
  315. int width = supportedSizes[0].getWidth();
  316. int height = supportedSizes[0].getHeight();
  317. Log.i("zzk", "capture: size=" + width + ", " + height);
  318. mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 5);
  319. ImageReader.OnImageAvailableListener listener = new ImageReader.OnImageAvailableListener() {
  320. @Override
  321. public void onImageAvailable(ImageReader imageReader) {
  322. // 回调函数的代码在子线程中执行,所以不能直接发出Toast消息,只能通过主线程发出
  323. runOnUiThread(new Runnable() {
  324. @Override
  325. public void run() {
  326. Toast.makeText(getBaseContext(), "Image available", Toast.LENGTH_SHORT).show();
  327. }
  328. });
  329. Image image = imageReader.acquireNextImage(); // 取出一个图像
  330. mBackgroundHandler.post(new ImageSaver(image, picFile)); // 在后台线程中将图像保存到文件
  331. }
  332. };
  333. mImageReader.setOnImageAvailableListener(listener, mBackgroundHandler); // 设置监听器,拍照完成后会执行上面的方法
  334. }
  335. }
  336. /**
  337. * 关闭CaptureSession
  338. */
  339. private void closeCaptureSession(){
  340. if(mCameraCaptureSession!=null){
  341. mCameraCaptureSession.close();
  342. mCameraCaptureSession = null;
  343. Toast.makeText(getBaseContext(), "CaptureSession closed", Toast.LENGTH_SHORT).show();
  344. }
  345. }
  346. /**
  347. * 开始预览
  348. * @throws CameraAccessException
  349. */
  350. private void startPreview() throws CameraAccessException {
  351. // 通过Builder模式创建CaptureRequest,创建时使用预览模板
  352. CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  353. // 加入接收图像数据的Surface
  354. builder.addTarget(mPreviewSurface);
  355. // 创建处理结果的回调函数(监听器)
  356. CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback(){};
  357. // 发送重复请求,让相机不断向预览组件发送图像数据
  358. mCameraCaptureSession.setRepeatingRequest(builder.build(), captureCallback, mBackgroundHandler);
  359. }
  360. /**
  361. * 停止预览
  362. * @throws CameraAccessException
  363. */
  364. private void stopPreview() throws CameraAccessException {
  365. mCameraCaptureSession.stopRepeating(); // 取消重复请求
  366. }
  367. /**
  368. * 拍照
  369. * @throws CameraAccessException
  370. */
  371. private void capture() throws CameraAccessException {
  372. // 通过Builder模式创建CaptureRequest,创建时使用拍照模板
  373. CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
  374. // 加入接收图像数据的Surface,包括预览用的Surface和保存图片文件用的ImageReader,拍照的同时预览也不停顿
  375. builder.addTarget(mPreviewSurface);
  376. builder.addTarget(mImageReader.getSurface());
  377. // 处理结果的回调函数(监听器)
  378. CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {};
  379. // 发送单次请求,让相机向ImageReader发送一次图像数据,拍照完成后调用ImageReader的OnImageAvailableListener监听器的onImageAvailable方法
  380. mCameraCaptureSession.capture(builder.build(), captureCallback, mBackgroundHandler);
  381. }
  382. }

完整例子下载:

Android多媒体功能开发-使用Camera2框架拍照的例子 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/article/detail/46133?site
推荐阅读
相关标签
  

闽ICP备14008679号