Android仿微信小视频录制功能

还没看完,应该还不错,先收藏,觉得可以开拓

https://blog.csdn.net/u012227600/article/details/50835633

--------------------------------------------------------------------------------------------------------------------------------------------

关于Android视频录制功能的实现流程以及相关API的介绍网上有很多,这里就不再赘述。在学习的过程中主要参考了 Vitamio公司的VCamera SDK 项目,因为该项目在Java层全开源,所以在调用Android系统的相机进行录制方面记录下一些学习体会。

具体关于VCamera项目,请移步VCamera

嗯,还是先看效果: 
这里写图片描述

定义

首先是定义视频录制接口,接口很简单只包含两个方法,因为是调用系统的API实现录制,所以在方法上只用考虑到start和stop方法,出于对功能的拓展性上(比如自定义相机功能),接口的方法还可以更丰富。

public interface IMediaRecorder {
    /**
     * 开始录制 
     * @return 录制失败返回null
     */
    public MediaObject startRecord();
    /**
     * 停止录制
     */
    public void stopRecord();
}

接着是bean:

    /** 视频最大时长,默认10秒 */
    private int mMaxDuration;
    /** 视频目录 */
    private String mOutputDirectory;
    /** 对象文件 */
    private String mOutputObjectPath;
    /** 视频码率 */
    private int mVideoBitrate;
    /** 最终视频输出路径 */
    private String mOutputVideoPath;
    /** 最终视频截图输出路径 */
    private String mOutputVideoThumbPath;
    /** 文件夹及文件名 */
    private String mKey;
    /** 开始时间 */
    private long mStartTime;
    /** 结束时间 */
    private long mEndTime;
    /** 视频移除标志位 */
    private boolean mRemove;

    /** 两个构造方法 */
    public MediaObject(String key, String path) {
        this(key, path, DEFAULT_VIDEO_BITRATE);
    }
    public MediaObject(String key, String path, int videoBitrate) {
        this.mKey = key;
        this.mOutputDirectory = path;
        this.mVideoBitrate = videoBitrate;
        this.mOutputObjectPath = mOutputDirectory + File.separator + mKey + ".obj";
        this.mOutputVideoPath = mOutputDirectory + File.separator + mKey +".mp4";
        this.mOutputVideoThumbPath = mOutputDirectory + File.separator + mKey + ".jpg";
        this.mMaxDuration = DEFAULT_MAX_DURATION;
    }

实现

在视频录制过程中分为两个部分,第一个就是我们在界面上看到的摄像头传来的预览画面Preview,另一个则是视频录制时使用的”录像机“Recorder,同样为了提高拓展性,在功能实现上我们先将预览画面提出来实现它。因为也许你有许多不同的Recorder,但是预览的方式只有那么一个。

那么抽象出一个录像父类非常重要,它实现了一些都需要用到的方法,其中当然包括我们的Preview

public abstract class MediaRecorderBase implements Callback, PreviewCallback, IMediaRecorder {...}
  • 1

非常直观哈,Callback接口是SurfaceHolder的回调接口,我们所看到的预览画面就是呈现在SurfaceView上的,PreviewCallback预览画面的回调接口,至于我们先前定义的IMediaRecorder就留给后人去实现吧。

回到主线预览画面上,关于SurfaceView这里不做介绍了,我们要做的是先把它的SurfaceHolder拿过来

    public void setSurfaceHolder(SurfaceHolder sh) {
        if (sh != null) {
            sh.addCallback(this);
            if (!DeviceUtils.hasHoneycomb()) {
                sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            }
        }
    }

然后就可以掌控这一切了。。

    public void prepare() {
        mPrepared = true;
        if (mSurfaceCreated)
            startPreview();
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        this.mSurfaceHolder = holder;
        this.mSurfaceCreated = true;
        if (mPrepared && !mStartPreview)
            startPreview();
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        this.mSurfaceHolder = holder;
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        release();
    }

startPreview()就是我们的预览方法:

