Android[视频] camera 视频录制

由于camera已经被舍弃,建议使用替代类camera2。下篇文章会讲到camera2。


切入主题。


首先是权限,这个比较容易忘记申请。

<!-- 存储 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!--摄像头-->
<uses-permission android:name="android.permission.CAMERA" />
<!--录音-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />





视频录制需要存储权限、照相机权限、录音权限和网络权限。网络权限在这里并不是主要的。不过一般都顺手加上。毕竟网络是最常用的。

照相机权限和录音权限从Android6.0开始需要动态申请,申请方式

Android6.0动态申请权限

当然现在框架有很多,可以选择一种自己喜欢的使用。

权限申请完成后,下面我们有就要自定义一个预览照相机拍摄画面的展示类了。有两个类可以选择 SurfaceView、SurfaceTexture 这里使用SurfaceView。

不管怎么说没有比实现代码实在的东西了。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1.0"
        >

        <SurfaceView
            android:id="@+id/surfaceview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />

        <Button
            android:id="@+id/btn_lamp"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:background="@drawable/btn_lamp_bg"
            android:layout_margin="@dimen/margin_10"
            android:layout_alignParentBottom="true"
            />

        <Button
            android:id="@+id/btn_lens"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:background="@drawable/lens"
            android:layout_margin="@dimen/margin_10"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            />

    </RelativeLayout>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="match_parent"
        android:layout_height="5dp"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:progressDrawable="@drawable/progressbar_color"
        />

</LinearLayout>

package pers.wtt.ui.video.view;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.OrientationEventListener;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;

import com.googlecode.mp4parser.util.Matrix;
import pers.wtt.R;
import pers.wtt.lib_common.utils.LogUtil;
import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Created by 王亭 on 2017/8/3.
 * 视频录制
 */
public class MovieRecorderView extends LinearLayout implements MediaRecorder.OnErrorListener,View.OnClickListener {

    private Context context;
    private Camera.Parameters parameters;
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private ProgressBar mProgressBar;
    private Button btn_lamp;
    private Button btn_lens;

    private MediaRecorder mMediaRecorder;
    private Camera mCamera;
    private Timer mTimer;// 计时器
    private OnRecordFinishListener mOnRecordFinishListener;// 录制完成回调接口  

    private int mWidth;// 视频分辨率宽度
    private int mHeight;// 视频分辨率高度  
    private boolean isOpenCamera;// 是否一开始就打开摄像头  
    private int mRecordMaxTime;// 一次拍摄最长时间  
    private int mTimeCount;// 时间计数  
    private File mVecordFile = null;// 文件
    private String ROATE_MP4 = "";
    private boolean record = false;
    private boolean isLamp = false;
    private int mOrientation = 0;
    private boolean camF = false;

    private Matrix matrix = Matrix.ROTATE_90;
    private OrientationEventListener orientationEventListener;

    public MovieRecorderView(Context context) {
        this(context, null);
    }

    public MovieRecorderView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public MovieRecorderView(final Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;

        mWidth=640;
        mHeight=480;
        isOpenCamera=true;
        mRecordMaxTime=1000;

        LayoutInflater.from(context).inflate(R.layout.moive_recorder_view, this);
        mSurfaceView = (SurfaceView) findViewById(R.id.surfaceview);
        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
        btn_lamp = (Button) findViewById(R.id.btn_lamp);
        btn_lens = (Button) findViewById(R.id.btn_lens);

        btn_lamp.setOnClickListener(this);
        btn_lens.setOnClickListener(this);

        orientationEventListener = new OrientationEventListener(context) {
            @Override
            public void onOrientationChanged(int orientation) {

                if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
                    return;
                }

                //保证只返回四个方向
                int newOrientation = ((orientation + 45) / 90 * 90) % 360;
                if (newOrientation != mOrientation) {
                    mOrientation = newOrientation;
                    btn_lamp.setRotation(-mOrientation);
                    btn_lens.setRotation(-mOrientation);
                    LogUtil.e("方向","mOrientation:"+mOrientation);
                    //返回的mOrientation就是手机方向,为0°、90°、180°和270°中的一个
                }

            }
        };

        if(orientationEventListener.canDetectOrientation()){
            orientationEventListener.enable();
        }

