Android自定义录像并且要获得视频第一帧图片

本人是一个小小码农,期间在CSDN上看了很多大神的文章,得到了很多帮助,避免了很多坑,所以我也想向大家分享一下我的开发的一些心得,给更多的萌新一些启发,避免走弯路,好了,废话不多说,直接进入正题...

先给大家看下布局:

很简单,背景使用SurfaceView,然后一个开始一个结束按钮,





    

    

然后进入代码片段:

这里开头一些findViewById我就不贴了,我习惯用ButterKnife

这是里面需要用到的一些参数:


    private SurfaceHolder mSurfaceHolder;

    private boolean isRecording;// 标记,判断当前是否正在录制
    private long mRecordCurrentTime = 0;//录制时间间隔

    // 存储文件
    private File mVecordFile;
    private Camera mCamera;
    private MediaRecorder mediaRecorder;

首先是录像前准备工作,设置surfaceview跟camera:

private void initSurface(){
        mSurfaceHolder = surfaceView.getHolder();
        // 设置Surface不需要维护自己的缓冲区
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        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();
        }
    };

//初始化摄像头
private void initCamera() {
        mCamera = Camera.open(0);  //①
        mCamera.setDisplayOrientation(90);
        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);

            mCamera.cancelAutoFocus();//此句加上 可自动聚焦 必须加
            Camera.Parameters parameters = mCamera.getParameters();
            //查询摄像头支持的分辨率
            parameters.getSupportedPreviewSizes();
            for (int i = 0; i < parameters.getSupportedPreviewSizes().size(); i++) {
//                Log.i("<><><><>Width", parameters.getSupportedPreviewSizes().get(i).width + "");
//                Log.i("<><><><>Height", parameters.getSupportedPreviewSizes().get(i).height + "");
            }
            //设置分辨率
            parameters.setPreviewSize(1280, 720);
            //设置聚焦模式
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            //缩短Recording启动时间
            parameters.setRecordingHint(true);
            //是否支持影像稳定能力,支持则开启
            if (parameters.isVideoStabilizationSupported())
                parameters.setVideoStabilization(true);
            mCamera.setParameters(parameters);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.i(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

中间的一段for循环是用来查看当前设备支持的分辨率,然后从中选出一个适合的分辨率,之前也有一段代码是自动挑选出合适的分辨率,但是我找不到, 而且我这个项目用的是开发板开发的,所以设备比较破旧,很多东西兼容性不好,一些代码就不适用,好了,录像前的准备工作做好了,接下来就可以开始录像了:

首先就是先创建一个视频文件,并且配置MediaRecorder:

/**
     * 创建视频文件
     *
     * @return
     */
    private boolean createRecordDir() {
        if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            Toast.makeText(this, getString(R.string.SDcardisxists), Toast.LENGTH_SHORT).show();
            return false;
        }

        File sampleDir = new File("/sdcard/myVideo/");
        if (!sampleDir.exists()) {
            sampleDir.mkdirs();
        }
        videoName = "VID_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".mp4";
        mVecordFile = new File(sampleDir, videoName);
        return true;
    }

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

    /**
     * 配置MediaRecorder()
     */
    private void setConfigRecord() {
        mediaRecorder = new MediaRecorder();
        mediaRecorder.reset();
        mediaRecorder.setCamera(mCamera);
        mediaRecorder.setOnErrorListener(onErrorListener);
        //录像角度
        mediaRecorder.setOrientationHint(270);
        //使用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);
        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);
        mediaRecorder.setVideoSize(1280, 720);

        mediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());

    }

接下来是按钮点击开始录像:

**
     * 开始录制
     *
     * @throws IOException
     */
    private void startRecord() throws IOException {
        //这是是判断视频文件有没有创建,如果没有就返回
        boolean creakOk = createRecordDir();
        if (!creakOk) {
            return;
        }
        mCamera.unlock();
        setConfigRecord();
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
        isRecording = true;
        mRecordTime.start();
    }

然后是停止录像并保存视频:

