【Android 進階】仿抖音系列之視訊預覽和錄製(五)
阿新 • • 發佈:2018-11-17
大家好,又見面了。在前幾篇中,我們通過2種方式實現了仿抖音的翻頁切換視訊,仿抖音列表播放視訊功能,這一篇,我們來說說視訊的錄製。
- 【Android 進階】仿抖音系列之翻頁上下滑切換視訊(一)
- 【Android 進階】仿抖音系列之列表播放視訊(二)
- 【Android 進階】仿抖音系列之列表播放視訊(三)
- 【Android 進階】仿抖音系列之翻頁上下滑切換視訊(四)
- 【Android 進階】仿抖音系列之視訊預覽和錄製(五)
主流的視訊錄製,一般都採用的是FFmpeg 例如 騰訊短視訊,由於FFmpeg的學習成本較大,這裡我們就說說系統自帶的MediaRecorder。
首先,需要實現攝像頭的預覽,這裡我們就用SurfaceView 。
- 1.在佈局中引入
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android: layout_height="match_parent"
tools:context=".activity.RecordActivity">
<SurfaceView
android:id="@+id/sv_record"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btn_start"
android:layout_width ="wrap_content"
android:layout_height="wrap_content"
android:text="start"
app:layout_constraintBottom_toBottomOf="parent" />
<Button
android:id="@+id/btn_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="switch"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
-
- 實現SurfaceHolder.Callback,重寫surfaceCreated、surfaceChanged、surfaceDestroyed 3個方法
其中surfaceCreated是SurfaceView建立成功時回撥,可以在這裡開始預覽;surfaceChanged是SurfaceView變化時回撥,這裡不做處理;surfaceDestroyed 是SurfaceView銷燬時回撥,可以在這裡釋放資源
surfaceHolder = svRecord.getHolder();
surfaceHolder.addCallback(this);
//設定一些引數方便後面繪圖
svRecord.setFocusable(true);
svRecord.setKeepScreenOn(true);
svRecord.setFocusableInTouchMode(true);
@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceHolder = holder;
requestPermision();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
surfaceHolder = holder;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//停止預覽並釋放攝像頭資源
stopPreview();
//停止錄製
startRecord();
}
-
- 開始預覽,首先是請求許可權,這裡使用的是easypermissions,也可以使用其他的封裝庫
private void requestPermision() {
String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
if (EasyPermissions.hasPermissions(this, perms)) {
// Already have permission, do the thing
startPreview();
// ...
} else {
// Do not have permissions, request them now
EasyPermissions.requestPermissions(this, "我們的app需要以下許可權",
RC_STORAGE, perms);
}
}
需要實現EasyPermissions.PermissionCallbacks,以及處理授權回撥
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
// Some permissions have been granted
startPreview();
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
// Some permissions have been denied
finish();
}
/**
* 開始預覽
*/
private void startPreview() {
if (svRecord == null || surfaceHolder == null) {
return;
}
if (camera == null) {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
currentCameraType = 1;
btnSwitch.setText("後");
}
try {
camera.setPreviewDisplay(surfaceHolder);
Camera.Parameters parameters = camera.getParameters();
camera.setDisplayOrientation(90);
//實現Camera自動對焦
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes != null) {
for (String mode : focusModes) {
mode.contains("continuous-video");
parameters.setFocusMode("continuous-video");
}
}
List<Camera.Size> sizes = parameters.getSupportedVideoSizes();
if (sizes.size() > 0) {
size = sizes.get(sizes.size() - 1);
}
camera.setParameters(parameters);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
其中,Camera.CameraInfo.CAMERA_FACING_BACK 代表後置攝像頭,保險起見,應該檢查裝置是否有後置攝像頭,這裡就不檢查了;還需要注意,當時後置時,應該旋轉攝像頭90度,否則預覽是斜的
-
- 切換攝像頭,這裡如上程式碼所見,使用了一個int 型變數currentCameraType來記錄前後攝像頭;
stopPreview();
if (currentCameraType == 1) {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
currentCameraType = 2;
btnSwitch.setText("前");
} else {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
currentCameraType = 1;
btnSwitch.setText("後");
}
startPreview();
/**
* 停止預覽
*/
private void stopPreview() {
//停止預覽並釋放攝像頭資源
if (camera == null) {
return;
}
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera = null;
}
需要注意,需要先停止預覽,切換攝像頭之後,再開始預覽;
到這裡攝像頭已經實現了攝像頭預覽。
開始錄製視訊
/**
* 開始錄製
*/
private void startRecord() {
if (mediaRecorder == null) {
mediaRecorder = new MediaRecorder();
}
temFile = getTemFile();
try {
camera.unlock();
mediaRecorder.setCamera(camera);
//從相機採集視訊
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 從麥克採集音訊資訊
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//編碼格式
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setVideoSize(size.width, size.height);
//每秒的幀數
mediaRecorder.setVideoFrameRate(24);
// 設定幀頻率,然後就清晰了
mediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);
mediaRecorder.setOutputFile(temFile.getAbsolutePath());
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
//解決錄製視訊, 播放器橫向問題
if (currentCameraType == 1) {
//後置
mediaRecorder.setOrientationHint(90);
} else {
//前置
mediaRecorder.setOrientationHint(270);
}
mediaRecorder.prepare();
//正式錄製
mediaRecorder.start();
isRecording = true;
showtoast("開始錄製");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取臨時檔案目錄
*
* @return
*/
private File getTemFile() {
String basePath = Environment.getExternalStorageDirectory().getPath() + "/doudemo/";
File baseFile = new File(basePath);
if (!baseFile.exists()) {
baseFile.mkdirs();
}
File temp = new File(basePath + System.currentTimeMillis() + ".mp4");
return temp;
}
這裡的坑還是比較多的
- 1.首先需要解鎖相機,呼叫camera.unlock();
- 2.關於視訊的size ,應該通過parameters.getSupportedVideoSizes(); 獲取該手機支援的寬高,如果設定手機不支援,會報錯;
- 3.注意各個方法呼叫順序,否則會報一些奇怪的錯,無奈…
- 4.攝像機角度問題,後置時,旋轉90度,前置時,旋轉270度
停止錄製,需要鎖定相機,需要預覽時,跳到預覽(播放)介面
/**
* 停止錄製
*/
private void stopRecord(boolean delete) {
if (mediaRecorder == null) {
return;
}
if (myTimer != null) {
myTimer.cancel();
}
try {
mediaRecorder.stop();
} catch (Exception e) {
e.printStackTrace();
}
mediaRecorder.reset();
mediaRecorder.release();
mediaRecorder = null;
if (camera != null) {
camera.lock();
}
isRecording = false;
if (delete) {
if (temFile != null && temFile.exists()) {
temFile.delete();
}
} else {
//停止預覽
stopPreview();
Intent intent = new Intent(RecordActivity.this, PrepareActivity.class);
intent.putExtra(PrepareActivity.VIDEO_PATH, temFile.getPath());
startActivity(intent);
}
showtoast("停止錄製");
}