/** 开始预览 */
    public void startPreview() {
        if (mStartPreview || mSurfaceHolder == null || !mPrepared)
            return;
        else
            mStartPreview = true;
        try {
            //打开镜头
            if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
                camera = Camera.open();
            else
                camera = Camera.open(mCameraId);
            try {
                //为preview设置SurfaceHolder
                camera.setPreviewDisplay(mSurfaceHolder);
            } catch (IOException e) {
                if (mOnErrorListener != null) {
                mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_SET_PREVIEW_DISPLAY, 0);
                }
                Log.e(" ", "setPreviewDisplay fail " + e.getMessage());
            }
            //获取摄像头参数
            Parameters parameters = camera.getParameters();
            prepareCameraParaments(parameters);
            setPreviewCallback(parameters);
            camera.setParameters(parameters);
            camera.startPreview();
            if (mOnPreparedListener != null)
                mOnPreparedListener.onPrepared();
        } catch (Exception e) {
            e.printStackTrace();
            if (mOnErrorListener != null) {
                mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_PREVIEW, 0);
            }
            Log.e(" ", "startPreview fail :" + e.getMessage());
        }
    }

这里有几个方法依次说一下,首先是protected void prepareCameraParaments(Parameters parameters)调用它会对相机进行一些参数上的处理,里面基本包括:

List<int[]> supportedPreviewFpsRange = parameters.getSupportedPreviewFpsRange();

这里返回一个数组列表,里面包含了设备所支持预览画面的FPS值,每个数组含有两个值,第一个值是最小FPS,另一个是最大FPS值,这样就构成了一个区间,可以检测你所期望的FPS值在不在设备支持中。那么:

parameters.setPreviewFrameRate(mFrameRate);

接着,

List<Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();

这个返回了设备支持的预览画面的尺寸,Size有很多,但是重点在比例,16:9、4:3 、11:9等等,比例非常关键,它直接关系到后来我们的SurfaceView的尺寸,选一个你想要的把它:

parameters.setPreviewSize(customSize.width, customSize.height);

我们知道小视频录制的时候是竖屏的,所以把它竖过来是必须的:

camera.setDisplayOrientation(90);

顺时针旋转90度,之所以这么做,API文档上有介绍,大意就是camera sensor没有转,但是我们可以把画面转过来。 
这样就可以了,当然如果想设置更多,当然没问题,这里的Parameters设置的参数实际上都是以Map键值对的形式传入的,具体的源码也有大神解读。那么,比如加一个防抖功能(前提是你的设备支持):

        if ("true".equals(parameters.get("video-stabilization-supported")))
            parameters.set("video-stabilization", "true");

再来看protected void setPreviewCallback(Parameters parameters)方法,作用呢就是给Camera对象设置我们一开始实现的那个PreviewCallback接口:

    protected void setPreviewCallback(Parameters parameters) {
        Camera.Size size = parameters.getPreviewSize();
        if (size != null) {
            PixelFormat pf = new PixelFormat();
            PixelFormat.getPixelFormatInfo(parameters.getPreviewFormat(), pf);
            int buffSize = size.width * size.height * pf.bitsPerPixel / 8;
            try {
                camera.addCallbackBuffer(new byte[buffSize]);
                camera.addCallbackBuffer(new byte[buffSize]);
                camera.addCallbackBuffer(new byte[buffSize]);
                camera.setPreviewCallbackWithBuffer(this);
            } catch (OutOfMemoryError e) {
                Log.e(" ", "startPreview...setPreviewCallback...", e);
            }
        } else {
            camera.setPreviewCallback(this);
        }
    }

之前我们为parameter对象设置过previewSize参数,所以明显的这里用到了Camera.setPreviewCallbackWithBuffer(this)方法,实际上就是在视频预览的回调中加入缓冲区Buffer,怎么做文档上说的很清楚,调用Camera.addCallbackBuffer(new byte[buffSize]);而且Applications can add one or more buffers to the queue.可以加入多个到队列中,再看When a preview frame arrives and there is still at least one available buffer, the buffer will be used and removed from the queue.好吧用过了还要抛弃掉。。另外buffer大小,文档中也告诉了怎么去计算,就是呈现出来的每帧画面中的每个像素所占的bits的和除以8。那么被无情抛弃的buffer怎么办?队空了没buffer用了,这一帧的画面也会被系统舍弃。实际上,我们有回调PreviewCallback

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        camera.addCallbackBuffer(data);
    }

再把它加回去。(以上全是个人理解,有偏差请见谅) 
当设置完参数就可以开启预览了camera.startPreview()。 
停止预览很简单:Camera.stopPreview(),然后将相机回调解绑,资源释放掉,holder资源释放,定义的标志位重置就好。毕竟这些资源太重了。

