前言
Android5.0之前使用android.hardware包下的Camera類進行拍照、錄視頻等功能。5.0以後,新增了android.hardware.camera2包,利用新的機制、新的類進行拍照、錄視頻。
使用Camera
一、拍照
由於手機攝像頭配置不同、攝像頭擺放方向不同、位置不同等等因素,與攝像頭相關參數如:攝像頭個數、支持的像素、畫面預覽方向、閃光燈、對焦方式、幀率等等都不一樣,必須根據當前手機的配置動態獲取。獲取方法如下:
Camera.Parameters p = mCamera.getParameters();
List<Camera.Size> preSizes = p.getSupportedPreviewSizes();
List<Camera.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<Camera.Size> 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網上已經有很多介紹了,我寫了一個使用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<Surface> 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;
}
}