系统录制
Intent intent=new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY,0);
intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT,10485760L);
//android 5.0 无效
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,10);
startActivityForResult(intent,VIDEO_CAPTURE);
本篇介绍MediaRecorder 和 SurfaceView 录制方法
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.Observer;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.hardware.Camera;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class MainActivity extends Activity implements SurfaceHolder.Callback {
private SurfaceView surfaceView;
private ImageView recorderBtn;
private ImageView recorder_btn_car;
private ImageView recorder_btn_s;
private ImageView iv_confirm;
private ImageView iv_relase;
private VideoFullScreen video_view;
private RelativeLayout rl_bottom;
private TextView tv_time;
private MediaRecorder recorder;
private boolean isStart = false;
private boolean recorded = false;
private Camera camera;
private SurfaceHolder holder;
private String path;
private Camera.Size size;
private int facingBack = Camera.CameraInfo.CAMERA_FACING_BACK;
private boolean isBack = true;
private boolean isSource = true;
private String mCurrentPath;
private int mWidth = 1280;
private int mHeight = 720;
private static final int REQUEST_PERMISSION = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏
//全屏(默认有个电量栏)
// getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// getWindow().setFormat((PixelFormat.TRANSLUCENT));
setContentView(R.layout.activity_main);
init();
Display display = getWindowManager().getDefaultDisplay();
mWidth = display.getHeight();
mHeight= display.getWidth();
// DisplayMetrics dm = getResources().getDisplayMetrics();
// prevHeight = dm.widthPixels;
// prevWidth = prevHeight * mWidth/mHeight;
recorderBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isStart) {
//未开始,点击开始
startTimer(16);
recorderBtn.setSelected(true);
recorder_btn_car.setVisibility(View.GONE);
recorder_btn_s.setVisibility(View.GONE);
startRecorder();
isStart = true;
} else {
recorderBtn.setSelected(false);
recorder_btn_car.setVisibility(View.VISIBLE);
recorder_btn_s.setVisibility(View.GONE);
if (mDisposable !=null) {
mDisposable.cancel();
mDisposable = null;
}
//已开始,点击停止
stopRecorder();
startVideo();
isStart = false;
}
}
});
recorder_btn_car.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
isBack = !isBack;
if (isBack) {
facingBack = Camera.CameraInfo.CAMERA_FACING_BACK;
}else {
facingBack = Camera.CameraInfo.CAMERA_FACING_FRONT;
}
releaseCamera();
initPreview();
// stopRecorder();
// startRecorder();
}
});
recorder_btn_s.setSelected(true);
recorder_btn_s.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
isSource = !isSource;
if (isSource) {
recorder_btn_s.setSelected(true);
}else {
recorder_btn_s.setSelected(false);
}
}
});
iv_relase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
video_view.stopPlayback();
video_view.setVisibility(View.GONE);
recorder_btn_car.setVisibility(View.VISIBLE);
recorder_btn_s.setVisibility(View.GONE);
recorderBtn.setSelected(false);
isStart = false;
recorderBtn.setVisibility(View.VISIBLE);
rl_bottom.setVisibility(View.GONE);
updataTimeFormat(tv_time,0);
initPreview();
}
});
iv_confirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.putExtra("data",mCurrentPath);
setResult(-1,intent);
finish();
}
});
}
private void init() {
surfaceView = findViewById(R.id.surface_view);
recorderBtn = findViewById(R.id.recorder_btn_container);
recorder_btn_car = findViewById(R.id.recorder_btn_car);
recorder_btn_s = findViewById(R.id.recorder_btn_s);
iv_confirm = findViewById(R.id.iv_confirm);
iv_relase = findViewById(R.id.iv_relase);
tv_time = findViewById(R.id.tv_time);
rl_bottom = findViewById(R.id.rl_bottom);
video_view = findViewById(R.id.video_view);
holder = surfaceView.getHolder();
// holder.setSizeFromLayout();
holder.setFixedSize(640,480);
holder.addCallback(this);//将holder加入回调接口
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/**
* 判断权限
*/
public void permissionRead(){
boolean permission = isPermission(Manifest.permission.CAMERA);
boolean permissionRead = isPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
boolean permissionWrite = isPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
boolean permissionAudio = isPermission(Manifest.permission.RECORD_AUDIO);
if (!permissionRead){
initPermission(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE});
}else if(!permissionWrite) {
initPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE});
}else if(!permission) {
initPermission(new String[]{Manifest.permission.CAMERA});
}else if (!permissionAudio){
initPermission(new String[]{Manifest.permission.RECORD_AUDIO});
}else {
initPreview();
}
}
/**
* 初始化权限
*/
public void initPermission(String[] permissionArr) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
boolean isGranted = true;
for (String permission : permissionArr) {
int result = ActivityCompat.checkSelfPermission(this, permission);
if (result != PackageManager.PERMISSION_GRANTED) {
isGranted = false;
break;
}
}
if (!isGranted) {
// 还没有的话,去申请权限
ActivityCompat.requestPermissions(this, permissionArr, REQUEST_PERMISSION);
}
}
}
/**
* 判断是否有权限
*/
public boolean isPermission(String permission) {
boolean isGranted = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int result = ActivityCompat.checkSelfPermission(this, permission);
if (result != PackageManager.PERMISSION_GRANTED) {
isGranted = false;
}
}
return isGranted;
}
private void openCamera(int in) {
Log.e("VideoRecorderActivity", "openCamera()");
camera = Camera.open(in);
if (camera != null) {
setCameraParameters();
camera.unlock();
}
}
private void setCameraParameters() {
if (camera == null) {
return;
}
Camera.Parameters parameters = camera.getParameters();
camera.setDisplayOrientation(90);
/**
* 增加对聚焦模式的判断,没有它会很模糊
*/
List<String> focusModesList = parameters.getSupportedFocusModes();
if (focusModesList.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
} else if (focusModesList.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
parameters.setRecordingHint(true);
camera.setParameters(parameters);
if (size == null) {
//所有支持的宽高的集合
List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
//从小到大排序
Collections.sort(mSupportedPreviewSizes, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size o1, Camera.Size o2) {
if (o1.width == o2.width) {
return 0;
} else if (o1.width > o2.width) {
return 1;
} else {
return -1;
}
}
});
//因为尺寸太大也不好,容量也就太大,所以只要满足要求就行,要求是宽大于1000或者高大于800的最小的一个
for (int num = 0; num < mSupportedPreviewSizes.size(); num++) {
Camera.Size size1 = mSupportedPreviewSizes.get(num);
if (size1.width >= 1000 && size1.height >= 800) {
size = size1;
break;
}
}
}
if (facingBack == Camera.CameraInfo.CAMERA_FACING_FRONT) {
Point point = CameraPreviewUtils.getBestPreview(parameters, new Point(mWidth, mHeight));
parameters.setPreviewSize(point.x, point.y);
camera.setParameters(parameters);
}else {
parameters.setPreviewSize(640, 480);
}
}
/**
* 释放摄像头资源
*/
private void releaseCamera() {
Log.e("VideoRecorderActivity", "releaseCamera()");
if (camera != null) {
camera.setPreviewCallback(null);
camera.stopPreview();
camera.lock();
camera.release();
camera = null;
}
}
private void startRecorder() {
Log.e("VideoRecorderActivity", "initRecorder()");
if (recorder == null) {
recorder = new MediaRecorder();
}
recorderBtn.setVisibility(View.VISIBLE);
openCamera(facingBack);
// camera.cancelAutoFocus();
recorder.setCamera(camera);
recorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
@Override
public void onError(MediaRecorder mr, int what, int extra) {
Log.e("VideoRecorderActivity","onError what = " +what +",extra = "+ extra );
stopRecorder();
}
});
recorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
@Override
public void onInfo(MediaRecorder mediaRecorder, int i, int i1) {
}
});
//设置音视频源, 这两项需要放在setOutputFormat之前
if (isSource) {
recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);//音频源
}
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//视频源
//设置格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置编码,这两项需要放在setOutputFormat之后
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
if (isSource) {
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
}
/**
* 这里直译作编码比特率,一般叫视频码率又叫视频比特率,也叫码流率:是指视频文件在单位时间内使用的数据流量。码率越大,
* 说明单位时间内取样率越大,数据流精度就越高,这样表现出来的的效果就是:视频画面更清晰画质更高。
*
*/
recorder.setVideoEncodingBitRate(2* 1024 * 1024);
/**
* 视频帧率:通常说一个视频的25帧,指的就是这个视频帧率,即1秒中会显示25帧;
* 视频帧率影响的是画面流畅感,也就是说视频帧率超高,表现出来的效果就是:画面越显得流畅。
* 你也可以这样理解,假设1秒只显1帧,那么一段视频看起来,就是有很明显的卡顿感,不流畅不连惯。
* 当然视频帧率越高,意味着画面越多,也就相应的,这个视频文件的大小也会随之增加,
* 占用存储空间也就增大了.一般25或30就可以
*/
recorder.setVideoFrameRate(30);
// if (isBack) {
recorder.setOrientationHint(displayOrientation(this));
// } else {
// recorder.setOrientationHint(270);
// }
if (facingBack == Camera.CameraInfo.CAMERA_FACING_BACK) {
recorder.setOrientationHint(displayOrientation(this));
}else {
recorder.setOrientationHint(360 - displayOrientation(this));
}
recorder.setMaxDuration(15*1000);//设置最大持续时间
/**
* 视频分辨率:分辨率就是我们常说的600x400分辨率、1920x1080分辨率,分辨率影响视频图像的大小,
* 与视频图像大小成正比:视频分辨率越高,图像越大,越清晰,对应的视频文件本身大小也会越大。
*/
// recorder.setVideoSize( 640,480);//市面上百分之99的手机支持这个视频尺寸.也可以像下面这样生成想要的尺寸
Log.d("生成的分辨率:","width="+size.width+" height="+size.height);
// if (size != null) recorder.setVideoSize(size.width, size.height);
Display display = getWindowManager().getDefaultDisplay();
int Height = display.getHeight();
int Width = display.getWidth();
// recorder.setVideoSize( prevWidth,prevHeight);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
recorder.setVideoSize(1920, 1080);
}else {
recorder.setVideoSize( Height,Width);
// if (size != null) recorder.setVideoSize(size.width, size.height);
}
//这句话必须有,而且参数必须这么写,通过surfaceView得到,不然报错或者不显示拍摄内容
recorder.setPreviewDisplay(surfaceView.getHolder().getSurface());
try {
mCurrentPath = createRecordPath();
recorder.setOutputFile(mCurrentPath);
recorder.prepare();
recorder.start();
} catch (Exception e) {
e.printStackTrace();
Log.e("VideoRecorderActivity", "recorder.prepare()异常" );
stopRecorder();
}
}
// private void
private int displayOrientation(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
int rotation = windowManager.getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
default:
degrees = 0;
break;
}
int result = (0 - degrees + 360) % 360;
if (APIUtils.hasGingerbread()) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(facingBack, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (info.orientation - degrees + 360) % 360;
}
}
return result;
}
private void stopRecorder() {
if (recorder != null) {
//这里录像停止如果recorder.setPreviewDisplay(null);那么结束后的预览就是摄像机的内容,
//不停止就是录制最后一帧的画面
// recorder.setPreviewDisplay(null);
// recorder.setOnErrorListener(null);
recorder.stop();
recorder.reset();
recorder.release();
recorder = null;
recorded = true;
// choose.setVisibility(View.VISIBLE);
recorderBtn.setVisibility(View.GONE);
rl_bottom.setVisibility(View.VISIBLE);
}
releaseCamera();
}
/**
* 预览功能就是当surface建立的时候,调用这个方法,在surfaceView里预览相机拍摄内容
* surfaceView可见时,surface被创建;surfaceview隐藏前,surface被销毁。这样能节省资源。
* 如果你要查看 surface被创建和销毁的时机,可以重载surfaceCreated(SurfaceHolder)
* 和 surfaceDestroyed(SurfaceHolder)。
*
* @param holder
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.e("VideoRecorderActivity", "surfaceCreated");
permissionRead();
}
/**
* 重置预览
*/
private void initPreview(){
if (camera == null) {
camera = Camera.open(facingBack);
}
try {
setCameraParameters();
camera.setPreviewDisplay(holder);
camera.startPreview();//开始预览
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "打开相机失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.e("VideoRecorderActivity", "surfaceChanged width =" + width);
Log.e("VideoRecorderActivity", "surfaceChanged height = " + height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.e("VideoRecorderActivity", "surfaceDestroyed");
releaseCamera();
}
private String createRecordPath() throws IOException {
File sdDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
if (sdDir != null) {
File dir = new File(sdDir.getAbsolutePath());//新建子目录
if (!dir.exists()) {
dir.mkdirs();
}
//视频文件的路径
String path = dir.getAbsolutePath() + "/" + System.currentTimeMillis() + ".mp4";
return path;
}
return null;
}
private Timer mDisposable = null;
private int number = 0;
private Handler handler = null;
public void startTimer(int second){
if (mDisposable !=null) {
return;
}
if (handler == null) {
handler = new Handler(getMainLooper());
}
mDisposable = new Timer();
mDisposable.schedule(new TimerTask() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
number ++;
if (number >= second) {
updataTimeFormat(tv_time,Math.round(number * 1000));
stopRecorder();
startVideo();
}else {
updataTimeFormat(tv_time,Math.round(number * 1000));
}
}
});
}
},1000,1000);
}
private void startVideo(){
if (video_view.isPlaying())
return;
video_view.setVisibility(View.VISIBLE);
video_view.setVideoPath(mCurrentPath);
video_view.start();
//或者
video_view.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
video_view.setVideoPath(mCurrentPath);
//或 //mVideoView.setVideoPath(Uri.parse(_filePath));
video_view.start();
}
});
}
/**
* 时间格式化
*
* @param textView 时间控件
* @param millisecond 总时间 毫秒
*/
private void updataTimeFormat(TextView textView, int millisecond) {
//将毫秒转换为秒
int second = millisecond / 1000;
//计算小时
int hh = second / 3600;
//计算分钟
int mm = second % 3600 / 60;
//计算秒
int ss = second % 60;
//判断时间单位的位数
String str = null;
if (hh != 0) {//表示时间单位为三位
str = String.format("%02d:%02d:%02d", hh, mm, ss);
} else {
str = String.format("%02d:%02d", mm, ss);
}
//将时间赋值给控件
textView.setText(str);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
permissionRead();
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></SurfaceView>
</FrameLayout>
<TextView
android:id="@+id/tv_time"
android:text="0:00"
android:padding="@dimen/dp_10"
android:textSize="18dp"
android:layout_marginTop="@dimen/dp_10"
android:textColor="@color/white"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></TextView>
<ImageView
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:background="@drawable/collect_play_selector"
android:src="@drawable/collect_play_selector"
android:id="@+id/recorder_btn_container"
android:layout_width="80dp"
android:layout_height="80dp"></ImageView>
<ImageView
android:text="声音"
android:visibility="gone"
android:layout_marginTop="@dimen/dp_10"
android:src="@drawable/collect_image_voice_selector"
android:layout_toLeftOf="@+id/recorder_btn_car"
android:id="@+id/recorder_btn_s"
android:layout_marginRight="10dp"
android:padding="@dimen/dp_10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"> </ImageView>
<ImageView
android:text="切换"
android:padding="@dimen/dp_10"
android:src="@drawable/collect_image_ca_selector"
android:layout_alignParentRight="true"
android:layout_marginTop="@dimen/dp_10"
android:id="@+id/recorder_btn_car"
android:layout_marginRight="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"> </ImageView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<com.hdketang.camera.VideoFullScreen
android:id="@+id/video_view"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.hdketang.camera.VideoFullScreen>
<RelativeLayout
android:id="@+id/rl_bottom"
android:visibility="gone"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_marginBottom="30dp"
android:paddingLeft="@dimen/dp_20"
android:paddingRight="@dimen/dp_20"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_relase"
android:layout_alignParentBottom="true"
android:src="@mipmap/ic_relese"
android:layout_width="40dp"
android:layout_height="wrap_content">
</ImageView>
<ImageView
android:id="@+id/iv_confirm"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:src="@mipmap/ic_config"
android:layout_width="40dp"
android:layout_height="wrap_content">
</ImageView>
</RelativeLayout>
</RelativeLayout>