回到录像部分,在这里需要做的首先是继承之前的MediaRecorderBase类并且实现未实现的IMediaRecorder接口方法。

public class MediaRecorderSystem extends MediaRecorderBase implements OnErrorListener {
    @Override
    public MediaObject startRecord() {...}
    @Override
    public void stopRecord() {...}
    ...
}

startRecord()会返回一个MediaObject对象,所以我们暴露出一个方法可以得到它

    /** 拍摄存储对象 */
    private MediaObject mMediaObject;
    public MediaObject setOutputDirectory(String key, String path) {
        if (StringUtils.isNotEmpty(path)) {
            File f = new File(path);
            if (f != null) {
                if (f.exists()) {
                    //已经存在,删除
                    FileUtils.deleteFile(f);
                }
                if (f.mkdirs()) {
                    mMediaObject = new MediaObject(key, path, mVideoBitrate);
                }
            }
        }
        return mMediaObject;
    }

之后在startRecord()中实现视频录制,直接上代码:

        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
            mMediaRecorder.setOnErrorListener(this);
        } else {
            mMediaRecorder.reset();
        }
        // Step 1: Unlock and set camera to MediaRecorder
        camera.unlock();
        mMediaRecorder.setCamera(camera);
        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
        // Step 2: Set sources
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//before setOutputFormat()
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//before setOutputFormat()
        //设置视频输出的格式和编码
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        CamcorderProfile mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_CIF);
        //after setVideoSource(),after setOutFormat()
        mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
        mMediaRecorder.setAudioEncodingBitRate(44100);
        if (mProfile.videoBitRate > 2 * 1024 * 1024)
                mMediaRecorder.setVideoEncodingBitRate(2 * 1024 * 1024);
        else
                mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);
        //after setVideoSource(),after setOutFormat();
        mMediaRecorder.setVideoFrameRate(mProfile.videoFrameRate);
        //after setOutputFormat()
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        //after setOutputFormat()
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        // Step 3: Set output file
        mMediaRecorder.setOutputFile(mMediaObject.getOutputVideoPath());
        // Step 4: start and return
        mMediaRecorder.prepare();
        mMediaRecorder.start();
        mMediaObject.setStartTime(System.currentTimeMillis());
        mRecording = true;
        return mMediaObject;

上面的一系列是调用系统API录制视频的基本流程,也可以去参考开发手册的讲解。 
stopRecord()方法中,把录像停下来:

    ...
    mMediaRecorder.setOnErrorListener(null);
    mMediaRecorder.setPreviewDisplay(null);
    ...
    mMediaRecorder.stop();
    ...
    camera.lock();

重写父类的release()方法,释放掉MediaRecorder资源:

...
super.release();
mMediaRecorder.release();
...

另外,在OnErrorListener监听中捕获到异常并且mMediaRecorder.reset()。 
视频录制功能基本上就完成了,但是我们需求的功能却不止这些,那么就再加点料。回到MediaRecorderBase类,实现需要添加的功能分别是:自动、手动对焦,变焦,摄像头切换以及闪光灯的开关。 
这些功能需求很简单: 
对焦:当我们一开启预览时会自动调节焦距,当获取到点击时则进入手动对焦; 
变焦:双击预览区域zoom+,再次双击zoom-回到初始状态; 
摄像头切换:默认开启后置摄像头,点击按钮切换至前置摄像头,再次点击切换到后置; 
闪光灯:默认关闭,点击按钮打开闪光灯保持常亮,再次点击关闭;

首先是对焦,这里用到持续对焦这条参数

...
//获取到设备支持的对焦模式
List<String> focusModes = parameters.getSupportedFocusModes();
if(!CollectionUtils.isEmpty(focusModes)){
    if(focusModes.list.contains("continuous-video"))
        parameters.setFocusMode("continuous-video");
}