/**
     * 停止录制
     */
    private void stopRecord() {
        if (isRecording && mediaRecorder != null) {

            mediaRecorder.setOnErrorListener(null);
            mediaRecorder.setPreviewDisplay(null);
            mediaRecorder.stop();
            mediaRecorder.reset();
            mediaRecorder.release();
            mediaRecorder = null;
            isRecording = false;
            System.out.println(TAG ,""+ mVecordFile.toString());
        }
    }

最后如果退出的话别忘了关闭摄像头:

/**
     * 关闭摄像头
     */
    private void stopCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

如果你的项目要求你去截取视频的第一帧用来做视频列表:

/**
     * 压缩图片
     *
     * @param image
     * @return
     */
    public static Bitmap compressImage(Bitmap image) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        int options = 90;

        while (baos.toByteArray().length / 1024 > 100) {
            baos.reset();
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);
            options -= 10;
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
        return bitmap;
    }


/**
     * 获取到视频第一帧
     */
    class imageTask extends AsyncTask {

        private int videoTime;

        @Override
        protected String doInBackground(File... files) {
            //创建一个文件夹
            String VideoImagePath = "/sdcard/myVideo/compress/";
            String videoImageName = "VID_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".png";
            File dir = new File(VideoImagePath);
            if (!dir.exists()) {
                dir.mkdir();
            }
            videoImageFile = new File(VideoImagePath, videoImageName);
            MediaMetadataRetriever mmr = new MediaMetadataRetriever();
            mmr.setDataSource(mVecordFile.toString());
            String duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); // 播放时长单位为毫秒
            videoTime = (int) (Integer.parseInt(duration) * 0.001);
            Log.i(TAG, duration);
//        Bitmap bitmap = ImageUtils.compressImage(mmr.getFrameAtTime(), 50, 50);
            Bitmap bitmap = compressImage(mmr.getFrameAtTime());
            try {
                FileOutputStream out = new FileOutputStream(videoImageFile);
                bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
                out.flush();
                out.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return videoImageFile.toString();
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
        }
    }

这里我比较喜欢用AsyncTask,虽然说完全没有必要开这个异步,但是我还是比较喜欢用,因为获得视频第一帧然后去压缩图片这个过程还是挺久的,如果放进异步的话这样用户体验感会好点;

有时候因为一些Android版本或者权限的问题会遇到一些BUG

第一个:Failed to connect to camera service

这是因为 android6.0使用camera.open()时需要在onCreate()里面添加如下代码,否则会报错"Failed to connect to camera service":


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[] {Manifest.permission.CAMERA}, 1);
            }
        }

第二个:setAudioSource failed

这是调用麦克风录音的时候权限问题

第一种方法时在Manifest里面添加权限:


    
    

如果这样还不行的话就要打开手机设置>>>应用管理>>>找到你的应用>>>点开你申请的权限

然后就OK了!

下面是全部代码:

package com.example.twj.test;