        mProgressBar.setMax(mRecordMaxTime);// 设置进度条最大量
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(new CustomCallBack());
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    private void OpenLightOn()    {
        PackageManager pm= context.getPackageManager();
        FeatureInfo[]  features=pm.getSystemAvailableFeatures();
        for(FeatureInfo f : features)
        {
            if(PackageManager.FEATURE_CAMERA_FLASH.equals(f.name))   //判断设备是否支持闪光灯
            {
                try {
                    initCamera(true, -1);
                    isLamp = true;
                    btn_lamp.setSelected(true);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private void CloseLightOff()   {
        if ( mCamera != null )
        {
            try {
                initCamera(false, -1);
                isLamp = false;
                btn_lamp.setSelected(false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    public boolean isRecord() {
        return record;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_lamp:
                if(isLamp){
                    CloseLightOff();
                }else{
                    OpenLightOn();
                }
                break;
            case R.id.btn_lens:
                if(camF){
                    camF = false;
                }else{
                    camF = true;
                }
                changeCamera();
                break;
        }
    }

    private void changeCamera() {

        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        int cameraCount = Camera.getNumberOfCameras(); // get cameras number

        LogUtil.e("摄像头数量","cameraCount:"+cameraCount + " camF:"+camF);

        for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
            Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo
            LogUtil.e("摄像头数量","摄像头:"+cameraInfo.facing+"  " + cameraInfo.toString());
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置
                if(camF) {
                    try {
                        initCamera(false, camIdx);
                        camF = true;
                        LogUtil.e("摄像头数量", "前置摄像头:" + camIdx);
                        break;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                if(!camF) {
                    try {
                        initCamera(false, camIdx);
                        camF = false;
                        LogUtil.e("摄像头数量", "后置摄像头:" + camIdx);
                        break;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public void init() {
        if (!isOpenCamera) {// 如果未打开摄像头,则打开
            try {
                initCamera(isLamp, -1);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *
     * @author wt
     *
     * @date 2015-2-5 
     */
    private class CustomCallBack implements SurfaceHolder.Callback, Camera.PreviewCallback {

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            if (!isOpenCamera)
                return;
            try {
                initCamera(false, -1);
            } catch (IOException e) {
                // TODO Auto-generated catch block  
                e.printStackTrace();
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (!isOpenCamera)
                return;
            freeCameraResource();
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {

        }
    }

    /**
     * 初始化摄像头 
     *
     * @author lip
     * @date 2015-3-16 
     * @throws IOException
     * @param lamp
     */
    private void initCamera(boolean lamp, int camIdx) throws IOException {
        if (mCamera != null) {
            freeCameraResource();
        }
        try {
            if(camIdx!=-1){
                mCamera = Camera.open(camIdx);
            }else {
                mCamera = Camera.open();
            }
        } catch (Exception e) {
            e.printStackTrace();
            freeCameraResource();
        }
        if (mCamera == null)
            return;

        setCameraParams(lamp);
        mCamera.setDisplayOrientation(90);
        mCamera.setPreviewDisplay(mSurfaceHolder);
        mCamera.startPreview();
        mCamera.unlock();
        isOpenCamera = true;
    }

    /**
     * 设置摄像头为竖屏 
     *
     * @author lip
     * @date 2015-3-16
     * @param lamp
     */
    private void setCameraParams(boolean lamp) {
        if (mCamera != null) {
            parameters = mCamera.getParameters();
            if(lamp)
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            parameters.set("orientation", "portrait");
            parameters.setPreviewSize(mWidth, mHeight);
            mCamera.setParameters(parameters);
        }
    }

    /**
     * 释放摄像头资源 
     *
     * @author wt
     * @date 2015-2-5 
     */
    private void freeCameraResource() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.lock();
            mCamera.release();
            mCamera = null;
            isOpenCamera = false;
        }
    }

    private void createRecordDir() {
        // File sampleDir = new File(Environment.getExternalStorageDirectory() + File.separator + "im/video/");
        File vecordDir = new File(Environment.getExternalStorageDirectory() + File.separator+"RecordVideo/");
        //File sampleDir = new File("/video/");  
        if (!vecordDir.exists()) {
            vecordDir.mkdirs();
        }
        // 创建文件  
        try {
            mVecordFile = File.createTempFile("recording", ".mp4", vecordDir);//mp4格式
            //LogUtilUtils.i(mVecordFile.getAbsolutePath());
            LogUtil.d("Path:",mVecordFile.getAbsolutePath());
        } catch (IOException e) {
        }
    }

    /**
     * 初始化 
     *
     * @author lip
     * @date 2015-3-16 
     * @throws IOException
     */
    private void initRecord() throws IOException {
        try {
            mMediaRecorder = new MediaRecorder();
            mMediaRecorder.reset();
            if (mCamera != null)
                mMediaRecorder.setCamera(mCamera);
            mMediaRecorder.setOnErrorListener(this);
            mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);// 视频源
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 音频源
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);// 视频输出格式
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);// 音频格式
            mMediaRecorder.setVideoSize(mWidth, mHeight);// 设置分辨率:
//            mMediaRecorder.setVideoFrameRate(18);// 帧率
            mMediaRecorder.setVideoEncodingBitRate(5 * 1024 * 512);// 设置帧频率,然后就清晰了
            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);// 视频录制格式
            // mediaRecorder.setMaxDuration(Constant.MAXVEDIOTIME * 1000);
            mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());

            if(camF){

                if(mOrientation==90){
                    matrix = Matrix.ROTATE_180;
                    mMediaRecorder.setOrientationHint(180);// 输出旋转180度,保持竖屏录制
                }else if(mOrientation==180) {
                    matrix = Matrix.ROTATE_90;
                    mMediaRecorder.setOrientationHint(90);// 输出旋转90度,保持竖屏录制
                }else if(mOrientation==270) {
                    matrix = Matrix.ROTATE_0;
                    mMediaRecorder.setOrientationHint(0);// 输出旋转0度,保持竖屏录制
                }else{
                    matrix = Matrix.ROTATE_270;
                    mMediaRecorder.setOrientationHint(270);// 输出旋转270度,保持竖屏录制
                }

            }else {
                if(mOrientation==90){
                    matrix = Matrix.ROTATE_180;
                    mMediaRecorder.setOrientationHint(180);// 输出旋转180度,保持竖屏录制
                }else if(mOrientation==180) {
                    matrix = Matrix.ROTATE_270;
                    mMediaRecorder.setOrientationHint(270);// 输出旋转270度,保持竖屏录制
                }else if(mOrientation==270) {
                    matrix = Matrix.ROTATE_0;
                    mMediaRecorder.setOrientationHint(0);// 输出旋转0度,保持竖屏录制
                }else{
                    matrix = Matrix.ROTATE_90;
                    mMediaRecorder.setOrientationHint(90);// 输出旋转90度,保持竖屏录制
                }
            }

            mMediaRecorder.prepare();

            mMediaRecorder.start();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (RuntimeException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 开始录制视频 
     *
     * @author wt
     * @date 2015-2-5 
    //     * @param fileName
    //     *            视频储存位置
     * @param onRecordFinishListener
     *            达到指定时间之后回调接口 
     */
    public void record(final OnRecordFinishListener onRecordFinishListener) {

        this.mOnRecordFinishListener = onRecordFinishListener;
        createRecordDir();
        try {
            if (!isOpenCamera)// 如果未打开摄像头,则打开  
                initCamera(isLamp, -1);
            initRecord();
            record = true;
            btn_lamp.setEnabled(false);
            btn_lens.setEnabled(false);
            mTimeCount = 0;// 时间计数器重新赋值  
            mTimer = new Timer();
            mTimer.schedule(new TimerTask() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    mTimeCount++;
                    mProgressBar.setProgress(mTimeCount);// 设置进度条  
                    if (mTimeCount == mRecordMaxTime) {// 达到指定时间,停止拍摄  
                        stop();
                        if (mOnRecordFinishListener != null) {
                            Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                            scanIntent.setData(Uri.fromFile(mVecordFile));
                            getContext().sendBroadcast(scanIntent);
                            mOnRecordFinishListener.onRecordFinish(mVecordFile.getAbsolutePath());
                        }
                    }
                }
            }, 0, 10);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            btn_lamp.setEnabled(true);
            btn_lens.setEnabled(true );

            mProgressBar.setProgress(1);// 设置进度条
        }
    };

    /**
     * 停止拍摄 
     *
     * @author wt
     * @date 2015-2-5 
     */
    public void stop() {
        mOrientation = 0;
        record = false;
        stopRecord();
        releaseRecord();
        freeCameraResource();
        handler.sendEmptyMessage(0);
//        ROATE_MP4 = DisplayUtil.rotateVideo(mVecordFile.getAbsolutePath(), Matrix.ROTATE_90);
    }

    /**
     * 停止录制 
     *
     * @author wt
     * @date 2015-2-5 
     */
    public void stopRecord() {
        mProgressBar.setProgress(0);
        if (mTimer != null)
            mTimer.cancel();
        if (mMediaRecorder != null) {
            // 设置后不会崩
            mMediaRecorder.setOnInfoListener(null);
            mMediaRecorder.setOnErrorListener(null);
            mMediaRecorder.setPreviewDisplay(null);
            try {
                mMediaRecorder.stop();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (RuntimeException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 释放资源 
     *
     * @author wt
     * @date 2015-2-5 
     */
    private void releaseRecord() {
        if (mMediaRecorder != null) {
            mMediaRecorder.setOnErrorListener(null);
            try {
                mMediaRecorder.release();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mMediaRecorder = null;
    }

    public int getTimeCount() {
        return mTimeCount;
    }

    /**
     * @return the mVecordFile 
     */
    public String getVideoPath() {
        return mVecordFile.getAbsolutePath();
    }

    /**
     * 录制完成回调接口 
     *
     * @author lip
     *
     * @date 2015-3-16 
     */
    public interface OnRecordFinishListener {
        public void onRecordFinish(String absolutePath);
    }

    @Override
    public void onError(MediaRecorder mr, int what, int extra) {
        try {
            if (mr != null)
                mr.reset();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

有一点需要注意,录制相关设置的顺序会影响程序运行。如果有异常出现,请检查顺序是否和本例相同。



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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >

        <per.wtt.ui.video.view.MovieRecorderView
            android:id="@+id/moive_rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerHorizontal="true"
            android:layout_weight="1.0"
            >

        </pers.wtt.ui.video.view.MovieRecorderView>

        <LinearLayout
            android:id="@+id/record_rl"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:layout_below="@id/moive_rv"
            android:gravity="center_horizontal|center_vertical"
            android:orientation="horizontal"
            >

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1.0"
                ></View>

            <LinearLayout
                android:id="@+id/ll_photo"
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:gravity="center_horizontal"
                android:orientation="vertical"
                android:visibility="invisible"
                >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/margin_5"
                    android:text="相册"
                    android:textColor="#9f9f9e"
                    android:textSize="@dimen/text_12sp"
                    android:visibility="invisible"
                    />

                <ImageView
                    android:id="@+id/iv_cvit_icon"
                    android:layout_width="40dp"
                    android:layout_height="40dp"
                    android:background="@drawable/ic_album"
                    />

                <TextView
                    android:id="@+id/tv_cvit_value"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/text_white"
                    android:textSize="@dimen/text_16sp"
                    android:visibility="gone"
                    />

                <TextView
                    android:id="@+id/tv_cvit_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/margin_5"
                    android:text="相册"
                    android:textColor="#9f9f9e"
                    android:textSize="@dimen/text_12sp"
                    />

            </LinearLayout>

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1.0"
                ></View>

            <Button
                android:id="@+id/start_btn"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:background="@drawable/btn_rec_bg"
                />

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1.0"
                ></View>

            <View
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:background="@drawable/ic_btn_rec"
                android:visibility="invisible"
                />

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1.0"
                ></View>
        </LinearLayout>

    </LinearLayout>

    <ImageView
        android:id="@+id/iv_close"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@mipmap/close_w"
        android:layout_margin="@dimen/margin_10"
        />

</RelativeLayout>

package pers.wtt.ui.video.fragment;

import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import pers.wtt.R;
import pers.wtt.base.BaseFragmentNew;
import pers.wtt.ui.video.activity.VideoCoverActivity;
import pers.wtt.ui.video.view.MovieRecorderView;
import pers.wtt.lib_common.utils.LogUtil;

import java.io.File;

/**
 * Created by 王亭 on 2017/4/13.
 */

public class VideoRECFragment extends BaseFragmentNew implements View.OnClickListener{

    private MovieRecorderView movieRV;
    private Button startBtn;
    private LinearLayout ll_photo;
    private ImageView iv_close;

    public static VideoRECFragment getInstance(String title) {
        VideoRECFragment videoRECFragment = new VideoRECFragment();
        return videoRECFragment;
    }

    @Override
    public int getLayoutID() {
        return R.layout.activity_video_rec;
    }

    @Override
    public void initView() {
        initViews();
        initEvents();
    }

/*    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       *//*set it to be no title*//*
        requestWindowFeature(Window.FEATURE_NO_TITLE);
       *//*set it to be full screen*//*
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_video_rec);
        initViews();
        initEvents();
    }*/

    private void initViews()
    {
        movieRV = (MovieRecorderView) view.findViewById(R.id.moive_rv);
        //录制
        startBtn = (Button)view.findViewById(R.id.start_btn);
        ll_photo = (LinearLayout) view.findViewById(R.id.ll_photo);
        iv_close = (ImageView) view.findViewById(R.id.iv_close);
    }

    private void initEvents()
    {
        //开始录制
        startBtn.setOnClickListener(this);
        ll_photo.setOnClickListener(this);
        iv_close.setOnClickListener(this);

    }

    public boolean isRec(){
        return movieRV.isRecord();
    }

    public void initRec(){
       movieRV.init();
    }

    public void stopRec(){
        LogUtil.e("stopRec");
        if(movieRV.isRecord()) {
            startBtn.setSelected(false);
            movieRV.stop();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        LogUtil.e("onPause");
        if(movieRV.isRecord()) {
            movieRV.stop();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_btn:
                if(!movieRV.isRecord()) {
                    startBtn.setSelected(true);
                    movieRV.record(new MovieRecorderView.OnRecordFinishListener() {
                        @Override
                        public void onRecordFinish(String absolutePath) {
                            Intent intent = new Intent();
                            intent.putExtra("filePath", absolutePath);
                            toTarget(VideoCoverActivity.class, intent, true);
                            getActivity().runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    startBtn.setSelected(false);
                                }
                            });

                        }
                    });

                }else{
                    startBtn.setSelected(false);
                    movieRV.stop();
                    String vecordFile = movieRV.getVideoPath();
                    Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                    scanIntent.setData(Uri.fromFile(new File(vecordFile)));
                    getContext().sendBroadcast(scanIntent);
                    if(vecordFile!=null){
                        Intent intent = new Intent();
                        intent.putExtra("filePath", vecordFile);
                        toTarget(VideoCoverActivity.class, intent, true);
                    }
                }
                break;
            case R.id.ll_photo:
                break;
            case R.id.iv_close:
                showInfoDialog("操作确认", "作品尚未保存,确认要取消吗?", new CallBack() {

                    @Override
                    public void cancel() {

                    }

                    @Override
                    public void confirm() {
                        getActivity().finish();
                    }
                });
                break;
        }
    }

}

视频录制后跳转到获取封面Activity


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    >

    <include layout="@layout/bar_title_back_rt" ></include>

    <ImageView
        android:id="@+id/iv_video_cover"
        android:layout_margin="@dimen/margin_10"
        android:layout_width="320dp"
        android:layout_height="240dp"
        android:layout_weight="1.0"
        android:scaleType="fitXY"
        />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_video_cover"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/margin_10"
        >

    </android.support.v7.widget.RecyclerView>

</LinearLayout>

package pers.wtt.ui.video.activity;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import pers.wtt.R;
import pers.wtt.base.BaseActivity;
import pers.wtt.ui.interfaces.IRecyclerViewItemClick;
import pers.wtt.utils.DisplayUtil;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2017/4/18.
 */

public class VideoCoverActivity extends BaseActivity implements IRecyclerViewItemClick,View.OnClickListener {

    private ImageView iv_back;
    private TextView tv_title;
    private TextView tv_btn_name;

    private ImageView iv_video_cover;
    private RecyclerView rv_video_cover;
    private LinearLayoutManager linearLayoutManager;
    private VideoCoverAdapter videoCoverAdapter;
    private String filePath = "";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_cover);

        iv_back = (ImageView) findViewById(R.id.iv_back);
        tv_title = (TextView) findViewById(R.id.tv_title);
        tv_btn_name = (TextView) findViewById(R.id.tv_btn_name);
        tv_btn_name.setVisibility(View.VISIBLE);
        tv_btn_name.setText(getResources().getString(R.string.next));
        tv_title.setText(getResources().getString(R.string.cover));
        iv_back.setOnClickListener(this);
        tv_btn_name.setOnClickListener(this);

        iv_video_cover = (ImageView) findViewById(R.id.iv_video_cover);
        rv_video_cover = (RecyclerView) findViewById(R.id.rv_video_cover);
        linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
        rv_video_cover.setLayoutManager(linearLayoutManager);
        videoCoverAdapter = new VideoCoverAdapter(this);
        videoCoverAdapter.setItemClickListener(this);
        rv_video_cover.setAdapter(videoCoverAdapter);

        filePath = getIntent().getStringExtra("filePath");
        new Thread(){
            @Override
            public void run() {
                super.run();
                MediaMetadataRetriever mmr = new MediaMetadataRetriever();
                mmr.setDataSource(filePath);
                String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); // 播放时长单位为毫秒
                int duration = 10 * 1000 * 1000;
                int current = duration/10;
                if(!TextUtils.isEmpty(durationStr)){
                    duration = Integer.valueOf(durationStr)*1000;
                    current = duration/10;
                }
                for (int i=0; i<10; i++){

                    try {
//                        final Bitmap bitmap = DisplayUtil.toGrayScale(mmr.getFrameAtTime(i*current, MediaMetadataRetriever.OPTION_CLOSEST));
                        final Bitmap bitmap = mmr.getFrameAtTime(i*current, MediaMetadataRetriever.OPTION_CLOSEST);
                        if(bitmap!=null) {

                            if (i == 0) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (iv_video_cover != null) {
                                            iv_video_cover.setImageBitmap(bitmap);
                                            iv_video_cover.setTag(bitmap);
                                        }
                                    }
                                });
                            }
                            try {

                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (videoCoverAdapter != null) {
                                            videoCoverAdapter.addData(bitmap);
                                            videoCoverAdapter.notifyDataSetChanged();
                                        }
                                    }
                                });

                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();

                       showToast("获取封面失败");

                        finish();
                    }

                }

             /*   Bitmap bitmap = mmr.getFrameAtTime();
                iv_video_cover.setImageBitmap(bitmap);
                System.out.println(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
                System.out.println(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));*/
                mmr.release();
            }
        }.start();

    }

    @Override
    public void onItemClick(View view, Object obj) {
        Bitmap bitmap = (Bitmap)obj;
        iv_video_cover.setImageBitmap(bitmap);
        iv_video_cover.setTag(bitmap);
    }

    @Override
    public void onDeleteClick(int position, Object obj) {

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.iv_back:
                showInfoDialog("操作确认", "作品尚未保存,确认要取消吗?", new CallBack() {

                    @Override
                    public void cancel() {

                    }

                    @Override
                    public void confirm() {
                        finish();
                    }
                });
                break;
            case R.id.tv_btn_name:
                Bitmap bitmap = (Bitmap) iv_video_cover.getTag();
                String coverPath = DisplayUtil.saveBitmap(this, bitmap);
                if(!TextUtils.isEmpty(coverPath)) {
                    Intent intent = new Intent();
                    intent.putExtra("filePath", filePath);
                    intent.putExtra("coverPath", coverPath);
                    toTarget(VideoShareActivity.class, intent, true);
                }else{
                    showToast("保存封面图片失败");
                }
                break;
        }
    }

    class VideoCoverAdapter extends RecyclerView.Adapter{

        private List<Bitmap> coverLst = null;
        private IRecyclerViewItemClick itemClickListener;
        private Context context;
        private int currentSel = 0;

        public VideoCoverAdapter(Context context) {
            super();
            this.context = context;
            this.coverLst = new ArrayList<Bitmap>();
        }

        public void setDatas(List<Bitmap> lst){
            coverLst = lst;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(context).inflate(R.layout.item_cover, parent, false);
            ConverViewHolder converViewHolder = new ConverViewHolder(view);

            return converViewHolder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
            ConverViewHolder converViewHolder = (ConverViewHolder)holder;
            converViewHolder.itemView.setTag(coverLst.get(position));
            converViewHolder.iv_conver.setImageBitmap(coverLst.get(position));
            if(position==getCurrentSel()) {
                ColorMatrix cm = new ColorMatrix();
                cm.setSaturation(1); // 设置饱和度
                ColorMatrixColorFilter grayColorFilter = new ColorMatrixColorFilter(cm);
                converViewHolder.iv_conver.setColorFilter(grayColorFilter); // 如果想恢复彩色显示,设置为null即可
                int padding = DisplayUtil.dip2px(context, 2);
                converViewHolder.iv_conver.setPadding(padding,padding,padding,padding);
                converViewHolder.iv_status.setVisibility(View.VISIBLE);
            }else{
                converViewHolder.iv_conver.setPadding(0,0,0,0);
                converViewHolder.iv_status.setVisibility(View.GONE);
                converViewHolder.iv_conver.setColorFilter(null); // 如果想恢复彩色显示,设置为null即可
            }
            converViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    setCurrentSel(position);
                    notifyDataSetChanged();
                    if(itemClickListener!=null){
                        itemClickListener.onItemClick(v, v.getTag());
                    }
                }
            });
        }

        @Override
        public int getItemCount() {
            if(coverLst!=null){
                return coverLst.size();
            }
            return 0;
        }

        public void setItemClickListener(IRecyclerViewItemClick itemClickListener) {
            this.itemClickListener = itemClickListener;
        }

        public int getCurrentSel() {
            return currentSel;
        }

        public void setCurrentSel(int currentSel) {
            this.currentSel = currentSel;
        }

        public void addData(Bitmap bitmap) {
            coverLst.add(bitmap);
        }
    }

    class ConverViewHolder extends RecyclerView.ViewHolder{

        private ImageView iv_conver;
        private ImageView iv_status;

        public ConverViewHolder(View itemView) {
            super(itemView);
            iv_conver = (ImageView) itemView.findViewById(R.id.iv_cover);
            iv_status = (ImageView) itemView.findViewById(R.id.iv_status);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if(keyCode==KeyEvent.KEYCODE_BACK){
            showInfoDialog("操作确认", "作品尚未保存,确认要取消吗?", new CallBack() {

                @Override
                public void cancel() {

                }

                @Override
                public void confirm() {
                    finish();
                }
            });
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }

}

最新布局,上面是ImageView 下面按钮是自定义View。


录制按钮 ,实现。

package pers.wtt.videocorder.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.annotation.Px;
import android.util.AttributeSet;
import android.view.View;

import com.orhanobut.logger.Logger;

import java.util.Timer;
import java.util.TimerTask;

import pers.wtt.videocorder.interfaces.OnCustomCirleListener;

/**
 * Created by 王亭 on 2017/12/18.
 */

public class CustomCircleButton extends View implements View.OnClickListener{

    private int mWidth;
    private int mHeight;
    //中心圆画笔
    private Paint mCirclePaint;
    //默认灰色描边/进度画笔
    private Paint mDefStrokePaint;
    //描边/进度画笔
    private Paint mStrokePaint;
    //按钮状态
    private Status currentStatus = Status.NORMAL;
    //总进度
    private int maxProgress = 360;
    //当前进度
    private int currentProgress = 0;
    //完成时间
    private int mComplete = 10;
    //定时器
    private Timer timer;
    //定时任务
    private TimerTask timerTask;
    //中心圆颜色
    private int mCircleColor = Color.parseColor("#129aee");
    //边框颜色
    private int mStrokeColor = Color.parseColor("#eeeeee");
    //进度颜色
    private int mProgressColor = Color.parseColor("#129aee");
    //进度完成接口
    private OnCustomCirleListener onCustomCirleListener;

    //更新UI
    private Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
                invalidate();
        }
    };

    public void setOnCustomCirleListener(OnCustomCirleListener onCustomCirleListener) {
        this.onCustomCirleListener = onCustomCirleListener;
    }

    public int getmCircleColor() {
        return mCircleColor;
    }

    public void setmCircleColor(int mCircleColor) {
        this.mCircleColor = mCircleColor;
    }

    public int getmStrokeColor() {
        return mStrokeColor;
    }

    public void setmStrokeColor(int mStrokeColor) {
        this.mStrokeColor = mStrokeColor;
    }

    public int getmProgressColor() {
        return mProgressColor;
    }

    public void setmProgressColor(int mProgressColor) {
        this.mProgressColor = mProgressColor;
    }

    public int getmComplete() {
        return mComplete;
    }

    /**
     * 设置进度完成时间
     * @param mComplete 单位秒
     */
    public void setmComplete(int mComplete) {
        this.mComplete = mComplete;
    }

    /**
     * 10秒为例 360度/10+1秒=36度每秒 1000毫秒/36度每秒≈28毫秒每度
     * 从0s开始的所以需要+1秒否则会少一秒
     * @return
     */
    private int getSpeed(){
        return Math.round(1000.00f/(360.00f/(mComplete+1)));
    }

    private void startTimer(){
        stopTimer();
        timer = new Timer();
        timerTask = new TimerTask() {
            @Override
            public void run() {

                if(currentProgress>maxProgress){
                    currentStatus = Status.NORMAL;
                    currentProgress = 0;
                    stopTimer();
                    if(onCustomCirleListener!=null)
                        onCustomCirleListener.complete();
                    handler.sendEmptyMessage(0);
                }else{
                    currentProgress+=1;
                    handler.sendEmptyMessage(0);
                }
            }
        };

        timer.schedule(timerTask, 0,getSpeed());
    }

    private void stopTimer(){
        if(timer!=null){
            timer.cancel();
            timer =null;
        }

        if(timerTask!=null){
            timerTask.cancel();
            timerTask=null;
        }
    }

    public enum Status{
        START,//开始
        STOP,//暂停
        NORMAL//停止
    }

    public CustomCircleButton(Context context) {
        this(context, null);
    }

    public CustomCircleButton(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomCircleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);//消除锯齿
        mCirclePaint.setColor(mCircleColor);

        mDefStrokePaint = new Paint();
        mDefStrokePaint.setStyle(Paint.Style.STROKE);
        mDefStrokePaint.setAntiAlias(true);//消除锯齿
        mDefStrokePaint.setColor(mStrokeColor);
        mDefStrokePaint.setStrokeWidth(20);

        mStrokePaint = new Paint();
        mStrokePaint.setStyle(Paint.Style.STROKE);
        mStrokePaint.setAntiAlias(true);//消除锯齿
        mStrokePaint.setColor(mProgressColor);
        mStrokePaint.setStrokeWidth(10);

        setOnClickListener(this);
    }

    @Override
    public void layout(@Px int l, @Px int t, @Px int r, @Px int b) {
        super.layout(l, t, r, b);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int r = getWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
        //圆心的横坐标为当前的View的左边起始位置+半径
        int centerX = r;
        //圆心的纵坐标为当前的View的顶部起始位置+半径
        int centerY = r;
        if(currentStatus==Status.NORMAL) {
            canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mCirclePaint);
            canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mDefStrokePaint);
        }else{
            canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mCirclePaint);
            canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mDefStrokePaint);
            RectF oval  = new RectF();
            oval.set(15,15,r*2 - 15,r * 2 - 15);
            canvas.drawArc(oval, -90, ((float) currentProgress / maxProgress) * 360, false, mStrokePaint);
        }
        super.onDraw(canvas);

    }



    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, mHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, mHeight);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

    }

    @Override
    public void onClick(View v) {
        Logger.e("CustomCircleButton Click!");
        if(currentStatus==Status.START) {
            currentStatus = Status.STOP;
            stopTimer();
        }else if(currentStatus==Status.STOP){
            currentStatus = Status.START;
            startTimer();

        }else{
            currentStatus = Status.START;
            startTimer();
        }
        invalidate();

        onCustomCirleListener.onClick(v);
    }
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Camera开发入门:目录 第一篇: 前景  一、Android Camera开发前景;      1)camera相关应用的领域      2)相关岗位介绍;      3)市场招聘介绍;      4)发展前景介绍;  二、学习这门课的重要性;      1)适合的人群;      2)熟悉和了解Android Camera 应用开发流程的重要性 第二篇: 开发环境安装  一、jdk、sdk的配置;  二、android studio的安装介绍;  三、adb命令的使用; 第三篇: Camera 常用api和最新框架介绍  一、android camera api介绍      1)camera1、camera2 区别;      2)camera 1、camera2 常用api介绍;      3)android camerax;  二、android camera最新框架介绍 第四篇:Camera api1实现预览、拍照、录像功能  一、预览  二、拍照  三、录像  四、获取实时预览流 第五篇: Camera2相机 打开功能实现第六篇: Camera2相机 预览功能实现  1)surfaceview、textureview 第七篇: Camera2相机 拍照功能实现 1)单拍; 第八篇:Camera2相机 录像功能实现1)正常录像 第九篇:Camera2预览方向、拍照方向设置     1) 预览变形问题如何处理? 第十篇:YUV流处理  1)如何获取实时预览流?  2)  思考:双码流方案如何实现?一边本地录像,一边后台推流 第十一篇:dumpsys media.camera 第十二篇:Camera2 Zoom变焦第十三篇:人脸识别(android 原生 & 三方人脸识别算法)第十四篇:Uvc UsbCamera第十五篇:Android Camera2拍摄RAW图第十六篇: Android Camera2同时打开前后摄 并 录像第十七篇: Android Camera2 视频慢动作  附:1)提供android开发相关资源      软件工具、Android相关学习书籍、学习相关网站博客等链接2)提供课程讲解中设计到的App 源码    * Camera API1使用源码    * Camera API2使用源码    * 调用三方算法人脸识别源码    *  录像慢动作源码    * Uvc UsbCamera相关源码3)课件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值