这里以”continuous-video”参数为例,因为不同设备存在差异,实际上是需要判断focusModes列表中支持的类型,类型有:“continuous-video”、“continuous-picture”和“auto”当然你也可以直接使用Parameters.FOCUS_MODE_CONTINUOUS_VIDEO。 
手动对焦方法如下:

    public boolean manualFocus(AutoFocusCallback cb, List<Area> focusAreas) {
        //判断系统是否是4.0以上的版本
        if (camera != null && focusAreas != null && DeviceUtils.hasICS()) {
            try {
                camera.cancelAutoFocus();
                Parameters parameters = camera.getParameters();
                if(parameters != null){
                    // getMaxNumFocusAreas检测设备是否支持
                    if (parameters.getMaxNumFocusAreas() > 0) {
                        parameters.setFocusAreas(focusAreas);
                    }
                    // getMaxNumMeteringAreas检测设备是否支持
                    if (parameters.getMaxNumMeteringAreas() > 0)
                        parameters.setMeteringAreas(focusAreas);
                    parameters.setFocusMode("macro");
                    camera.setParameters(parameters);
                    camera.autoFocus(cb);
                    return true;
                }
            } catch (Exception e) {
                if (mOnErrorListener != null) {
                    mOnErrorListener.onVideoError(
                            MEDIA_ERROR_CAMERA_AUTO_FOCUS, 0);
                }
                if (e != null)
                    Log.e(" ", "autoFocus", e);
            }
        }
        return false;
    }

首先传入一个AutoFocusCallback回调,它会告诉我们对焦是否OK和当前Camera的对象,List<Area>是需要对焦的区域。流程很简单,先取消自动对焦,然后向相机参数中设置好对焦区域和对焦模式,再调用相机的Camera.autoFocus(...)方法,这样就会对指定的区域进行对焦。

变焦:

    public void setZoom(int zoomValue) {
        Parameters parameters = camera.getParameters();
        if (parameters.isZoomSupported()) {
            final int MAX = parameters.getMaxZoom();
            if (MAX == 0)
                return;
            if (zoomValue > MAX)
                zoomValue = MAX;
            parameters.setZoom(zoomValue); // value zoom value. The valid range
                                            // is 0 to getMaxZoom.
            camera.setParameters(parameters);
        }
        else
            return;
    }

传入一个焦距值,然后判断相机是否支持变焦,这里会出一个bug,对于有的设备,isZoomSupported()返回了true,但是仍然无法变焦,则需要判断下MaxZoom的值,如果为0,则仍然不支持变焦,因为是固定传入焦距,所以直接设置就好了。

摄像头切换我们在startPreview()方法中有这么一段:

        ...
        try {
            if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
                camera = Camera.open();
            else
                camera = Camera.open(mCameraId);
            try {
                camera.setPreviewDisplay(mSurfaceHolder);
            } catch (IOException e) {
                if (mOnErrorListener != null) {
                mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_SET_PREVIEW_DISPLAY, 0);
                }
                Log.e(" ", "setPreviewDisplay fail " + e.getMessage());
            }
        ...

通过对mCameraId的改变,先调用stopPreview()再调用startPreview()就可以完成了。

最后,对于闪光的切换就不必多说了

    public void toggleFlashMode() {
        Parameters parameters = camera.getParameters();
        if (parameters != null) {
            try {
                final String mode = parameters.getFlashMode();
                if (TextUtils.isEmpty(mode) || Camera.Parameters.FLASH_MODE_OFF.equals(mode))
                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                else
                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                camera.setParameters(parameters);
            } catch (Exception e) {
                Log.e(" ", "toggleFlashMode", e);
            }
        }
    }

那么,录像功能上基本上就OK了,现在要把它用起来。

UI

先上xml:

<?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" >
    <RelativeLayout
        android:id="@+id/title_layout"
        android:layout_width="match_parent"
        android:layout_height="49dip"
        android:background="@color/black"
        android:gravity="center_vertical" >

        <ImageView
            android:id="@+id/title_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dip"
            android:padding="10dip"
            android:src="@drawable/arrow_left" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="48dip"
            android:layout_alignParentRight="true"
            android:gravity="right|center_vertical"
            android:orientation="horizontal" >
            <CheckBox
                android:id="@+id/record_camera_led"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/record_camera_flash_led_selector"
                android:button="@null"
                android:textColor="@color/white" />
            <CheckBox
                android:id="@+id/record_camera_switcher"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="10dp"
                android:background="@drawable/record_camera_switch_selector"
                android:button="@null" />
        </LinearLayout>
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/camera_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/title_layout" >
        <SurfaceView
            android:id="@+id/record_preview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <ImageView
            android:id="@+id/record_focusing"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:scaleType="fitXY"
            android:src="@drawable/video_focus"
            android:visibility="gone" />
        <TextView android:id="@+id/record_tip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:textSize="12sp"
            android:background="@drawable/recorder_tips"/>
        <com.example.activity.widget.movie.view.ProgressView
            android:id="@+id/record_progress"
            android:layout_width="match_parent"
            android:layout_height="3dp"/>
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/bottom_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/title_layout"
        android:background="@color/black" >
        <ImageView
            android:id="@+id/record_controller"
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:layout_centerInParent="true"
            android:src="@drawable/bg_movie_add_shoot" />
        <TextView 
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:layout_centerInParent="true"
            android:text="按住拍"
            android:gravity="center"
            android:textColor="#FF45C01A"
            android:textSize="20sp"/>
    </RelativeLayout>
