相机
Android框架包含了各种相机和相机功能的支持,使你可以在你的应用中捕获图像和视频。本文讨论一个简单快速的获取图像和视频的方法,并概述一个创建自定义用户相机体验的方法。
录制视频有两种方法:
系统相机的录制视频功能
通过安卓自带的MediaRecorder来录制视频
基础知识
Android框架支持通过CameraAPI或Cemera intent来抓取图像和视频。下面就是相关的类:
Camera:此类是控制设备相机的主要API.此类用于在创建相机应用时获取图片和视频.
SurfaceView:此类为用户提供camera的实时图像预览.
MediaRecorder:此类用于从camera录制视频.
Intent:一个MediaStore.ACTION_IMAGE_CAPTURE或MediaStore.ACTION_VIDEO_CAPTURE型的intent,可以使用它来抓取图像或视频,而不用操作Camera对象。
Manifest中的声明
1 2 3 4 5 6
| <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.CAMERA" />
|
Camera(Android 5.0 API 21已弃用)
拍摄图片
利用Camera想要拍照,需要如下使用步骤:
利用open(int)获取Camera实例。
利用getParameters()获取默认设置,如果需要利用setParameters(Camera.Parameters)进行参数设置。
利用setDisplayOrientation(int)函数设置正确的预览方向。
想要预览,需要配合SurfaceView,利用setPreviewDisplay(SurfaceHolder)设置SurfaceView的SurfaceHolder用于预览。
调用startPreview()开始预览,拍照之前必须已经开始预览。
takePicture 拍摄照片
调用takePicture后预览会停止,需用重新调用startPreview才能再次开始预览。预览开始后,就可以通过Camera.takePicture()方法拍摄一张照片,返回的照片数据通过Callback接口获取。
takePicture()接口可以获取三个类型的照片:
第一个,ShutterCallback接口,在拍摄瞬间瞬间被回调,通常用于播放“咔嚓”这样的音效
第二个,PictureCallback接口,返回未经压缩的RAW类型照片
第三个,PictureCallback接口,返回经过压缩的JPEG类型照片
调用takePickture后预览会停止,想要继续预览需要调用startPreview()函数
调用stopPreview()停止预览
调用release()释放资源,为了节省资源在Activity.onPause是调用停止预览,在onResume是开始预览。
录制视频
跳转使用Intent
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra (MediaStore.EXTRA_DURATION_LIMIT, 30);
intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, 1024 * 1024 * 100);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
String filePath = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/msc/" + "test.mp4"; Uri uri = Uri.fromFile(new File(filePath)); intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); startActivityForResult (intent, VIDEO_WITH_CAMERA);
|
录制完回调获取
1 2 3 4 5 6 7 8 9 10 11 12
| @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); try { if (resultCode == Activity.RESULT_OK && requestCode == VIDEO_WITH_CAMERA) { Uri uri = data.getData(); Log.e(TAG, "onActivityResult: " + uri.toString()); } } catch (Exception e) { e.printStackTrace(); } }
|
获取视频时长
1 2 3
| mediaPlayer.setDataSource(this, uri); mediaPlayer.prepare(); int duration = mediaPlayer.getDuration() / 1000;
|
获取视频的第一帧的图片
1 2 3 4
| MediaMetadataRetriever media = new MediaMetadataRetriever(); String videoPath = uri.getPath(); media.setDataSource(videoPath); Bitmap bitmap = media.getFrameAtTime();
|
选择本地视频
这里有个坑,不同品牌机子间获取本地相册有兼容性问题,需要查阅对应手册针对性调用,有一篇博客记录到:
“使用ACTION_PICK的方式打开相册在oppo、vivo、华为、小米使用均没问题,但是在魅族和一加都不可以(跳转后的界面无法选择视频)。而魅族手机使用ACTION_OPEN_DOCUMENT方式可以选择视频文件,但是在华为手机(测试用的华为)上则会出现闪退(无法获取到被选中的视频文件的路径)。”
使用系统的选择本地视频的方法
1 2 3 4 5 6 7 8 9 10
| Intent intent = new Intent(); if ("Meizu".equalsIgnoreCase(android.os.Build.MANUFACTURER)) { intent.setAction(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("video/*"); } else { intent.setAction(Intent.ACTION_PICK); intent.setData(android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI); } context.startActivityForResult(intent, REQUEST_CODE_CHOOSE_VIDEO);
|
选择视频后回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void handleVideoResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && null != data) if (requestCode == REQUEST_CODE_CHOOSE_VIDEO) { try { Uri selectedVideo = data.getData(); String[] filePathColumn = {MediaStore.Video.Media.DATA}; cursor = context.getContentResolver().query(selectedVideo, filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String path = cursor.getString(columnIndex); } finally { if (cursor != null) cursor.close(); } } }
|
选中后,可以获取到视频的各种信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int videoId = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID));
String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE));
String videoPath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
int duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));
long size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE));
String imagePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
int imageId = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
|
视频录制也可以通过 MediaRecorder 类完成,其步骤与音频录制基本相同,只是添加了一些对视频进行处理的操作。
视频录制的基本步骤如下:
1. 调用Camera.open()方法打开摄像头。
2. 调用 Camera.setPreviewDisplay() 连接预览窗口。
以便将从摄像头获取的图像放置到预览窗口中显示出来。
3. 调用 Camera.startPreview()启动预览。
显示摄像头拍摄到的图像。
打开摄像机openCamera()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private void openCamera() { Log.d(TAG, "openCamera."); try { camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); camera.setDisplayOrientation(90); } catch (Exception e) { Log.e(TAG, "open camera error!"); e.printStackTrace(); return; } try { camera.setPreviewDisplay(mSurfaceHolder); } catch (IOException e) { Log.e(TAG, "preview failed."); e.printStackTrace(); } camera.startPreview(); }
|
4. 使用 MediaRecorder 进行视频录制。
1)使用 Camera.unlock() 方法解锁摄像头,以使 MediaRecorder 获得对摄像头的使用权。
unlock的作用是:Camera属于硬件设备,通常情况下Camera被一个使用Camera的进程锁定,是不允许其他进程使用的。unLock必须在你调用MediaRecorder.setCamera之前调用。
从api14 开始,录制视频时MediaRecorder调用start函数时Camera 会自动的调用Lock,所以再开始录制视频之前或者录制视频结束之后不需要手动的调用lock函数。
【注意:lock和unLock 都是只有在录制视频时才会使用,其他情况用不到这两个函数。如果你不是要录制视频,只是简单地预览不需要调用这个函数。】
unlock()解锁摄像机
1 2 3 4 5 6 7
| Camera.Parameters params = camera.getParameters(); int height = params.getSupportedVideoSizes().get(3).height; int width = params.getSupportedVideoSizes().get(3).width;
camera.unlock();
mVideoRecorder.setCamera(camera);
|
2)配置 MediaRecorder。
音频源和视频源中包括
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| AudioSource类中包括:(一般使用默认或者主麦克风或者摄像头旁边的麦克风。) MediaRecorder.AudioSource.DEFAULT: 默认音频源 MediaRecorder.AudioSource.MIC:主麦克风。 MediaRecorder.AudioSource.VOICE_CALL:来源为语音拨出的语音与对方说话的声音 MediaRecorder.AudioSource.VOICE_COMMUNICATION:摄像头旁边的麦克风 MediaRecorder.AudioSource.VOICE_DOWNLINK:下行声音 MediaRecorder.AudioSource.VOICE_RECOGNITION:语音识别 MediaRecorder.AudioSource.VOICE_UPLINK:上行声音 VideoSource类中包括: VideoSource.DEFAULT:默认 VideoSource.CAMERA:摄像头 VideoSource.SURFACE:Surface作为来源 在录制视频的步骤中有一步是调用setCamera设置Camera,这一步相当于设置来源是摄像头,下面就需要使用VideoSource.CAMERA作为视频源,还可以使用MediaRecorder的getSurface播放视频,代替setCamera,这时的视频来源就是Surface。
|
建立 MediaRecorder 类的对象,并设置音频源和视频源
1 2 3 4 5 6
| MediaRecorder mVideoRecorder = new MediaRecorder();
mVideoRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mVideoRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
|
设置视频的输出和编码格式
1 2 3 4 5 6 7 8 9 10 11
| mVideoRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mVideoRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mVideoRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mVideoRecorder.setVideoSize(width, height);
mVideoRecorder.setVideoFrameRate(30); mVideoRecorder.setVideoEncodingBitRate(8*1024*1024);
|
也可以通过 CamcorderProfile 对象可用于对 MediaRecorder 进行相关设置。
CamcorderProfile 为预先定义好的一组视频录制相关配置信息。
Android SDK 共定义了10+种 CamcorderProfile 配置,如 CamcorderProfile. QUALITY_HIGH、CamcorderProfile. QUALITY_LOW、CamcorderProfile. QUALITY_TIME_LAPSE_1080P 等。其中,QUALITY_LOW 和 QUALITY_HIGH 两种配置是所有的摄像头都支持的,其他配置则根据硬件性能决定。
每一种配置都涉及文件输出格式、视频编码格式、视频比特率、视频帧率、视频的高和宽、音频编码格式、音频的比特率、音频采样率和音频录制的通道数几个方面。通过使用这些预定义配置能够降低代码复杂度,提高编码效率。
CamcorderProfile和参数设置
1 2 3 4 5 6 7 8 9 10 11 12 13
| CamcorderProfile get(int cameraId, int quality) CamcorderProfile get(int quality)
CamcorderProfile mCamcorderProfile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_BACK, CamcorderProfile.QUALITY_HIGH); mMediaRecorder.setProfile(mCamcorderProfile);
|
设置录制的视频文件的保存位置及文件名
1
| mVideoRecorder.setOutputFile(PATH_NAME);
|
使用 MediaRecorder.setPreviewDisplay() 方法指定 MediaRecorder 的视频预览窗口
1
| mVideoRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
|
3)将录像器置于准备状态,然后启动录像器
1 2
| mVideoRecorder.prepare(); mVideoRecorder.start();
|
5. 视频录制完成后,可使用以下方法停止视频录制。
停止录像器,重置录像器的相关配置,释放录像器对象
1 2 3
| mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release();
|
【调用 Camera.lock() 方法锁定摄像头。从 Android N(7.x) 开始,该调用也不再必需,除非 MediaRecorder.prepare() 方法失败】
6. 调用Camera.stopPreview()方法停止预览。
7. 调用Camera.release()方法释放摄像头。
1 2 3 4 5 6
| private void releaseCamera() { if (camera != null) { camera.release(); camera = null; } }
|
#. 其它一些关于MediaRecorder的方法。
Android 4.0.3 引入可以使图像稳定化(通过修改参数)
1 2 3 4 5 6
| Camera.Parameters parameters = camera.getParameters();
if (parameters.isVideoStabilizationSupported()) { parameters.setVideoStabilization(true); } camera.setParameters(parameters);
|
创建一个延时的视频
1 2 3
|
mediaRecorder.setCaptureRate(0.03);
|
Camera2
简介
Android 5.0(API 21)开始出现了新的相机Camera 2 API,弃用以前的Camera API,这是因为Camera1 那寥寥无几的 API 和极差的灵活性早已不能满足日益复杂的相机功能开发。Camera2 的出现给相机应用程序带来了巨大的变革,因为它的目的是为了给应用层提供更多的相机控制权限,从而构建出更高质量的相机应用程序。
Camera2 API不仅提高了android系统的拍照性能,还支持RAW照片输出,还可以设置相机的对焦模式,曝光模式,快门等等。
这里引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。这一切建立在一个叫作 CameraCaptureSession 的会话中。
整个拍摄流程如下:
创建一个用于从 Pipeline 获取图片的 CaptureRequest。
修改 CaptureRequest 的闪光灯配置,让闪光灯在拍照过程中亮起来。
创建两个不同尺寸的 Surface 用于接收图片数据,并且将它们添加到 CaptureRequest 中。
发送配置好的 CaptureRequest 到 Pipeline 中等待它返回拍照结果。
一个新的 CaptureRequest 会被放入一个被称作 Pending Request Queue 的队列中等待被执行,当 In-Flight Capture Queue 队列空闲的时候就会从 Pending Request Queue 获取若干个待处理的 CaptureRequest,并且根据每一个 CaptureRequest 的配置进行 Capture 操作。最后我们从不同尺寸的 Surface 中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的 CaptureResult,流程结束。
Camera2 中主要的API类
CameraManager类:摄像头管理类,用于检测、打开系统摄像头,通过getCameraCharacteristics(cameraId)可以获取摄像头特征。
CameraCharacteristics类:相机特性类,例如,是否支持自动调焦,是否支持zoom,是否支持闪光灯一系列特征。
CameraDevice类: 相机设备,类似早期的camera类。
CameraCaptureSession类:用于创建预览、拍照的Session类。通过它的setRepeatingRequest()方法控制预览界面,通过它的capture()方法控制拍照动作或者录像动作。
CameraRequest类:一次捕获的请求,可以设置一些列的参数,用于控制预览和拍照参数,例如:对焦模式,曝光模式,zoom参数等等。
1 2 3 4
|
通过以下代码可以获取摄像头的特征对象,例如:前后摄像头,分辨率等。 CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
相机特性类 CameraCharacteristics是一个包含相机参数的对象,可以通过一些key获取对应的values。 以下几种常用的参数: LENS_FACING:获取摄像头方向。LENS_FACING_FRONT是前摄像头,LENS_FACING_BACK是后摄像头。 SENSOR_ORIENTATION:获取摄像头拍照的方向。 FLASH_INFO_AVAILABLE:获取是否支持闪光灯。 SCALER_AVAILABLE_MAX_DIGITAL_ZOOM:获取最大的数字调焦值,也就是zoom最大值。 LENS_INFO_MINIMUM_FOCUS_DISTANCE:获取最小的调焦距离,某些手机上获取到的该values为null或者0.0。前摄像头大部分有固定焦距,无法调节。 INFO_SUPPORTED_HARDWARE_LEVEL:获取摄像头支持某些特性的程度。 以下手机中支持的若干种程度: INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方位的硬件支持,允许手动控制全高清的摄像、支持连拍模式以及其他新特性。 INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,这个需要单独查询。 INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:所有设备都会支持,也就是和过时的Camera API支持的特性是一致的。
|
1 2 3 4 5 6 7 8 9 10 11
|
CameraDevice的reateCaptureRequest(int templateType)方法创建CaptureRequest.Builder。 templateType参数有以下几种: TEMPLATE_PREVIEW:预览 TEMPLATE_RECORD:拍摄视频 TEMPLATE_STILL_CAPTURE:拍照 TEMPLATE_VIDEO_SNAPSHOT:创建视视频录制时截屏的请求 TEMPLATE_ZERO_SHUTTER_LAG:创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量。 TEMPLATE_MANUAL:创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
该抽象类用于CemeraDevice相机设备状态的回调。
protected final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override public void onOpened(@NonNull CameraDevice cameraDevice) { mCameraDevice = cameraDevice; startPreView(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { cameraDevice.close(); mCameraDevice = null; }
@Override public void onError(@NonNull CameraDevice cameraDevice, int error) { cameraDevice.close(); mCameraDevice = null; }
@Override public void onClosed(@NonNull CameraDevice camera) { super.onClosed(camera); } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
该抽象类用于Session过程中状态的回调。 public static abstract class StateCallback { public abstract void onConfigured(@NonNull CameraCaptureSession session); public abstract void onConfigureFailed(@NonNull CameraCaptureSession session); public void onReady(@NonNull CameraCaptureSession session) {} public void onActive(@NonNull CameraCaptureSession session) {} public void onCaptureQueueEmpty(@NonNull CameraCaptureSession session) {} public void onClosed(@NonNull CameraCaptureSession session) {} public void onSurfacePrepared(@NonNull CameraCaptureSession session,@NonNull Surface surface) {} }
|
CameraManager 是那个站在高处统管所有摄像投设备(CameraDevice)的管理者,而每个 CameraDevice 自己会负责建立 CameraCaptureSession 以及建立 CaptureRequest。
CameraCharacteristics 是 CameraDevice 的属性描述类,非要做个对比的话,那么它与原来的 CameraInfo 有相似性。
类图中有着三个重要的 callback,其中 CameraCaptureSession.CaptureCallback 将处理预览和拍照图片的工作,需要重点对待。
这些类是如何相互配合的?下面是简单的流程图。
Camera2 拍照和录像流程例子
1. 打开指定的方向的相机。
最先获取CameraManager对象,通过该对象的getCameraIdList()获取到一些列的摄像头参数。
通过循环匹配,获取到指定方向的摄像头,例如后摄像头等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
for (String cameraId : manager.getCameraIdList()) { CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId); Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); if (facing == null) { continue; } if (facing != CameraCharacteristics.LENS_FACING_BACK) { continue; } manager.openCamera(mCameraId, stateCallback, workThreadManager.getBackgroundHandler()); return; }
|
当然,实际开发中,还需要获取相机支持的特性(闪光灯、zoom调焦、手动调焦等),和设置摄像头的参数(例如:预览的Size)。
2. 创建预览界面。
CameraDevice.StateCallback 对象传入CameraManager中openCamera(mCameraId, stateCallback, workThreadManager.getBackgroundHandler())的第二个参数,用于监听摄像头的状态。
创建 CameraDevice.StateCallback 对象,且开启一个相机。当相机开启后,将出现相机预览界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
protected CameraDevice mCameraDevice;
protected final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override public void onOpened(@NonNull CameraDevice cameraDevice) { mCameraDevice = cameraDevice; createCameraPreviewSession(); } ............... };
private CaptureRequest.Builder mPreviewRequestBuilder;
private void createCameraPreviewSession() { mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mCameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), stateCallback, null); }
|
3. 在预览界面过程中,需要间隔刷新界面。
相机预览使用TextureView来实现。
创建一个CameraCaptureSession ,通过一个用于预览界面的CaptureRequest,间隔复用给CameraCaptureSession。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private CameraCaptureSession mCaptureSession; CameraCaptureSession.StateCallback stateCallback=new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { mCaptureSession = cameraCaptureSession; setCameraCaptureSession(); } ....... }
private void setCameraCaptureSession() { .......... mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler()); }
|
只要未开始拍照动作或者录像动作,该复用的CaptureRequest会重复的刷新预览界面。
接下来,等待用户点击拍照按钮或者录像按钮,进行拍照动作,或者录像动作。
4. 拍照动作。
首先锁住焦点,通过在相机预览界面CaptureRequest,然后以类似方式运行一个预捕获序列。接下来,已经准备好捕捉图片。
创建一个新的CaptureRequest,且拍照。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
private void captureStillPicture() { try { final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(imageReader.getSurface()); ........... mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), captureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } }
|
拍照界面产生的数据只是在手机内存中,图片是一个磁盘文件,还需要一个将拍照产生数据写入文件中的操作类ImageReader。
先是创建ImageReader对象,和设置监听器等一系列参数。
1 2 3 4 5 6 7 8 9 10
|
private ImageReader imageReader;
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizeByArea());
imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2); imageReader.setOnImageAvailableListener(onImageAvailableListener, workThreadManager.getBackgroundHandler());
|
将ImageReader的surface配置到captureBuilder对象中captureBuilder.addTarget(imageReader.getSurface());(captureStillPicture()中)
当拍照完成后,会在该监听状态中回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
protected final ImageReader.OnImageAvailableListener onImageAvailableListener = (ImageReader reader) -> writePictureData(reader.acquireNextImage()); public void writePictureData(Image image) { if (camera2ResultCallBack != null) { camera2ResultCallBack.callBack(ObservableBuilder.createWriteCaptureImage(appContext, image)); } }
public static Observable<String> createWriteCaptureImage(final Context context, final Image mImage) { Observable<String> observable = Observable.create(subscriber -> { File file = FileUtils.createPictureDiskFile(context, FileUtils.createBitmapFileName()); ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(file); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } subscriber.onNext(file.getAbsolutePath()); }); return observable; }
|
5. 录像动作。
录像是长时间的动作,录像过程中需要重复性的刷新录制界面。其余的步骤和拍照动作基本类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
private void startRecordingVideo() { try { mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); ......... mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { mPreviewSession = cameraCaptureSession; Log.i(TAG, " startRecordingVideo 正式开始录制 "); updatePreview(); } ............. }, workThreadManager.getBackgroundHandler()); } catch (CameraAccessException | IOException e) { e.printStackTrace(); } }
private void updatePreview() { try { mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, workThreadManager.getBackgroundHandler()); } catch (CameraAccessException e) { e.printStackTrace(); } }
|
和拍照类似,将视频数据写入磁盘文件中,也是需要一个操作类MediaRecorder来实现的。
创建该操作类对象,设置一些参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| private MediaRecorder mMediaRecorder;
private void setUpMediaRecorder() throws IOException { mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mNextVideoAbsolutePath = FileUtils.createVideoDiskFile(appContext, FileUtils.createVideoFileName()).getAbsolutePath(); mMediaRecorder.setOutputFile(mNextVideoAbsolutePath); mMediaRecorder.setVideoEncodingBitRate(10000000); mMediaRecorder.setVideoFrameRate(30); mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight()); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); switch (mSensorOrientation) { case SENSOR_ORIENTATION_DEFAULT_DEGREES: mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); break; case SENSOR_ORIENTATION_INVERSE_DEGREES: mMediaRecorder.setOrientationHint(ORIENTATIONS.get(rotation)); break; default: break; } mMediaRecorder.prepare(); }
|
间隔性的随着视频录制而输出数据到文件中
1 2 3 4
| Surface recorderSurface = mMediaRecorder.getSurface(); surfaces.add(recorderSurface); mPreviewBuilder.addTarget(recorderSurface);
|
最后,当录制视频结束后,停止输出
1 2 3
| mMediaRecorder.stop(); mMediaRecorder.reset();
|
6. 恢复到预览界面。
完成一些列拍照或录像动作后,重新恢复到预览界面
1 2 3 4 5 6 7 8 9 10 11
|
private void unlockFocus() { try { mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler()); } catch (CameraAccessException e) { e.printStackTrace(); } }
|
当然,还有关闭相机操作,和与Activity生命周期绑定的操作,这里不再做介绍了。
总结
Camera Intent方便快捷,不需要太多的代码量;MediaRecorder代码量稍大
Camera Intent视频清晰度只有两种,一个是最不清楚,一个是最清楚;MediaRecorder视频清晰度可根据数值自动往上调
Camera Intent在录制过程中,操作方便,有自己的暂停、录制、播放按钮;MediaRecorder需要自己去写暂停、录制或播放按钮
Camera1 严格区分了预览和拍照两个流程,而 Camera2 则把这两个流程都抽象成了 Capture 行为,只不过一个是一次性的 Capture,一个是不断重复的 Capture 而已
如同 Camera1 一样,Camera2 的一些 API 调用也会耗时,所以建议使用独立的线程执行所有的相机操作,尽量避免直接在主线程调用 Camera2 的 API,可使用 HandlerThread
Camera2 所有的相机操作都可以注册相关的回调接口,然后在不同的回调方法里写业务逻辑,这可能会让代码因为不够线性而错综复杂,建议可以尝试使用子线程的阻塞方式来尽可能地保证代码的线性执行。例如在子线程阻塞等待 CaptureResult,然后继续执行后续的操作,而不是将代码拆分到到 CaptureCallback.onCaptureCompleted() 方法里
可以认为 Camera1 是 Camera2 的一个子集,也就是说 Camera1 能做的事情 Camera2 一定能做
一些其它地方别人的总结:
如果应用程序需要同时兼容 Camera1 和 Camera2,建议分开维护,因为 Camera1 蹩脚的 API 设计很可能让 Camera2 灵活的 API 无法得到充分的发挥,另外将两个设计上完全不兼容的东西搅和在一起带来的痛苦可能远大于其带来便利性,多写一些冗余的代码也许还更开心
官方说 Camera2 的性能会更好,但起码在较早期的一些机器上运行 Camera2 的性能并没有比 Camera1 好
当设备的 Supported Hardware Level 低于 FULL 的时候,建议还是使用 Camera1,因为 FULL 级别以下的 Camera2 能提供的功能几乎和 Camera1 一样,所以倒不如选择更加稳定的 Camera1
参考与鸣谢
https://www.jianshu.com/p/9a2e66916fcb