import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class Main4Activity extends AppCompatActivity {
    @BindView(R.id.stop_button)
    Button stopButton;
    private String TAG = "--==>>";

    @BindView(R.id.record_surfaceView)
    SurfaceView surfaceView;
    @BindView(R.id.record_control)
    Button recordControl;
    private SurfaceHolder mSurfaceHolder;

    private boolean isRecording;// 标记,判断当前是否正在录制

    // 存储文件
    private File mVecordFile;
    private Camera mCamera;
    private MediaRecorder mediaRecorder;
    private String videoName;
    private File videoImageFile;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main4);
        ButterKnife.bind(this);
        initSurface();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[] {Manifest.permission.CAMERA}, 1);
            }
        }

    }

    @OnClick({R.id.record_control, R.id.stop_button})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            //开始
            case R.id.record_control:
                //这是是判断视频文件有没有创建,如果没有就返回
                boolean creakOk = createRecordDir();
                if (!creakOk) {
                    return;
                }
                mCamera.unlock();
                setConfigRecord();
                try {
                    mediaRecorder.prepare();
                    mediaRecorder.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                isRecording = true;
                break;
                //停止
            case R.id.stop_button:
                stopRecord();
                break;
        }
    }


    private void initSurface() {
        mSurfaceHolder = surfaceView.getHolder();
        // 设置Surface不需要维护自己的缓冲区
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        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();
        }
    };

    //初始化摄像头
    private void initCamera() {
        mCamera = Camera.open(0);  //①
        mCamera.setDisplayOrientation(90);
        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);

            mCamera.cancelAutoFocus();//此句加上 可自动聚焦 必须加
            Camera.Parameters parameters = mCamera.getParameters();
            //查询摄像头支持的分辨率
            parameters.getSupportedPreviewSizes();
            for (int i = 0; i < parameters.getSupportedPreviewSizes().size(); i++) {
                Log.i("<><><><>Width", parameters.getSupportedPreviewSizes().get(i).width + "");
                Log.i("<><><><>Height", parameters.getSupportedPreviewSizes().get(i).height + "");
            }
            //设置分辨率
            parameters.setPreviewSize(1280, 720);
            //设置聚焦模式
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            //缩短Recording启动时间
            parameters.setRecordingHint(true);
            //是否支持影像稳定能力,支持则开启
            if (parameters.isVideoStabilizationSupported())
                parameters.setVideoStabilization(true);
            mCamera.setParameters(parameters);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.i(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    /**
     * 创建视频文件
     *
     * @return
     */
    private boolean createRecordDir() {
        if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            Toast.makeText(this, "SD卡不存在!", Toast.LENGTH_SHORT).show();
            return false;
        }

        File sampleDir = new File("/sdcard/myVideo/");
        if (!sampleDir.exists()) {
            sampleDir.mkdirs();
        }
        videoName = "VID_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".mp4";
        mVecordFile = new File(sampleDir, videoName);
        return true;
    }

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

    /**
     * 配置MediaRecorder()
     */
    private void setConfigRecord() {
        mediaRecorder = new MediaRecorder();
        mediaRecorder.reset();
        mediaRecorder.setCamera(mCamera);
        mediaRecorder.setOnErrorListener(onErrorListener);
        //录像角度
        mediaRecorder.setOrientationHint(270);
        //使用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);
        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);
        mediaRecorder.setVideoSize(1280, 720);

        mediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());

    }

    /**
     * 停止录制
     */
    private void stopRecord() {
        if (isRecording && mediaRecorder != null) {

            mediaRecorder.setOnErrorListener(null);
            mediaRecorder.setPreviewDisplay(null);
            mediaRecorder.stop();
            mediaRecorder.reset();
            mediaRecorder.release();
            mediaRecorder = null;
            isRecording = false;
            Log.i(TAG ,""+ mVecordFile.toString());
            new imageTask().execute(mVecordFile);
        }
    }

    /**
     * 关闭摄像头
     */
    private void stopCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }


    /**
     * 压缩图片
     *
     * @param image
     * @return
     */
    public static Bitmap compressImage(Bitmap image) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        int options = 90;

        while (baos.toByteArray().length / 1024 > 100) {
            baos.reset();
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);
            options -= 10;
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
        return bitmap;
    }


    /**
     * 获取到视频第一帧
     */
    class imageTask extends AsyncTask {


        @Override
        protected String doInBackground(File... files) {
            //创建一个文件夹
            String VideoImagePath = "/sdcard/myVideo/compress/";
            String videoImageName = "VID_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".png";
            File dir = new File(VideoImagePath);
            if (!dir.exists()) {
                dir.mkdir();
            }
            videoImageFile = new File(VideoImagePath, videoImageName);
            MediaMetadataRetriever mmr = new MediaMetadataRetriever();
            mmr.setDataSource(mVecordFile.toString());
            String duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); // 播放时长单位为毫秒
            Log.i(TAG, duration);
//        Bitmap bitmap = ImageUtils.compressImage(mmr.getFrameAtTime(), 50, 50);
            Bitmap bitmap = compressImage(mmr.getFrameAtTime());
            try {
                FileOutputStream out = new FileOutputStream(videoImageFile);
                bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
                out.flush();
                out.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return videoImageFile.toString();
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
        }
    }

}

到这里项目一些基本的录像方面的要求就大致完成了,这是我第一次贴代码,如果有不足还请各位大神指出;

你可能感兴趣的:(Android)