</RelativeLayout>

不出意外的不太像。。蛤蛤 
Activity: 
onCreate中处理View:

private void initViews() {
    ...
    mWindowWidth = DeviceUtils.getScreenWidth(this);
    int height = (int) (mWindowWidth * MediaRecorderBase.PREVIEW_RATIO);
    ((RelativeLayout.LayoutParams)mBottomLayout.getLayoutParams()).topMargin = mWindowWidth;
    ((RelativeLayout.LayoutParams)mRecordTipView.getLayoutParams()).topMargin = mWindowWidth - DisplayUtil.dip2px(this,(40));
    ((RelativeLayout.LayoutParams)mProgressView.getLayoutParams()).topMargin = mWindowWidth - DisplayUtil.dip2px(this, 3);
    RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mSurfaceView
                .getLayoutParams();
    lp.width = mWindowWidth;
    lp.height = height;
    mSurfaceView.setLayoutParams(lp);
    mRecordTipView.setVisibility(View.GONE);
}

这里我们对SurfaceView的的宽度和高度做了处理,而不是在xml中,之前的PreView中设置过camera.setDisplayOrientation(90)画面旋转的处理(下图),所以SurfaceView的尺寸也应做出相应的调整 
这里写图片描述 
onResume()中对MediaRecorder对象做处理:

...
    private MediaRecorderBase mMediaRecorder;
    private MediaObject mMediaObject;
    @Override
    protected void onResume() {
        super.onResume();
        if (mMediaRecorder == null) {
            initMediaRecorder();
        } else {
            mRecordLed.setChecked(false);
            mMediaRecorder.prepare();
        }
    }
    private void initMediaRecorder() {
        mMediaRecorder = new MediaRecorderSystem();
        mMediaRecorder.setOnErrorListener(this);
        File f = new File(CACHE_PATH);
        if (!FileUtils.checkFile(f)) {
            f.mkdirs();
        }
        String key = String.valueOf(System.currentTimeMillis());
        mMediaObject = mMediaRecorder.setOutputDirectory(key,
                CACHE_PATH + key);
        mMediaObjList.add(mMediaObject);
        mMediaRecorder.setSurfaceHolder(mSurfaceView.getHolder());
        mMediaRecorder.prepare();
    }
...

这么做确保Activity从后台切换回来显示预览依旧正常; 
Activity销毁时,处理等待删除缓存目录以及释放资源:

    @Override
    protected void onDestroy() {
        //activity 销毁时删除废弃的缓存目录
        if (!CollectionUtils.isEmpty(mMediaObjList)) {
            for (MediaObject obj : mMediaObjList) {
                if(obj != null && obj.isRemove())
                    FileUtils.deleteDir(obj.getOutputDirectory());
            }
        }
        mMediaRecorder.release();
        super.onDestroy();
    }

录制按钮的Touch事件:

private View.OnTouchListener mOnVideoControllerTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (mMediaRecorder == null) {
                return false;
            }
            if (mMediaObject == null){
                String key = String.valueOf(System.currentTimeMillis());
                mMediaObject = mMediaRecorder.setOutputDirectory(key,
                        CACHE_PATH + key);
                mMediaObjList.add(mMediaObject);
            }
            int action = event.getAction();
            if(action == MotionEvent.ACTION_DOWN){
                // 判断是否已经超时
                if (mMediaObject.getDuration() >= RECORD_TIME_MAX) {
                    return true;
                }
                mMediaObject.setStartTime(System.currentTimeMillis());
                startRecord();
            }
            if(action == MotionEvent.ACTION_MOVE){
                if(event.getY() < 0)
                    mAtRemove = true;
                else
                    mAtRemove = false;
                changeTip();
                mProgressView.setRemove(mAtRemove);
                mMediaObject.setRemove(mAtRemove);
                mHandler.sendEmptyMessage(HANDLE_INVALIDATE_PROGRESS);
            }
            if(action == MotionEvent.ACTION_UP){
                //停止录制
                if (mPressedStatus){
                    mRecordTipView.setVisibility(View.GONE);
                    mCameraSwitch.setVisibility(View.VISIBLE);
                    stopRecord();
                }
            }
            return true;
        }
    };
  •  

