首先我们来了解Andorid系统为我们提供的系统相机的API的实现方式,系统相机相对而言呢好处当然是使用简单,视频清晰啦。
首先记得添加必要的权限
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
好的,回到主题,我们调用视频相机录制的核心代码如下[!--empirenews.page--]
Uri fileUri = Uri.fromFile(getOutputMediaFile());//设置视频录制保存地址的uri Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10); //限制持续时长 startActivityForResult(intent, RECORD_SYSTEM_VIDEO);
简单讲解一下,这个MediaStore.ACTION_VIDEO_CAPTURE 顾名思义就是我们调用系统视频录制的action。开始我们应该设置一个Uri作为我们想要保存视频的路径,如果我们不传递自定义的路径,那么系统就会将录制后的视频保存在默认的路径(一般在DCIM/Camera)下。限制的视频录制时间单位是秒(s),这里我们就是设置成为10s。 好了,下面贴出这一段完整的demo代码
public static final int RECORD_SYSTEM_VIDEO = 1; private VideoView mVideoView; //用来显示播放录制后的视频 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mVideoView = (VideoView) findViewById(R.id.videoView); } /** * 启用系统相机录制 * * @param view */ public void reconverIntent(View view) { Uri fileUri = Uri.fromFile(getOutputMediaFile()); Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10); //限制的录制时长 以秒为单位 //intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY,1); //设置拍摄的质量最小是0,最大是1(建议不要设置中间值,不同手机似乎效果不同。。。) //intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, 1024 * 1024);//限制视频文件大小 以字节为单位 startActivityForResult(intent, RECORD_SYSTEM_VIDEO); } private File getOutputMediaFile() { if(!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()){ Toast.makeText(this, "请检查SDCard!", Toast.LENGTH_SHORT).show(); return null; } File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DCIM), "MyCameraApp"); if (!mediaStorageDir.exists()) { mediaStorageDir.mkdirs(); } // Create a media file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); File mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"); return mediaFile; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) { return; } switch (requestCode) { case RECORD_SYSTEM_VIDEO: mVideoView.setVideoURI(data.getData()); mVideoView.start(); break; } }[!--empirenews.page--]
这里是layout布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.liuzhongjun.cameratest.my.MainActivity"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="reconverIntent" android:text="启动系统视频录制视频" android:textAllCaps="false" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="customVideo" android:text="启动自定义相机录制视频" /> <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>[!--empirenews.page--]
try { AssetFileDescriptor videoAsset = getContentResolver().openAssetFileDescriptor(data.getData(), "r"); FileInputStream fis = videoAsset.createInputStream(); File tmpFile = new File(Environment.getExternalStorageDirectory(),"VideoFile.mp4"); FileOutputStream fos = new FileOutputStream(tmpFile); byte[] buf = new byte[1024]; int len; while ((len = fis.read(buf)) > 0) { fos.write(buf, 0, len); } fis.close(); fos.close(); } catch (IOException io_e) { // TODO: handle error }
OK,简单的使用就完成了,感觉时候还是很简单吧。然而优点虽然是简单好用,缺点就是无法自定义,相机默认录制的视频又过大(曾经用华为mate9亲测录制10s的视频大小就达到了19M多,Mi2s也差不多有10M,不同的手机因为手机分频率的不同就能够有很大的差别),按这样下去用户录制一点视频上传所耗费流量和时间都不少了啊,然而通过我们的自定义视频录制,10s的视频我们能够控制在2M以内,另外我们也可以加入视频录制的暂停和继续,同时可以更加需求做出各种各样的自定义功能。噼里啪啦说了这么多好处,那么就开始我们的自定义视频录制之旅吧![!--empirenews.page--]
因为我们在这里会使用到自定义相机的某些硬件特性,因此我们还需要在AndroidManifest.xml中加入如下特征(之前的系统相机所需要的权限这里同样需要),保证一些功能的正常使用。
<uses-feature android:name="android.hardware.camera" android:required="true" /> <uses-feature android:name="android.hardware.camera.autofocus" />
自定义视频录制相对系统相机录制而言较为复杂,在正式使用之前我们先来了解这个类 点击会链接到官网该类API的介绍,我们可以知道通过MediaRecorder我们就可以实现视频与音频的录制并将其合并为标准的视频格式。 另外如果英语还不错或者想要了解官方资料的,对于自定义音视频录制的实现流程完全可用通过这里:
接下来让我们好好分析下实现流程: 1.首先我们需要实现相机的预览(这里就需要通过Camera+SurfaceView实现自定义相机的显示) 2.监听录像按钮,在点击录像的同时完成对MediaRecorder初始化和参数配置,启动音视频录制。
OK,有了思路就就明白要怎么走了。首先我们需要实现相机的预览
首先我们需要有显示界面相应的布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <SurfaceView android:id="@+id/record_surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="#20ffffff" android:padding="10dp"> <!-- 开始/结束 录制按钮 --> <ImageView android:id="@+id/record_control" android:layout_width="60dp" android:layout_height="60dp" android:layout_centerInParent="true" android:contentDescription="@string/begin" android:onClick="startRecord" android:src="@drawable/recordvideo_start" /> <Chronometer android:id="@+id/record_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:format="%s" /> </RelativeLayout> </RelativeLayout>[!--empirenews.page--]
接下来是实现代码
public class CustomRecordActivity extends AppCompatActivity implements View.OnClickListener { //UI private ImageView mRecordControl; private SurfaceView surfaceView; private SurfaceHolder mSurfaceHolder; private Chronometer mRecordTime; //DATA private boolean isPause; //暂停标识 private boolean isRecording; // 标记,判断当前是否正在录制 private long mRecordCurrentTime = 0; //录制时间间隔 // 存储文件 private File mVecordFile; private Camera mCamera; private MediaRecorder mediaRecorder; private MediaRecorder.OnErrorListener onErrorListener = new MediaRecorder.OnErrorListener() { @Override public void onError(MediaRecorder mediaRecorder, int what, int extra) { try { if (mediaRecorder != null) { mediaRecorder.reset(); } } catch (Exception e) { e.printStackTrace(); } } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_custom); initView(); } private void initView() { surfaceView = (SurfaceView) findViewById(R.id.record_surfaceView); mRecordControl = (ImageView) findViewById(R.id.record_control); mRecordTime = (Chronometer) findViewById(R.id.record_time); mRecordControl.setOnClickListener(this); //配置SurfaceHodler mSurfaceHolder = surfaceView.getHolder(); // 设置Surface不需要维护自己的缓冲区 mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // 设置分辨率 mSurfaceHolder.setFixedSize(320, 280); // 设置该组件不会让屏幕自动关闭 mSurfaceHolder.setKeepScreenOn(true); mSurfaceHolder.addCallback(mCallBack); //相机创建回调接口 } private SurfaceHolder.Callback mCallBack = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { initCamera(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { if (mSurfaceHolder.getSurface() == null) { return; } } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { stopCamera(); } }; /** * 初始化摄像头 * * @author liuzhongjun * @date 2016-3-16 */ private void initCamera() { if (mCamera != null) { stopCamera(); } //默认启动后置摄像头 mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); if (mCamera == null) { Toast.makeText(this, "未能获取到相机!", Toast.LENGTH_SHORT).show(); return; } try { mCamera.setPreviewDisplay(mSurfaceHolder); //配置CameraParams setCameraParams(); //启动相机预览 mCamera.startPreview(); } catch (IOException e) { Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } } /** * 设置摄像头为竖屏 * * @author liuzhongjun * @date 2016-3-16 */ private void setCameraParams() { if (mCamera != null) { Camera.Parameters params = mCamera.getParameters(); //设置相机的很速屏幕 if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) { params.set("orientation", "portrait"); mCamera.setDisplayOrientation(90); } else { params.set("orientation", "landscape"); mCamera.setDisplayOrientation(0); } //设置聚焦模式 params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); //缩短Recording启动时间 params.setRecordingHint(true); //是否支持影像稳定能力,支持则开启 if (params.isVideoStabilizationSupported()) params.setVideoStabilization(true); mCamera.setParameters(params); } } /** * 释放摄像头资源 * * @author liuzhongjun * @date 2016-2-5 */ private void stopCamera() { if (mCamera != null) { mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } } @Override public void onClick(View view) { switch (view.getId()) { case R.id.record_control: if (!isRecording) { //开始录制视频 } else { //停止视频录制 } break; } } }[!--empirenews.page--]
通过上面的代码即可实现了自定义相机的显示,能够有预览图像了。 另外,建议我们在AndroidManifest.xml声明的时候,设置screenOrientation为一直竖屏,这样能够防止录像时横竖切换造成的数据丢失(我看到的系统相机也都是固定为竖屏的,什么?你说看到横屏的录像?还不拿来我看看?),设置代码如下:
<activity android:name=".my.CustomRecordActivity" android:screenOrientation="portrait"> </activity>
/** * 开始录制视频 */ public void startRecord() { boolean creakOk = createRecordDir(); if (!creakOk) { return; } initCamera(); mCamera.unlock(); setConfigRecord(); try { //开始录制 mediaRecorder.prepare(); mediaRecorder.start(); } catch (IOException e) { e.printStackTrace(); } isRecording = true; if (mRecordCurrentTime != 0) { mRecordTime.setBase(SystemClock.elapsedRealtime() - (mRecordCurrentTime - mRecordTime.getBase())); } else { mRecordTime.setBase(SystemClock.elapsedRealtime()); } mRecordTime.start(); } /** * 停止录制视频 */ public void stopRecord() { if (isRecording && mediaRecorder != null) { // 设置后不会崩 mediaRecorder.setOnErrorListener(null); mediaRecorder.setPreviewDisplay(null); //停止录制 mediaRecorder.stop(); mediaRecorder.reset(); //释放资源 mediaRecorder.release(); mediaRecorder = null; mRecordTime.stop(); //设置开始按钮可点击,停止按钮不可点击 mRecordControl.setEnabled(true); mPauseRecord.setEnabled(false); isRecording = false; } } /** * 创建视频文件保存路径 */ private boolean createRecordDir() { if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { Toast.makeText(this, "请查看您的SD卡是否存在!", Toast.LENGTH_SHORT).show(); return false; } File sampleDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Record"); if (!sampleDir.exists()) { sampleDir.mkdirs(); } String recordName = "VID_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".mp4"; mVecordFile = new File(sampleDir, recordName); return true; } /** * 配置MediaRecorder() */ private void setConfigRecord() { mediaRecorder = new MediaRecorder(); mediaRecorder.reset(); mediaRecorder.setCamera(mCamera); mediaRecorder.setOnErrorListener(OnErrorListener); //使用SurfaceView预览 mediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); //1.设置采集声音 mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置采集图像 mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //2.设置视频,音频的输出格式 mp4 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); //3.设置音频的编码格式 mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); //设置图像的编码格式 mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); //设置立体声 // mediaRecorder.setAudioChannels(2); //设置最大录像时间 单位:毫秒 // mediaRecorder.setMaxDuration(60 * 1000); //设置最大录制的大小 单位,字节 // mediaRecorder.setMaxFileSize(1024 * 1024); //音频一秒钟包含多少数据位 CamcorderProfile mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P); mediaRecorder.setAudioEncodingBitRate(44100); if (mProfile.videoBitRate > 2 * 1024 * 1024) mediaRecorder.setVideoEncodingBitRate(2 * 1024 * 1024); else mediaRecorder.setVideoEncodingBitRate(1024 * 1024); mediaRecorder.setVideoFrameRate(mProfile.videoFrameRate); //设置选择角度,顺时针方向,因为默认是逆向90度的,这样图像就是正常显示了,这里设置的是观看保存后的视频的角度 mediaRecorder.setOrientationHint(90); //设置录像的分辨率 mediaRecorder.setVideoSize(352, 288); mediaRecorder.setOutputFile(mVecordFile.getAbsolutePath()); }[!--empirenews.page--]