说明:camera子系统 系列文章针对Android10.0系统,主要针对 camera API2 + HAL3 框架进行解读。
1 录像&保存视频流程简要解读
@1 当预览创建之后,点击录像 button,触发录像事件,首先是停止预览,准备切换到录制视频模式,该部分关键代码如下所示:
private void closeCameraCaptureSession() {
if (mCaptureSession != null) {
(TAG, "close Session:" + ());
();
mCaptureSession = null;
}
}
//...
//camera record process,step1 停止预览,准备切换到录制视频
try {
();
closeCameraCaptureSession();
} catch (CameraAccessException e) {
();
}
@2 创建 MediaRecorder 实例,并初始化相关设置,,关键代码如下所示:
//camera record process,step2 mMediaRecorder相关设置
mVideoFile = new File((),new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) +"demo.mp4");
();//设置音频来源
();//设置视频来源
(.MPEG_4);//设置输出格式
();//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
();//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
(8*1024*1920);//设置比特率 一般是 1*分辨率 到 10*分辨率之间波动。比特率越大视频越清晰但是视频文件也越大。
(30);//设置帧数.
((),());
(90);
Surface surface = new Surface(());
(surface);
(());
try {
();
} catch (IOException e) {
();
}
@3 调用 (CameraDevice.TEMPLATE_RECORED)方法, 为新的捕获请求创建一个 对象,并用CameraDevice.TEMPLATE_RECORED 参数初始化,关键代码如下所示:
//camera record process,step3 创建 对象,并用CameraDevice.TEMPLATE_RECORED 参数初始化
mPreviewRequestBuilder = (CameraDevice.TEMPLATE_RECORD);
(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
@4 调用 ()方法,将MediaRecorder和预览用的Surface实例添加到该请求的目标列表中,关键代码如下所示:
SurfaceTexture surfaceTexture = ();
((),());
Surface previewSurface = new Surface(surfaceTexture);
Surface recorderSurface = ();//从获取录制视频需要的Surface
//camera record process,step4 将MediaRecorder和预览用的Surface实例添加到该请求的目标列表中
(previewSurface);
(recorderSurface);
@5 执行 方法,通过提供目标输出集来创建新 的捕获会话,该方法传入三个参数:
- List<Surface>:新的用于捕获图像信息的 Surface 集合,此处为显示预览 信息的 surface 实例,以及记录图像信息用的 MediaRecorder 的实例
- :用于通知新捕获 session 的callback
- Handler:为一个句柄,代表执行 callback 的 handler,如果程序希望直 接在当前线程中执行 callback,则可以将 handler 参数设为 null
同时在这里重写onConfigured()方法,调用CameraCaptureSession . setRepeatingReqest()方法,通过此捕获session,持续重复捕获图像(也可以调用 ()方法,设置捕获的参数,可在此处设置 3A算法相)。最后调用 ()方法,开始捕获数据并将数据编码到指定文件。关键代码如下所示:
//camera record process,step5 执行 方法
((previewSurface,recorderSurface),new () {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
mCaptureSession = cameraCaptureSession;
(TAG, "Video Session:" + ());
(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
(new Runnable() {
@Override
public void run() {
try {
((), null, mCameraHandler);
} catch (CameraAccessException e) {
throw new RuntimeException("Can't visit camera");
}
}
});
();
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
}
},mCameraHandler);
@6 录像后 当点击停止录像时,执行CameraCaptureSession的stopRepeating()方法,取消持续捕获,同时调用 CameraCaptureSession的abortCapture()方法,尽可能快地丢弃当前待处理和正在进行的所有捕获(若不做这两步处理,停止录像时会闪退)关键代码如下所示:
(TAG,"stopRecorder");
try {
//camera record process,step6 取消持续捕获&
();
();
} catch (CameraAccessException e) {
();
}
@7 调用 ,停止图像捕获 并且重启预览模式(根据需要可以此时处理视频,使得系统Gallery可以直接查看到该视频),关键代码如下所示:
//camera record process,step7 停止图像捕获 并且重启预览模式
();
();
//可根据需要将视频设置为系统gallery可见。
(new (mContext, mVideoFile));
startCaptureSession();
关于视频对Gallery可见,以下为关键参考代码:
public static class VideoSaver implements Runnable {
private final File mFile;
Context mContext;
VideoSaver(Context context,File file) {
mContext = context;
mFile = file;
}
private ContentValues getVideoContentValues(File paramFile,long paramLong) {
ContentValues values = new ContentValues();
(, ());
(.DISPLAY_NAME, ());
(.MIME_TYPE, "video/mp4");
(.DATE_TAKEN, (paramLong));
(.DATE_MODIFIED, (paramLong));
(.DATE_ADDED, (paramLong));
(, ());
(, (()));
return values;
}
@Override
public void run() {
(TAG, "recorder video Run");
ContentResolver localContentResolver = ();
ContentValues localContentValues = getVideoContentValues(mFile, ());
Uri localUri = (.EXTERNAL_CONTENT_URI, localContentValues);
OutputStream os = null;
FileInputStream fis = null;
byte[] buf = new byte[1024];
int len;
try {
if (localUri != null) {
fis = new FileInputStream(mFile);
os = (localUri);
}
if (os != null) {
while ((len = (buf)) >= 0) {
(buf, 0, len);
}
}
} catch (FileNotFoundException e) {
();
} catch (IOException e) {
();
} finally {
try {
if(os!=null) {
();
}
if(fis!=null){
();
}
} catch (IOException e) {
();
}
}
}
}
2 camera录像&保存视频流程代码完整解读
2.1 java源码部分(草稿)
Camera流程相关代码如下所示:
class CameraCoreManager {
private static final String TAG = "CameraDemo";
private Context mContext;
private CameraManager mCameraManager;
private String mCameraId;
private HandlerThread mCameraThread;
private Handler mCameraHandler;
private ImageReader mImageReader;
private CameraDevice mCameraDevice;
private CameraCharacteristics mCameraCharacteristics;
private MediaRecorder mMediaRecorder;
//Max preview width&height that is guaranteed by Camera2 API
private static final int MAX_PREVIEW_WIDTH = 1080;
private static final int MAX_PREVIEW_HEIGHT = 720;
//A Semaphore to prevent the app from exiting before closing the camera.
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
private Size mPreviewSize = new Size(1920, 1080);
private mPreviewRequestBuilder;
private mCaptureRequestBuilder;
private mRecordRequestBuilder;
private CameraCaptureSession mCaptureSession;
private int mFacing = CameraCharacteristics.LENS_FACING_BACK;
private mFrameCallback;
private SurfaceTexture mSurfaceTexture;
private File mCameraFile;
private File mVideoFile;
private TextureView mTextureView;
private enum State{
STATE_PREVIEW,
STATE_CAPTURE,
}
State mState = State.STATE_PREVIEW;
//camera capture process,step3 创建ImageReader并设置mImageAvailableListener,实现如下:
private mImageAvailableListener = new () {
@Override
public void onImageAvailable(ImageReader reader) {
if(mState == State.STATE_PREVIEW){
//(TAG, "##### onFrame: Preview");
Image image = ();
();
}else if(mState == State.STATE_CAPTURE) {
(TAG,"capture one picture to gallery");
mCameraFile = new File("aa_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
(new (mContext, (), mCameraFile));
mState = State.STATE_PREVIEW;
}else{
(TAG, "##### onFrame: default/nothing");
}
}
};
//camera preview process,step2 mStateCallback 实例化
private mStateCallback = new () {
@Override
public void onOpened(CameraDevice camera) {
//重写onOpened方法,最为关键
();
mCameraDevice = camera;
startCaptureSession();
}
@Override
public void onDisconnected(CameraDevice camera) {
();
();
mCameraDevice = null;
}
@Override
public void onError(CameraDevice camera, int error) {
("DEBUG", "onError: " + error);
();
();
mCameraDevice = null;
("DEBUG", "onError: restart camera");
stopPreview();
startPreview();
}
};
mCaptureCallback = new () {
@Override
public void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request, TotalCaptureResult result) {
(session, request, result);
}
@Override
public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
(session, request, failure);
}
};
public CameraCoreManager(Context context, TextureView textureView) {
mContext = context;
mCameraManager = (CameraManager) (Context.CAMERA_SERVICE);
mMediaRecorder = new MediaRecorder();
mState = State.STATE_PREVIEW;
mTextureView = textureView;
}
public void startPreview() {
(TAG,"startPreview");
if (!chooseCameraIdByFacing()) {
(TAG, "Choose camera failed.");
return;
}
mCameraThread = new HandlerThread("CameraThread");
();
mCameraHandler = new Handler(());
if (mImageReader == null) {
mImageReader = ((), (), , 2);
(mImageAvailableListener, mCameraHandler);
}else{
();
}
openCamera();
}
public void stopPreview() {
(TAG,"stopPreview");
closeCamera();
if (mCameraThread != null) {
();
mCameraThread = null;
}
mCameraHandler = null;
}
private boolean chooseCameraIdByFacing() {
try {
String ids[] = ();
if ( == 0) {
(TAG, "No available camera.");
return false;
}
for (String cameraId : ()) {
CameraCharacteristics characteristics = (cameraId);
StreamConfigurationMap map = (
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
Integer level = (CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
continue;
}
Integer internal = (CameraCharacteristics.LENS_FACING);
if (internal == null) {
continue;
}
if (internal == mFacing) {
mCameraId = cameraId;
mCameraCharacteristics = characteristics;
return true;
}
}
mCameraId = ids[1];
mCameraCharacteristics = (mCameraId);
Integer level = (CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
return false;
}
Integer internal = (CameraCharacteristics.LENS_FACING);
if (internal == null) {
return false;
}
mFacing = CameraCharacteristics.LENS_FACING_BACK;
} catch (CameraAccessException e) {
();
}
return true;
}
@SuppressLint("MissingPermission")
public void openCamera() {
if ((mCameraId)) {
(TAG, "Open camera failed. No camera available");
return;
}
try {
if (!(2500, )) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
//camera preview process,step1 打开camera
(mCameraId, mStateCallback, mCameraHandler);
} catch (InterruptedException | CameraAccessException e) {
(TAG, ());
}
}
private void closeCamera() {
try {
();
if (mCaptureSession != null) {
();
mCaptureSession = null;
}
if (mCameraDevice != null) {
();
mCameraDevice = null;
}
if (mImageReader != null) {
();
mImageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
();
}
}
private void startCaptureSession() {
mState = State.STATE_PREVIEW;
if (mCameraDevice == null) {
return;
}
if ((mImageReader != null || mSurfaceTexture != null)) {
try {
closeCameraCaptureSession();
//camera preview process,step3 创建一个 ,templateType来区分是拍照还是预览
mPreviewRequestBuilder = (CameraDevice.TEMPLATE_PREVIEW);
//camera preview process,step4 将显示预览用的surface的实例传入,即将显示预览用的 surface 的实例,作为一个显示层添加到该 请求的目标列表中
(());
List<Surface> surfaceList = (());
if (mSurfaceTexture != null) {
((), ());
Surface surface = new Surface(mSurfaceTexture);
(());
(surface);
//camera preview process,step5 将显示预览用的surface的实例传入,即将显示预览用的surface的实例,作为一个显示层添加到该请求的目标列表中
surfaceList = (surface, ());
}
Range<Integer>[] fpsRanges = (CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
("DEBUG", "##### fpsRange: " + (fpsRanges));
//camera preview process,step6 & 7
// 6 执行createCaptureSession方法
// 7 参数中实例化 ,并重写 onConfigured 方法
(surfaceList, new () {
@Override
public void onConfigured(CameraCaptureSession session) {
if (mCameraDevice == null) return;
(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
mCaptureSession = session;
try {
if (mCaptureSession != null)
//camera preview process,step8 用 ()方法创建预览
((), mCaptureCallback, mCameraHandler);
} catch (CameraAccessException | IllegalArgumentException | IllegalStateException | NullPointerException e) {
();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
(TAG, "Failed to configure capture session");
}
@Override
public void onClosed(CameraCaptureSession session) {
if (mCaptureSession != null && (session)) {
mCaptureSession = null;
}
}
}, mCameraHandler);
} catch (CameraAccessException e) {
();
(TAG, ());
} catch (IllegalStateException e) {
stopPreview();
startPreview();
} catch (UnsupportedOperationException e) {
();
(TAG, ());
}
}
}
public void captureStillPicture() {
try {
(TAG,"captureStillPicture");
mState = State.STATE_CAPTURE;
if (mCameraDevice == null) {
return;
}
// camera capture process,step1 创建作为拍照的
mCaptureRequestBuilder = (CameraDevice.TEMPLATE_STILL_CAPTURE);
// camera capture process,step2 将imageReader的surface作为的目标
(());
// 设置自动对焦模式
(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 设置为自动模式
(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
// 设置摄像头旋转角度
(CaptureRequest.JPEG_ORIENTATION, Surface.ROTATION_0);
// 停止连续取景
();
// camera capture process,step5 &6 捕获静态图像,结束后执行onCaptureCompleted
((), new () {
@Override// 拍照完成时激发该方法
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
(TAG,"onCaptureCompleted");
startCaptureSession();
}
}, mCameraHandler);
} catch (CameraAccessException e) {
();
}
}
private void setupMediaRecorder(){
//camera record process,step1 停止预览,准备切换到录制视频
try {
();
closeCameraCaptureSession();
} catch (CameraAccessException e) {
();
}
//camera record process,step2 mMediaRecorder相关设置
mVideoFile = new File((),new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) +"demo.mp4");
();//设置音频来源
();//设置视频来源
(.MPEG_4);//设置输出格式
();//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
();//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
(8*1024*1920);//设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。
(30);//设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。
((),());
(90);
Surface surface = new Surface(());
(surface);
(());
try {
();
} catch (IOException e) {
();
}
SurfaceTexture surfaceTexture = ();
((),());
Surface previewSurface = new Surface(surfaceTexture);
Surface recorderSurface = ();//从获取录制视频需要的Surface
try {
//camera record process,step3 创建 对象,并用CameraDevice.TEMPLATE_RECORED 参数初始化
mPreviewRequestBuilder = (CameraDevice.TEMPLATE_RECORD);
(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//camera record process,step4 将MediaRecorder和预览用的Surface实例添加到该请求的目标列表中
(previewSurface);
(recorderSurface);
//请注意这里设置了(previewSurface,recorderSurface) 2个Surface,很好理解录制视频也需要有画面预览,第一个是预览的Surface,第二个是录制视频使用的Surface
//camera record process,step5 执行 方法
((previewSurface,recorderSurface),new () {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
mCaptureSession = cameraCaptureSession;
(TAG, "Video Session:" + ());
(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
(new Runnable() {
@Override
public void run() {
try {
((), null, mCameraHandler);
} catch (CameraAccessException e) {
throw new RuntimeException("Can't visit camera");
}
}
});
();
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
}
},mCameraHandler);
} catch (CameraAccessException e) {
();
}
}
private void closeCameraCaptureSession() {
if (mCaptureSession != null) {
(TAG, "close Session:" + ());
();
mCaptureSession = null;
}
}
public void startRecorder(){
(TAG,"startRecorder");
setupMediaRecorder();
}
public void stopRecorder(){
(TAG,"stopRecorder");
try {
//camera record process,step6 取消持续捕获
();
//丢弃当前待处理和正在进行的所有捕获
();
} catch (CameraAccessException e) {
();
}
//camera record process,step7 停止图像捕获 并且重启预览模式
();
();
//可根据需要将视频设置为系统gallery可见。
(new (mContext, mVideoFile));
startCaptureSession();
}
public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
mSurfaceTexture = surfaceTexture;
}
}
保存照片和保存视频相关操作在FileUtils类中代码如下所示:
class FileUtils {
private static final String TAG = "FileUtils";
//camera capture progress,step4 保存图片相关操作
public static class ImageSaver implements Runnable {
private final Image mImage;
private final File mFile;
Context mContext;
ImageSaver(Context context,Image image, File file) {
mContext = context;
mImage = image;
mFile = file;
}
@Override
public void run() {
(TAG,"take picture Image Run");
ByteBuffer buffer = ()[0].getBuffer();
byte[] bytes = new byte[()];
(bytes);
ContentValues values = new ContentValues();
(, "This is an qr image");
(.DISPLAY_NAME, ());
(.MIME_TYPE, "image/jpeg");
(, "");
(.RELATIVE_PATH, "Pictures/");
Uri external = .EXTERNAL_CONTENT_URI;
ContentResolver resolver = ();
Uri insertUri = (external, values);
OutputStream os = null;
try {
if (insertUri != null) {
os = (insertUri);
}
if (os != null) {
(bytes);
}
} catch (IOException e) {
();
} finally {
();
try {
if(os!=null) {
();
}
} catch (IOException e) {
();
}
}
}
}
public static class VideoSaver implements Runnable {
private final File mFile;
Context mContext;
VideoSaver(Context context,File file) {
mContext = context;
mFile = file;
}
private ContentValues getVideoContentValues(File paramFile,long paramLong) {
ContentValues values = new ContentValues();
(, ());
(.DISPLAY_NAME, ());
(.MIME_TYPE, "video/mp4");
(.DATE_TAKEN, (paramLong));
(.DATE_MODIFIED, (paramLong));
(.DATE_ADDED, (paramLong));
(, ());
(, (()));
return values;
}
@Override
public void run() {
(TAG, "recorder video Run");
ContentResolver localContentResolver = ();
ContentValues localContentValues = getVideoContentValues(mFile, ());
Uri localUri = (.EXTERNAL_CONTENT_URI, localContentValues);
OutputStream os = null;
FileInputStream fis = null;
byte[] buf = new byte[1024];
int len;
try {
if (localUri != null) {
fis = new FileInputStream(mFile);
os = (localUri);
}
if (os != null) {
while ((len = (buf)) >= 0) {
(buf, 0, len);
}
}
} catch (FileNotFoundException e) {
();
} catch (IOException e) {
();
} finally {
try {
if(os!=null) {
();
}
if(fis!=null){
();
}
} catch (IOException e) {
();
}
}
}
}
}
Activity UI相关代码如下所示:
public class MainActivity extends AppCompatActivity implements {
private static final String TAG = "CameraDemo";
private ImageButton mTakePictureBtn;
private Button mBtnVideoStart;
private Button mBtnVideoStop;
private CameraCoreManager manager;
private TextureView mTextureView;
Lock Mutex;
@Override
protected void onCreate(Bundle savedInstanceState) {
(savedInstanceState);
setContentView(.activity_camera);
int rotation = getWindowManager().getDefaultDisplay().getRotation();
mTakePictureBtn = findViewById(.camera_take_picture);
(this);
mBtnVideoStart = findViewById(.btn_video_start);
(this);
mBtnVideoStop = findViewById(.btn_video_stop);
(this);
mTextureView = findViewById(.texture_view);
(new () {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
(surface);
();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
manager = new CameraCoreManager(this,mTextureView);
(TAG,"onCreate:init");
}
@Override
public void onResume() {
();
}
@Override
public void onPause() {
();
}
@Override
protected void onDestroy() {
();
();
}
@Override
public void onClick(View v) {
switch (()) {
case .camera_take_picture:
(TAG,"takepicture");
();
break;
case .btn_video_start:
();
break;
case .btn_video_stop:
();
break;
default:
break;
}
}
}
2.2 layout文件
这里涉及布局文件主要为activity_camera.xml,xml文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
< xmlns:andro
xmlns:app="/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gray"
android:gravity="center"
android:orientation="horizontal"
android:padding="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<ImageButton
android:
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/ic_camera" />
</LinearLayout>
<TextureView
android:
android:layout_width="420dp"
android:layout_height="360dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="录相开始"
app:layout_constraintTop_toBottomOf="@id/texture_view"
app:layout_constraintLeft_toLeftOf="parent"/>
<Button
android:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="录相结束"
app:layout_constraintTop_toBottomOf="@id/texture_view"
app:layout_constraintRight_toRightOf="parent"/>
</>