按下去就开始录制,向上滑提示放开取消,下滑回来继续录,直到放开停止录制。下面是两个控制录制的方法:


    /**
     * 开始录制 
     */
    private void startRecord() {
        if (mMediaRecorder != null) {
            mMediaRecorder.startRecord();
            // 使用系统录制环境,不能在中途切换前后摄像头,否则有问题
            if (mMediaRecorder instanceof MediaRecorderSystem) {
                mCameraSwitch.setVisibility(View.GONE);
            }
        }
        mPressedStatus = true;
        mRecordController.setImageResource(R.drawable.bg_movie_add_shoot);      
        mCameraSwitch.setEnabled(false);
        mRecordLed.setEnabled(false);
        mTimeCount = 0;// 时间计数器重新赋值
        mTimer = new Timer();
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                mTimeCount++;
                mProgressView.setProgress(mTimeCount * 100);// 设置进度条
                mHandler.sendEmptyMessage(HANDLE_INVALIDATE_PROGRESS);
                if (mTimeCount >= RECORD_TIME_MAX / 100) {// 达到指定时间
                    this.cancel();
                    mHandler.removeMessages(HANDLE_INVALIDATE_PROGRESS);
                }
            }
        }, 0, 100);
    }

    /**
     * 停止录制
     */
    private void stopRecord() {
        resetTimer();
        mPressedStatus = false;
        if (mMediaRecorder != null && mMediaObject != null) {
            long endTime = System.currentTimeMillis();
            mMediaObject.setEndTime(endTime);
            int duration = (int) mMediaObject.getDuration();
            //录制时间小于最小值取消录制并返回
            if(duration < RECORD_TIME_MIN || mMediaObject.isRemove()){
                mMediaObject.setRemove(true);
                mMediaObjList.add(mMediaObject);
                if(duration < RECORD_TIME_MIN && !mAtRemove)
                    Tools.showToast("视频时间太短");
                mMediaRecorder.stopRecord();
                mCameraSwitch.setEnabled(true);
                mRecordLed.setEnabled(true);
                mMediaObject = null;
                return;
            }
            mMediaRecorder.stopRecord();
        }
        mCameraSwitch.setEnabled(true);
        mRecordLed.setEnabled(true);
        saveObj();
        saveThumb();
        //到下一个activity
//      Intent intent = new Intent();
//      intent.putExtra("MediaObj", mMediaObject);
//      intent.setClass(this, MoviePreviewActivity.class);
//      startActivity(intent);
//      finish();
    }

    /**
     * 重置计时器
     */
    private void resetTimer(){
        if(mTimer != null){
            mTimer.cancel();
            mTimer.purge();
            mProgressView.setProgress(0);
            mProgressView.setRemove(false);
            mHandler.sendEmptyMessage(HANDLE_INVALIDATE_PROGRESS);
        }

    }
  •  

Timer计时器用于更新进度条。 
再看SurfaceView的触摸事件:

private GestureDetector mDetector;
private View.OnTouchListener mOnSurfaveViewTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (mMediaRecorder == null || !mCreated) {
                return false;
            }
            return mDetector.onTouchEvent(event);
        }

};
...
class ZoomGestureListener extends SimpleOnGestureListener{
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        if (mMediaRecorder == null || !mCreated) {
            return false;
        }

        if(!mZoomIn){
            mMediaRecorder.setZoom(8); //zoom in..
            mZoomIn = true;
        }else{
            mMediaRecorder.setZoom(0); //zoom out..
            mZoomIn = false;
        }
            return true;
        }
    @Override
    public boolean onDown(MotionEvent e) {
        checkCameraFocus(e);
        return true;
    }
}
  •  

用GestureDetector 对单击和双击事件进行捕获处理。checkCameraFocus(...)方法就是用来手动对焦的:

