Android5.0之前使用android.hardware包下的Camera类进行拍照、录视频等功能。5.0以后,新增了android.hardware.camera2包,利用新的机制、新的类进行拍照、录视频。
由于手机摄像头配置不同、摄像头摆放方向不同、位置不同等等因素,与摄像头相关参数如:摄像头个数、支持的像素、画面预览方向、闪光灯、对焦方式、帧率等等都不一样,必须根据当前手机的配置动态获取。获取方法如下:
Camera.Parameters p = mCamera.getParameters();
List.Size> preSizes = p.getSupportedPreviewSizes();
List.Size> preSizes = p.getSupportedPictureSizes();
......
......
获取其它参数就是getSupportedXXX,返回的都是list,手动遍历,选择最合适的参数。
拍照主要由两部分功能组成,一个是预览界面,一个是“获取正确的照片”。实现步骤如下:
1、初始化Camera
public boolean initCamera() {
mCamera = null;
try {
mCamera = Camera.open();
} catch (Exception e) {
}
return null != mCamera;
}
2、设置预览orientation
private void setPreviewOrientation(){
int result = 0;
// 计算Activity转动的角度degree。如果在manifest中设置为portrait,则degree总是为0
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degree = 0;
switch (rotation) {
case Surface.ROTATION_0:
degree = 0;
break;
case Surface.ROTATION_90:
degree = 90;
break;
case Surface.ROTATION_180:
degree = 180;
break;
case Surface.ROTATION_270:
degree = 270;
break;
}
// 获取后置摄像头信息,获取方法见后面
Camera.CameraInfo info = getBackCameraInfo();
// 摄像头转动角度减去Activity转动角度,得到角度差,然后在设置预览画面角度时把这个角度差补偿回来
if (null != info) {
int offset = info.orientation - degree;
// 如果角度差为正值,result就等于offset;
// 如果角度差为-90度,result就转270度,刚好到-90度的位置;如果角度差为-180度....以此类推
result = (offset + 360) % 360;
}
// 设置预览画面转动角度
mCamera.setDisplayOrientation(result);
}
//遍历所有摄像头信息,根据info.facing判断该摄像头是前置还是后置
public static Camera.CameraInfo getBackCameraInfo() {
int cameraNums = Camera.getNumberOfCameras();
for (int i = 0; i < cameraNums; i++) {
// CameraInfo类只是存储了一些字段,刚new出来,这些字段都为null
Camera.CameraInfo info = new Camera.CameraInfo();
// 虽然方法名为getXXX,但实际上的作用是:获取第cameraId个摄像头,把这个摄像头的信息存储到info中
Camera.getCameraInfo(i, info);
if (Camera.CameraInfo.CAMERA_FACING_BACK == info.facing) {
return info;
}
}
return null;
}
3、设置预览画面大小、比例
// 获取该摄像头支持的预览尺寸sizes,遍历sizes,计算出最合适的尺寸bestSize。具体算法就不贴出了
private void setPreviewSize(){
Camera.Parameters p = mCamera.getParameters();
List preSizes = p.getSupportedPreviewSizes();
Camera.Size bestSize = KuCameraUtil.getBestSize(preSizes, minTotalPix, maxTotalPix, rate);
if (null != bestSize && bestSize.width > 0 && bestSize.height > 0) {
p.setPreviewSize(bestSize.width, bestSize.height);
// camera设置完previewSize后,负责显示预览的surfaceView/textureView也需要进行设置大小
mCamera.setParameters(p);
}
}
4、初始化显示预览画面的控件surfaceView或textureView
// 预览控件既可以用surfaceView,也可以用textureView,这两个类的详细介绍有空再整理,推荐使用textureView
private void initTextureView(){
mTextureView = (TextureView) findViewById(R.id.preview);
// 获取camera的previewSize,根据其设置mTextureView控件的大小、比例
Camera.Size size = mCamera.getPreviewSize();
if (null != size) {
// 宽度固定为屏幕宽度,按照previewSize的宽高比设置高度
float rate = (float) size.height / (float) size.width;
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mTextureView.getLayoutParams();
params.width = ScreenUtils.getScreenWidth(this);
params.height = (int) (rate >= 1 ? params.width * rate : params.width / rate);
mTextureView.setLayoutParams(params);
}
// mTextureView生命周期回调
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
// 开始预览
mKuCamera.startPreview(surface);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// 结束预览
mKuCamera.stopPreview();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
}
5、执行拍照,获取拍照图片
// 拍照
private void takePhoto(){
mCamera.takePhoto(new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] bytes, Camera camera) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (bitmap != null) {
try {
File file = new File(KuPathUtil.getImageDir(), KuPathUtil.getNowTimeStr() + "jpg");
OutputStream os = new FileOutputStream(file);
os.write(bytes);
os.flush();
os.close();
// 获取到的图片是摄像头捕获到的原始图片,也需要对其进行旋转,旋转方法见后面
PhotoUtil.setPicOrientation(file.getAbsolutePath(), mNeedOrientation);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
// 旋转图片
public static void setPicOrientation(String filePath, int degree) {
try {
ExifInterface exifInterface = new ExifInterface(filePath);
String orientation = String.valueOf(ExifInterface.ORIENTATION_NORMAL);
// 角度转换为对应的ORIENTATION_ROTATE值
if (90 == degree) {
orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);
} else if (180 == degree) {
orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);
} else if (270 == degree) {
orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);
}
//设置选择角度
exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, orientation);
exifInterface.saveAttributes();
} catch (IOException e) {
e.printStackTrace();
}
}
6、停止预览、释放camera资源
private void release() {
if (null != mCamera) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
1、初始化camera、设置预览orientation、设置previewSize、初始化surfaceView/textureView与上面拍照的流程一模一样。其中,如果使用surfaceView,在MediaRecorder中也需要设置,使用textureView则不需要。
2、进行录制
private void startRecording(){
try {
// 初始化recorder
mRecorder = new MediaRecorder();
// 解锁摄像头,连接到摄像头
mCamera.unlock();
mRecorder.setCamera(mCamera);
// 设置音、视频源
mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 设置文件输出格式、音视频编码格式。设置顺序必须按照下面的顺序来,否则会报错
// 关于音视频格式的优缺点,参考http://blog.csdn.net/wcl0715/article/details/676137和http://blog.csdn.net/androidzhaoxiaogang/article/details/6865644
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 设置orientation,这个跟预览画面的角度一样,按照上面的代码获取角度
mRecorder.setOrientationHint(mNeedOrientation);
// 设置比特率,这个参数对视频文件的大小影响最大(格式相同情况下)
mRecorder.setVideoEncodingBitRate(800 * 800);
// 设置帧率。设置得过低会闪屏,设置高一点也不会增加文件大小,建议设置30左右
mRecorder.setVideoFrameRate(30);
// 设置视频size。通过camera.getParameters().getSupportedVideoSizes()获取到所有支持的sizes,选一个最合适的
if (size.width > 0 && size.height > 0) {
mRecorder.setVideoSize(size.width , size.height);
}
// 到时间后,可以通过MediaRecorder.OnInfoListener接收到回调;到时间后录制不会自动停止,但最终视频文件只截取前面10s
mRecorder.setMaxDuration(10000);
mRecorder.setOutputFile(videoPath);
// 准备、开始
mRecorder.prepare();
mRecorder.start();
return true;
} catch (IOException e) {
Log.i("wk", "record prepare failed,IOException:" + e.toString());
return false;
}
}
3、停止录制、释放MediaRecorder资源
private void stopRecording() {
if (null != mRecorder) {
mRecorder.stop();
mRecorder.release();
mRecorder = null;
}
}
4、锁定Camera、停止预览、释放Camera资源
Camera2网上已经有很多介绍了,我写了一个使用Camera2录像的demo,使用方法、注意事项等就直接在代码注释中,就不单独用文字描述了,偷懒。
为了方便理清camera2使用的主逻辑,一些配置代码、计算代码放在各个helper类里。这种代码设计不是最优设计,实际开发中不要照搬。
先是使用的代码,各个helper类的代码放在后面。
/**
* 一、Camera2使用
* Camera2的操作都是基于管道的,就是发送请求、等待回应的过程,使用起来没有代码结构不如Camera那种线性调用清晰。通过下面四个回调就能说清楚使用过程:
*
* 1.等待surface创建成功的回调,即SurfaceTextureListener(或者是SurfaceView的listener,demo用的是TextureView)
* 做Camera开发就必须要预览,要预览就得有Surface,所以第一步就是等待Surface创建完成;
*
* 2.等待Camera启动完成的回调,即CameraDevice.StateCallback
* Camera的启动需要一个过程,只有Camera启动后才可进行各种操作
*
* 3.等待会话建立的回调,即CameraCaptureSession.StateCallback
* 要向Camera发送各种操作请求,就必须先建立会话通道
*
* 4.等待操作请求的回调,即CameraCaptureSession.CaptureCallback
* 向Camera发起了"拍照"请求后,Camera需要一定时间才能完成,等待完成后就可以对图像数据进行处理了
*
* 总结起来就是:创建surface、启动camera、创建camera会话、发起拍照请求
*
* 二、预览、拍照/录像中的方向问题
* 1.摄像头总是采集某个矩形范围内的图像,摄像头的方向决定了这个矩形的哪个边是底、哪个边是左/右;
*
* 2.摄像头会把采集到的图像按照摄像头的方向传输给屏幕,用于显示预览图,所以需要根据摄像头方向和屏幕方向,决定预览显示的旋转角度;
*
* 3.摄像头采集的图像就是最终的成像数据,所以需要根据摄像头方向和拍摄时的手机方向,决定最终成像需要旋转的角度
*/
public class VideoActivity extends Activity implements View.OnClickListener {
private static final String TAG = "VideoActivity";
private TextureView mTextureView;
private CameraDevice mCameraDevice;
// 方便理清camera2使用的主逻辑,一些配置代码、计算代码放在各个helper类里
private CameraHelper mCameraHelper;
private RecorderHelper mRecorderHelper;
private TextureHelper mTextureHelper;
// 一个device同一时间只能存在一个session,新的session启动时会关闭其它session;
// 一个session对应一个request、surfaceList,注意处理好一一对应关系
private CaptureRequest.Builder mRequest;
private List mSurfaceList = new ArrayList<>();
private CameraCaptureSession mSession;
// camera2中用到的几个回调,通过指定handler,回调方法就会在该handler所在线程被调用
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video);
mTextureView = findViewById(R.id.texture);
findViewById(R.id.video_record).setOnClickListener(this);
findViewById(R.id.video_stop).setOnClickListener(this);
mCameraHelper = new CameraHelper(this);
mRecorderHelper = new RecorderHelper(this);
mTextureHelper = new TextureHelper(this);
}
@Override
public void onResume() {
super.onResume();
// 启动后台线程,用于执行回调中的代码
startBackgroundThread();
// 如果Activity是从stop/pause回来,TextureView是OK的,只需要重新开启camera就行
if (mTextureView.isAvailable()) {
openCamera();
} else {
// Activity创建时,添加TextureView的监听,TextureView创建完成后就可以开启camera就行了
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
public void onPause() {
// 关闭camera,关闭后台线程
closeCamera();
stopBackgroundThread();
super.onPause();
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.video_record:
if (!mRecorderHelper.isRecording()) {
startRecord();
}
break;
case R.id.video_stop:
if (mRecorderHelper.isRecording()) {
stopRecord();
}
break;
}
}
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void openCamera() {
// 设置预览大小、方向/角度
mTextureHelper.configPreview(mTextureView, mTextureView.getWidth(), mTextureView.getHeight());
// 开启后置摄像头
mCameraHelper.openCamera(mCameraHelper.getBackCameraId(), new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// 如果openCamera()方法的第三个参数指定了handler,那么下面的代码就会在该handler所在线程中执行,如果不指定就在openCamera()方法所在线程执行
mCameraDevice = cameraDevice;
startPreviewSession();
if (null != mTextureView) {
mTextureHelper.configPreview(mTextureView, mTextureView.getWidth(), mTextureView.getHeight());
}
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
mCameraDevice = null;
finish();
}
}, null);
}
private void addTextureViewSurface() {
// 获取TextureView中的surface,添加到request中、添加到surfaceList中
Surface previewSurface = mTextureHelper.getSurface(mTextureView);
if (null != previewSurface) {
mRequest.addTarget(previewSurface);
mSurfaceList.add(previewSurface);
}
}
private void addRecorderSurface() {
// 获取MediaRecorder中的surface,添加到request中、添加到surfaceList中
Surface recorderSurface = mRecorderHelper.getSurface();
if (null != recorderSurface) {
mRequest.addTarget(recorderSurface);
mSurfaceList.add(recorderSurface);
}
}
private void closeCamera() {
// 关闭camera预览,关闭MediaRecorder
closePreviewSession();
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
mRecorderHelper.release();
}
private void startPreviewSession() {
if (null == mCameraDevice || !mTextureView.isAvailable()) {
return;
}
try {
// 创建新的会话前,关闭以前的会话
closePreviewSession();
// 创建预览会话请求
mSurfaceList.clear();
mRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
addTextureViewSurface();
// 启动会话
// 参数1:camera捕捉到的画面分别输出到surfaceList的各个surface中;
// 参数2:会话状态监听;
// 参数3:监听器中的方法会在指定的线程里调用,通过一个handler对象来指定线程;
mCameraDevice.createCaptureSession(mSurfaceList, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mSession = session;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
@Override
public void onClosed(@NonNull CameraCaptureSession session) {
super.onClosed(session);
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void startRecordSession() {
if (null == mCameraDevice || !mTextureView.isAvailable()) {
return;
}
try {
closePreviewSession();
// 创建录像会话请求
mSurfaceList.clear();
mRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
addTextureViewSurface();
addRecorderSurface();
// 启动会话。可以看出跟上面的"预览session"是一样的,只是surfaceList多加了一个
mCameraDevice.createCaptureSession(mSurfaceList, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mSession = session;
updatePreview();
runOnUiThread(new Runnable() {
@Override
public void run() {
mRecorderHelper.start();
}
});
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(VideoActivity.this, "Failed", Toast.LENGTH_SHORT).show();
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void updatePreview() {
if (null == mCameraDevice) {
return;
}
try {
mRequest.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
// 这个接口是预览。作用是把camera捕捉到的画面输出到surfaceList中的各个surface上,每隔一定时间重复一次
mSession.setRepeatingRequest(mRequest.build(), null, mBackgroundHandler);
// 这个接口是拍照。由于拍照需要获得图像数据,所以这里需要实现CaptureCallback,在回调里获得图像数据
// mSession.capture(CaptureRequest request, CaptureCallback listener, Handler handler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void closePreviewSession() {
if (mSession != null) {
try {
mSession.stopRepeating();
mSession.abortCaptures();
mSession.close();
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
private void startRecord() {
// 设置Recorder配置,启动录像会话
int sensorOrientation = mCameraHelper.getSensorOrientation(mCameraHelper.getBackCameraId());
int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
mRecorderHelper.configRecorder(sensorOrientation, displayRotation);
startRecordSession();
}
private void stopRecord() {
// 关闭录像会话,停止录像,重新进入预览
mRecorderHelper.stop();
startPreviewSession();
}
// TextureView状态监听
private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
mTextureHelper.configPreview(mTextureView, width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
}
接下来是三个helper类的代码,首先是TextureHelper类:
public class TextureHelper {
private Activity mActivity;
// 这里只是展示用法,实际开发中需要根据摄像头的支持size来取
private Size mPreviewSize = new Size(960, 720);
public TextureHelper(Activity activity) {
mActivity = activity;
}
// 配置预览图的大小、方向/角度
public void configPreview(TextureView textureView, int targetWidth, int targetHeight) {
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, targetWidth, targetHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max((float) targetHeight / mPreviewSize.getHeight(), (float) targetWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
textureView.setTransform(matrix);
}
// 根据TextureView和预览size,获取surface
public Surface getSurface(TextureView textureView) {
SurfaceTexture texture = textureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(960, 720);
return new Surface(texture);
}
}
然后是CameraHelper类:
public class CameraHelper {
private CameraManager mManager;
private String mBackCameraId;
public CameraHelper(Context context) {
mManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
}
public String getBackCameraId() {
if(!TextUtils.isEmpty(mBackCameraId)){
return mBackCameraId;
}
try {
String[] ids = mManager.getCameraIdList();
for (String cameraId : ids) {
CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
// 根据摄像头的朝向判断是否是后置摄像头
if (null != facing && CameraMetadata.LENS_FACING_BACK == facing) {
mBackCameraId = cameraId;
return cameraId;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
return "";
}
public int getSensorOrientation(String cameraId) {
try {
CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
return characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
} catch (CameraAccessException e) {
e.printStackTrace();
}
return -1;
}
public Size[] getSupportSize(String cameraId, Class klass) {
try {
CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (null != map) {
return map.getOutputSizes(klass);
// map.getOutputSizes(MediaRecorder.class);// 支持的录像视频size
// map.getOutputSizes(MediaRecorder.class);// 支持的录像视频size
// map.getOutputSizes(ImageReader.class);// 支持的拍照照片size
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
return null;
}
// 注意,camera、recorder权限都是隐私权限,6.0以后需要动态权限配置
@SuppressLint("MissingPermission")
public void openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler) {
try {
mManager.openCamera(cameraId,callback,handler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
最后是RecorderHelper类:
public class RecorderHelper {
private Context mContext;
private MediaRecorder mRecorder;
private String mPath;
private boolean hasPrepared;
private static final int SENSOR_DEFAULT_DEGREES = 90;
private static final int SENSOR_INVERSE_DEGREES = 270;
private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
static {
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
static {
INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
}
public RecorderHelper(Context context) {
mContext = context;
}
private void initIfNecessary() {
if (null == mRecorder) {
mRecorder = new MediaRecorder();
}
}
private void updatePath() {
final File dir = mContext.getExternalFilesDir(null);
if (null != dir) {
mPath = dir.getAbsolutePath() + "/" + System.currentTimeMillis() + ".mp4";
}
}
public void configRecorder(int sensorOrientation, int displayRotation) {
initIfNecessary();
// 设置存储路径
updatePath();
if (TextUtils.isEmpty(mPath)) {
return;
}
mRecorder.setOutputFile(mPath);
// 设置音、视频采集源
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
// 设置音、视频编码格式,以及文件封装格式。设置顺序必须跟下面一模一样,否则报错
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 设置比特率、帧率和分辨率
mRecorder.setVideoEncodingBitRate(800 * 800);
mRecorder.setVideoFrameRate(30);
// 这里只是展示用法,实际开发中需要根据摄像头的支持size来取
mRecorder.setVideoSize(960, 720);
// 根据camera方向和屏幕角度,设置录制视频的角度补偿
if (SENSOR_DEFAULT_DEGREES == sensorOrientation) {
mRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(displayRotation));
} else if (SENSOR_INVERSE_DEGREES == sensorOrientation) {
mRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(displayRotation));
}
try {
mRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
hasPrepared = true;
}
public void start() {
if (hasPrepared) {
mRecorder.start();
}
}
// 停止之后,MediaRecorder不需要置空,下次使用时需要重新配置
public void stop() {
if (hasPrepared) {
mRecorder.stop();
mRecorder.reset();
hasPrepared = false;
}
}
public void release() {
if (null != mRecorder) {
mRecorder.release();
hasPrepared = false;
mRecorder = null;
}
}
public Surface getSurface() {
return hasPrepared ? mRecorder.getSurface() : null;
}
public boolean isRecording() {
return hasPrepared;
}
}