Android (系统+自定义)短视频录制(含暂停继续录制功能) 总结

前言

在Android开发中自然少不了对视频录制的需求,然而视频录制虽然有系统提供给我们能够直接使用的API,但是我们往往在完成需求的过程中需要自定义实现短视频录制。网上虽然也有不少资料,但是总是零零碎碎的,因此自己收集了多方面的资料自己写了一个demo来理解和学习短视频录制,记录在此同时也希望能够给大家带来一些参考和启发。


一、调用系统相机的视频录制

首先我们来了解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" />

因为这里我们主要的关注点在相机的使用上,关于Android6.0的权限管理这里就不做多的插入了,有需要的朋友可以到这儿学习学习啦


好的,回到主题,我们调用视频相机录制的核心代码如下

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;
        }
    }

这里是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>
  • 多说一点,因为国内某些手机对相机做了系统上的修改,可能通过我们传递的fileUri无法保存到相对应的位置,因此我们可以在onActivityResult的回调数据中通过下面的方法自行保存视频
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以内,另外我们也可以加入视频录制的暂停和继续,同时可以更加需求做出各种各样的自定义功能。噼里啪啦说了这么多好处,那么就开始我们的自定义视频录制之旅吧!


二、自定义视频录制

因为我们在这里会使用到自定义相机的某些硬件特性,因此我们还需要在AndroidManifest.xml中加入如下特征(之前的系统相机所需要的权限这里同样需要),保证一些功能的正常使用。

   <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />
   <uses-feature android:name="android.hardware.camera.autofocus" />

自定义视频录制相对系统相机录制而言较为复杂,在正式使用之前我们先来了解这个类 MediaRecorder
点击会链接到官网该类API的介绍,我们可以知道通过MediaRecorder我们就可以实现视频与音频的录制并将其合并为标准的视频格式。
另外如果英语还不错或者想要了解官方资料的,对于自定义音视频录制的实现流程完全可用通过这里:

接下来让我们好好分析下实现流程:
1.首先我们需要实现相机的预览(这里就需要通过Camera+SurfaceView实现自定义相机的显示)
2.监听录像按钮,在点击录像的同时完成对MediaRecorder初始化和参数配置,启动音视频录制。

OK,有了思路就就明白要怎么走了。首先我们需要实现相机的预览

1.实现相机预览

首先我们需要有显示界面相应的布局文件

<?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>   

界面预览图

接下来是实现代码


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;
          }     
    }
}

通过上面的代码即可实现了自定义相机的显示,能够有预览图像了。
相机预览效果
另外,建议我们在AndroidManifest.xml声明的时候,设置screenOrientation为一直竖屏,这样能够防止录像时横竖切换造成的数据丢失(我看到的系统相机也都是固定为竖屏的,什么?你说看到横屏的录像?还不拿来我看看?),设置代码如下:

<activity
     android:name=".my.CustomRecordActivity"
     android:screenOrientation="portrait">
</activity>

2.实现视频录制功能

来吧,将MediaRecord的配置流程按照官网的样子来一波吧!(图片来自Android开发者官网)
Step1
Step2
原文是英文,这里我通过谷歌浏览器将其翻译了方便大家更好的观看
这一个系列标准的步骤中,我们需要着重理解的是第4步的开始录制视频:首先我们必须按照这样的顺序来配置MediaRecorder,what?你问我为什么?没有为什么想搞懂就去看源码去!(反正我知道你不按照套路来你就不行)。
ok,下面是实现的demo代码,相对于官方的demo做了一些优化和调整。

   /**
     * 开始录制视频
     */
    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());

    }

核心代码就这么些啦,感觉重要部分注释已经标注的很清楚了!
有什么建议和问题欢迎大家提出。
源代码下载地址(含视频录制的暂停继续):https://github.com/Gentleman-jun/VideoRecordDemo

  • 17
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 41
    评论
如果您想要实现自定义视频播放进度条和暂停/播放按钮,可以按照以下步骤进行: 1. 在布局文件中添加 VideoView、SeekBar 和暂停/播放按钮: ``` <VideoView android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="wrap_content" /> <SeekBar android:id="@+id/seek_bar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/play_pause_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Pause" /> ``` 2. 在 Activity 中获取 VideoView、SeekBar 和暂停/播放按钮的引用: ``` VideoView videoView = findViewById(R.id.video_view); SeekBar seekBar = findViewById(R.id.seek_bar); Button playPauseButton = findViewById(R.id.play_pause_button); ``` 3. 设置 VideoView 的路径并开始播放: ``` videoView.setVideoPath("path/to/video.mp4"); videoView.start(); ``` 4. 为 VideoView 添加一个 OnPreparedListener,当视频准备好时,设置 SeekBar 的最大值和添加一个定时器来更新 SeekBar 的进度: ``` videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { seekBar.setMax(videoView.getDuration()); final Handler handler = new Handler(); Runnable runnable = new Runnable() { @Override public void run() { seekBar.setProgress(videoView.getCurrentPosition()); handler.postDelayed(this, 1000); } }; handler.postDelayed(runnable, 1000); } }); ``` 5. 为 SeekBar 添加一个 OnSeekBarChangeListener,当拖动 SeekBar 时,改变视频播放的位置: ``` seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { videoView.seekTo(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) {} }); ``` 6. 为暂停/播放按钮添加一个 OnClickListener,当点击按钮时,暂停继续播放视频: ``` playPauseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (videoView.isPlaying()) { videoView.pause(); playPauseButton.setText("Play"); } else { videoView.start(); playPauseButton.setText("Pause"); } } }); ``` 7. 为 VideoView 添加一个 OnCompletionListener,当视频播放完成时,将暂停/播放按钮的文本设置为“Play”: ``` videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { playPauseButton.setText("Play"); } }); ``` 以上就是实现自定义视频播放进度条和暂停/播放按钮的方法。您可以根据您的需求自定义 SeekBar 的样式和暂停/播放按钮的图标。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值