private void checkCameraFocus(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        float touchMajor = event.getTouchMajor();
        float touchMinor = event.getTouchMinor();

        //触摸范围
        Rect touchRect = new Rect((int) (x - touchMajor / 2),
                (int) (y - touchMinor / 2), (int) (x + touchMajor / 2),
                (int) (y + touchMinor / 2));

        //坐标转换为focusArea范围
        Rect focusRect = new Rect();
        focusRect.set(touchRect.left * 2000 / mSurfaceView.getWidth() - 1000,
                    touchRect.top * 2000 / mSurfaceView.getHeight() - 1000,
                    touchRect.right * 2000 / mSurfaceView.getWidth() - 1000,
                    touchRect.bottom * 2000 / mSurfaceView.getHeight() - 1000);
        if (focusRect.left >= focusRect.right
                || focusRect.top >= focusRect.bottom)
            return;

        ArrayList<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
        focusAreas.add(new Camera.Area(focusRect, 1000));
        if (!mMediaRecorder.manualFocus(new Camera.AutoFocusCallback() {

            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                // if (success) {
                mFocusImage.setVisibility(View.GONE);
                System.out.println("onAutoFocus previewsize..width = " + camera.getParameters().getPreviewSize().width 
                        + "\nheight = " + camera.getParameters().getPreviewSize().height);
                // }
            }
        }, focusAreas)) {
            mFocusImage.setVisibility(View.GONE);
        }

        int focusWidth = mFocusImage.getWidth();
        RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mFocusImage
                .getLayoutParams();
        int left = touchRect.left - (focusWidth / 2);
        int top = touchRect.top - (focusWidth / 2);
        if (left < 0)
            left = 0;
        else if (left >= mWindowWidth)
            left = mWindowWidth - focusWidth;
        if (top > mSurfaceView.getHeight())
            top = mSurfaceView.getHeight() - focusWidth;
        lp.leftMargin = left;
        lp.topMargin = top;
        mFocusImage.setLayoutParams(lp);
        mFocusImage.setVisibility(View.VISIBLE);
        mFocusImage.startAnimation(mFocusAnimation);
        mHandler.sendEmptyMessageDelayed(HANDLE_HIDE_RECORD_FOCUS, 3500);// 最多3.5秒也要消失
    }

这里的focusRect是通过touchRect计算映射过去的,之所以这么做,先看下图 
这里写图片描述 
简单来说Camera.Area对象的Rect字段是描述了一个矩形区域在一个2000 x 2000个单元格组成的区域中的映射位置。坐标-1000, -1000代表了top, left,并且坐标1000, 1000代表了bottom, right。并且即使使用Camera.setDisplayOrientation()旋转预览图像也不会改变该坐标系。

关于摄像头切换和闪光灯开关这里就简单说下,无非就是获取点击事件,然后调用之前在MediaRecorderBase类中封装好的方法即可。

最后差点忘了我们的进度条。。这是一个粗糙的进度条:

public class ProgressView extends View {
    /** 进度条 */
    private Paint mProgressPaint;
    /** 回删 */
    private Paint mRemovePaint;
    /** 最长时长 */
    private int mMax;
    /** 进度*/
    private int mProgress;
    private boolean isRemove;
    public ProgressView(Context Context, AttributeSet Attr) {
        super(Context, Attr);
        init();
    }
    private void init() {
        mProgressPaint = new Paint();
        mRemovePaint = new Paint();
        setBackgroundColor(getResources().getColor(R.color.transparent));
        mProgressPaint.setColor(Color.GREEN);
        mProgressPaint.setStyle(Paint.Style.FILL);
        mRemovePaint.setColor(getResources().getColor(
                R.color.title_back));
        mRemovePaint.setStyle(Paint.Style.FILL);;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        final int width = getMeasuredWidth(), height = getMeasuredHeight();
        int progressLength = (int) ((mProgress / (mMax * 1.0f)) * (width / 2));
        canvas.drawRect(progressLength, 0, width - progressLength, height, isRemove ? mRemovePaint : mProgressPaint);
        canvas.restore();
    }
    public void setMax(int max){
        this.mMax = max;
    }
    public void setProgress(int progress){
        this.mProgress = progress;
    }
    public void setRemove(boolean isRemove){
        this.isRemove = isRemove;
    }
}

结语

作为第一篇博,难免会啰嗦抓不到重点,其实也是自己在总结的时候没有做很好得精炼,后续在总结UI的实现上,贴了代码没有介绍,是因为发现这边可总结的确实不多,OK就这样。

猜你喜欢

转载自blog.csdn.net/tantion/article/details/82380668