大家好,又见面了。在前几篇 中,我们通过2种方式实现了仿抖音的翻页切换视频,仿抖音列表播放视频功能 ,这一篇,我们来说说视频的录制。
主流的视频录制,一般都采用的是FFmpeg 例如 腾讯短视频,由于FFmpeg的学习成本较大,这里我们就说说系统自带的MediaRecorder。
首先,需要实现摄像头的预览,这里我们就用SurfaceView 。
< 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 ( ) ;
}
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) ) {
startPreview ( ) ;
} else {
EasyPermissions. requestPermissions ( this , "我们的app需要以下权限" ,
RC_STORAGE, perms) ;
}
}
需要实现EasyPermissions.PermissionCallbacks,以及处理授权回调
@Override
public void onRequestPermissionsResult ( int requestCode, String[ ] permissions, int [ ] grantResults) {
super . onRequestPermissionsResult ( requestCode, permissions, grantResults) ;
EasyPermissions. onRequestPermissionsResult ( requestCode, permissions, grantResults, this ) ;
}
@Override
public void onPermissionsGranted ( int requestCode, @NonNull List< String> perms) {
startPreview ( ) ;
}
@Override
public void onPermissionsDenied ( int requestCode, @NonNull List< String> perms) {
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 ) ;
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 ( ) ;
}
}
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 ( "停止录制" ) ;
}