MediaRecorder结合SurfaceView录制视频

手机一般都有麦克风和摄像头,而Android系统就可以利用这些硬件来录制音视频了。为了增加对录制音视频的支持,Android系统提供了一个MediaRecorder的类。使用MediaRecorder结合SurfaceView录制视频。下面先简单了解一下MediaRecorder这个类。

MediaRecorder应用实例

  • 使用MediaRecorder录制音乐
  • 使用MediaRecorder录制视频

MediaRecorder功能设置(方法/作用)

  • getAudioSourceMax() 获取音频信号源的最高值。
  • getMaxAmplitude() 最后调用这个方法采样的时候返回最大振幅的绝对值
  • getMetrics() 返回当前Mediacorder测量的数据
  • getSurface() 当使用Surface作为视频源的时候,返回Sufrace对象
  • pause() 暂停录制
  • prepare() 准备录制
  • resume() 恢复录制
  • release() 释放与此MediaRecorder对象关联的资源
  • reset() 重新启动mediarecorder到空闲状态
  • setAudioChannels(int numChannels) 设置录制的音频通道数
  • setAudioEncoder(int audio_encoder) 设置audio的编码格式
  • setAudioEncodingBitRate(int bitRate) 设置录制的音频编码比特率
  • setAudioSamplingRate(int samplingRate) 设置录制的音频采样率
  • setAudioSource(int audio_source) 设置用于录制的音源
  • setAuxiliaryOutputFile(String path) 辅助时间的推移视频文件的路径传递
  • setAuxiliaryOutputFile(FileDescriptor fd) 在文件描述符传递的辅助时间的推移视频
  • setCamera(Camera c) 设置一个recording的摄像头,此方法在API21被遗弃,被getSurface替代
  • setCaptureRate(double fps) 设置视频帧的捕获率
  • setInputSurface(Surface surface) 设置持续的视频数据来源
  • setMaxDuration(int max_duration_ms) 设置记录会话的最大持续时间(毫秒)
  • setMaxFileSize(long max_filesize_bytes) 设置记录会话的最大大小(以字节为单位)
  • setOutputFile(FileDescriptor fd) 传递要写入的文件的文件描述符
  • setOutputFile(String path) 设置输出文件的路径
  • setOutputFormat(int output_format) 设置在录制过程中产生的输出文件的格式
  • setPreviewDisplay(Surface sv) 表面设置显示记录媒体(视频)的预览
  • setVideoEncoder(int video_encoder) 设置视频编码器,用于录制
  • setVideoEncodingBitRate(int bitRate) 设置录制的视频编码比特率
  • setVideoFrameRate(int rate) 设置要捕获的视频帧速率
  • setVideoSize(int width, int height) 设置要捕获的视频的宽度和高度
  • setVideoSource(int video_source) 开始捕捉和编码数据到setOutputFile(指定的文件)
  • setLocation(float latitude, float longitude) 设置并存储在输出文件中的地理数据(经度和纬度)
  • setProfile(CamcorderProfile profile) 指定CamcorderProfile对象
  • setOrientationHint(int degrees) 设置输出的视频播放的方向提示
  • setOnErrorListener(MediaRecorder.OnErrorListener l) 注册一个用于记录录制时出现的错误的监听器
  • setOnInfoListener(MediaRecorder.OnInfoListener listener) 注册一个用于记录录制时出现的信息事件

MediaRecorder内的嵌套类

  • MediaRecorder.AudioEncoder
  • MediaRecorder.AudioSource
  • MediaRecorder.VideoSource
  • MediaRecorder.OutputFormat

MediaRecorder.AudioEncoder

大家都知道在录音的时候都要调用setAudioEncoder()方法,这个方法里面总有不同的参数,这个类就是参数的值,这里说一下各个不同值的区别:

default: 默认值。
AAC: 高级音频编码,简单说下优缺点:

AAC优点:相对于mp3,AAC格式的音质更佳,文件更小。
AAC不足:AAC属于有损压缩的格式,与时下流行的APE、FLAC等无损格式相比音质存在”本质上”的差距。加之,传输速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC头上”小巧”的光环不复存在。

HE_AAC: HE-AAC混合了AAC与SBR技术。
AAC_ELD: 低延时的AAC音频编解码器。
AMR_NB: 编码的是无视频纯声音3gp文件就是amr,他的文件比AAC的小,音乐效果没ACC的好。
AMR_WB: VMR-WB 是新型可变速率多模式宽带语音编解码器,专为无线 CDMA 2000标准而设计,目的在于在 50 至 7000 HZ 的频带上进行语音编码,采样率为 16 KHZ。VMR-WB 基于 3GPP AMR-WB (G722.2) 编解码器,在每秒速率12.65 Kbit 上可实现互操作。
VORBIS: Vorbis是一种新的音频压缩格式,类似于MP3等现有的音乐格式。但有一点不同的是,它是完全免费、开放和没有专利限制的。OGG Vorbis有一个很出众的特点,就是支持多声道,随着它的流行,以后用随身听来听DTS编码的多声道作品将不会是梦想。

MediaRecorder.AudioSource

这个类对应setAudioSource(int) 方法,主要用来设置音频源; MediaRecorder.AudioSource音频参数说明如下:
MediaRecorder.AudioSource.CAMCORDER 设定录音来源于同方向的相机麦克风相同,若相机无内置相机或无法识别,则使用预设的麦克风
MediaRecorder.AudioSource.DEFAULT 默认音频源
MediaRecorder.AudioSource.MIC 设定录音来源为主麦克风。
MediaRecorder.AudioSource.VOICE_CALL设定录音来源为语音拨出的语音与对方说话的声音
MediaRecorder.AudioSource.VOICE_COMMUNICATION 摄像头旁边的麦克风
MediaRecorder.AudioSource.VOICE_DOWNLINK 下行声音
MediaRecorder.AudioSource.VOICE_RECOGNITION 语音识别
MediaRecorder.AudioSource.VOICE_UPLINK 上行声音
MediaRecorder.VideoEncoder

通过setVideoEncoder(int)来设置视频编码格式。
default: 默认编码
H263: H.263 多用于视频传输,其优点是压缩后体积小,占用带宽少;
MPEG_4_SP: 码率低代表它无需高码率即可有很好的视频效果,H264就更好了
H264 也是用于网络视频传输,优点也和H263差不多;再是H264会比前两者更优秀一点,不过一般用在标清或者高清压缩比较多。
VP8: 据说比H264优秀。
HEVC: 一种新的视频压缩标准。可以替代H.264/ AVC编码标准。它将在H.264标准2至4倍的复杂度基础上,将压缩效率提升一倍以上。

MediaRecorder.VideoSource

通过setVideoSource(int)方法,设置视频的来源。
CAMERA: 视频数据来源摄像头
DEFAULT: 系统默认
SURFACE: 视频数据来源于Surface

MediaRecorder.OutputFormat

通过setOutputFormat(int)方法来控制视频输出的格式:同理列举下各个参数的说明:
AAC_ADTS: ADTS的全称是Audio Data Transport Stream。是AAC音频的传输流格式。是AAC的一种非常常见的传输格式,
AMR_NB: 编码的是无视频纯声音3gp文件就是amr,他的文件比AAC的小,他的音乐效果没ACC的好
AMR_WB: VMR-WB 是新型可变速率多模式宽带语音编解码器,专为无线 CDMA 2000标准而设计,目的在于在 50 至 7000 HZ 的频带上进行语音编码,采样率为 16 KHZ。VMR-WB 基于 3GPP AMR-WB (G722.2) 编解码器,在每秒速率12.65 Kbit 上可实现互操作。
DEFAULT: 默认输出
MPEG_4: 这将指定录制的文件为mpeg-4格式,可以保护Audio和Video
RAW_AMR: 录制原始文件,这只支持音频录制,同时要求音频编码为AMR_NB
THREE_GPP: 录制后文件是一个3gp文件,支持音频和视频录制
WEBM: 编码为VP8/VORBIS的输出格式。
输出格式,大同小异,这里也没有做特别详细的讲解,将一下基本用法就可以了。一般情况下使用输出格式为MPEG_4的即可。

上述主要介绍了MediaRecorder的方法,作用以及部分参数的定义,下面进行MediaRecorder与SurfaceView结合使用并进行录制视频,视频格式为mp4。

MediaRecorder结合SurfaceView录制视频的步骤

  • SurfaceView与Camera进行绑定

    • 实现SurfaceHolder.Callback(他的生命周期有三个)回调
    • 在surfaceCreated中做相机的初始化操作
    • 在surfaceChanged中设置相机的相关参数
    • 在surfaceDestroyed中释放相机资源
  • 创建存放录制视频的相关路径

  • 初始化MediaRecorder

    • 释放Camera锁(Camera.unlock()),并设置MediaRecorder与Camera进行绑定
    • 设置MediaRecorder的相关参数
    • 录制前的准备
  • 停止录制

SurfaceView与Camera进行绑定

在SurfaceView与Camera进行绑定前一定要先取得holder即mSurfaceHolder = mSurfaceView.getHolder();并且保证屏幕常亮mSurfaceHolder.setKeepScreenOn(true)。
实现SurfaceHolder.Callback回调(mSurfaceHolder.addCallback(SurfaceHolder.Callback)),其回调有两种实现方式:

  • 直接在类上实现SurfaceHolder.Callback接口
  • 自定义一个Callback类去实现SurfaceHolder.Callback接口
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            if (mCamera == null) {
                openCamera();
            }
            if (null != mCamera) {
                mCamera.setPreviewDisplay(mSurfaceHolder);//Camera屏幕通过SurfaceHolder与SurfaceView 进行绑定
                mCamera.startPreview();//开始预览
            }
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this, "打开相机失败", Toast.LENGTH_SHORT).show();
        }
    }

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

        mScreenWidth = width;
        mScreenHeight = height;
        setCameraParameters();

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        releaseCameraResource();
    }
    /**
     * 打开相机
     */
    private void openCamera() {
        if (null != mCamera) {
            releaseCameraResource();
        }
        try {
            if (!checkCameraFacing(0) && !checkCameraFacing(1)) {
                Toast.makeText(MainActivity.this, "未发现有可用摄像头", Toast.LENGTH_SHORT).show();
                return;
            }
            if (!checkCameraFacing(mCameraPosition)) {
                Toast.makeText(MainActivity.this, mCameraPosition == 0 ? "后置摄像头不可用" : "前置摄像头不可用", Toast.LENGTH_SHORT).show();
                return;
            }
            mCamera = Camera.open(mCameraPosition);
//            mCamera = Camera.open(0);
        } catch (Exception e) {
            e.printStackTrace();
            releaseCameraResource();
        }
    }

    /**
     * 检查是否有摄像头
     *
     * @param facing 前置还是后置
     * @return
     */
    private boolean checkCameraFacing(int facing) {
        int cameraCount = Camera.getNumberOfCameras();
        Camera.CameraInfo info = new Camera.CameraInfo();
        for (int i = 0; i < cameraCount; i++) {
            Camera.getCameraInfo(i, info);
            if (facing == info.facing) {
                return true;
            }
        }
        return false;
    }

    //设置相机参数
    private void setCameraParameters(){
        try {
//            mCamera = Camera.open();// 打开摄像头
            if (mCamera == null)
                return;
//            mCamera.setDisplayOrientation(90);//将展示方向旋转90度
//            mCamera.setPreviewDisplay(mSurfaceHolder);//Surface 预览
            //可以通过获取相机的参数实例,设置里面各种效果,包括刚刚的预览图,前置摄像头,闪光灯等
            mParameters = mCamera.getParameters();// 获得相机参数

//            //设置图片格式
//            mParameters.setPictureFormat(ImageFormat.JPEG);
//            mParameters.setJpegQuality(100);
//            mParameters.setJpegThumbnailQuality(100);

//            mParameters.setPictureFormat(PixelFormat.JPEG);//设定图片格式为JPEG 默认为NV21
//            mParameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);//设置预览版式为YCbCr_420_SP 默认为NV21

            //该方法返回了SurfaceView的宽与高,根据给出的尺寸与宽高比例,获取一个最适配的预览尺寸
            List<Camera.Size> mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes();
            List<Camera.Size> mSupportedVideoSizes = mParameters.getSupportedVideoSizes();
            mOptimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
                    mSupportedPreviewSizes, mScreenWidth, mScreenHeight);
            //该方法是获取最佳的预览与摄像尺寸。然后设置预览图像大小
            mParameters.setPreviewSize(mOptimalSize.width, mOptimalSize.height); // 设置预览图像大小

            mCamera.setDisplayOrientation(getDegree());
//            if(this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE){
//                //如果是竖屏
////                mParameters.set("orientation", "portrait");
//                Log.e("lu","我是竖屏............");
//                //在2.2以上可以使用
//                mCamera.setDisplayOrientation(90);
//            }else{
////                mParameters.set("orientation", "landscape");
//                Log.e("lu","我是横屏............");
//                //在2.2以上可以使用
//                mCamera.setDisplayOrientation(0);
//            }

            List<String> focusModes = mParameters.getSupportedFocusModes();
            if (focusModes.contains("continuous-video")) {
                mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            }
            mFpsRange =  mParameters.getSupportedPreviewFpsRange();

            List<String> modes = mParameters.getSupportedFocusModes();
            if (modes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                //支持自动聚焦模式
                mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            }

            mCamera.setParameters(mParameters);// 设置相机参数
//            mCamera.startPreview();// 开始预览


            //假设要支持自动对焦功能,则在需要的情况下,或者在上述surfaceChanged调用完startPreview函数后,可以调用Camera::autoFocus函数来设置自动对焦回调函数,该步是可选操作,有些设备可能不支持,可以通过Camera::getFocusMode函数查询。代码可以参考如下:
            // 自动对焦
//            mCamera.autoFocus(new Camera.AutoFocusCallback(){
//                @Override
//                public void onAutoFocus(boolean success, Camera camera){
//                    if (success){
//                        // success为true表示对焦成功,改变对焦状态图像
//                        ivFocus.setImageResource(R.drawable.focus2);
//                    }
//                }
//            });


        }catch (Exception io){
            io.printStackTrace();
        }
    }


    private int getDegree() {
        //获取当前屏幕旋转的角度
        int rotating = this.getWindowManager().getDefaultDisplay().getRotation();
        int degree = 0;//度数
        //根据手机旋转的角度,来设置surfaceView的显示的角度
        switch (rotating) {
            case Surface.ROTATION_0:
                degree = 90;
                break;
            case Surface.ROTATION_90:
                degree = 0;
                break;
            case Surface.ROTATION_180:
                degree = 270;
                break;
            case Surface.ROTATION_270:
                degree = 180;
                break;
        }
        return degree;
    }

    /**
     * 释放摄像头资源
     */
    private void releaseCameraResource() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.lock();
            mCamera.release();
            mCamera = null;
        }
    }

最优尺寸

    /**
     * 这两个队列分别是 该相机支持的 预览大小(一般就是拍照时照片的大小),另外一个就是支持适配的大小,
     * 因为都是队列,说明相机支持很多组尺寸,而且,照片的尺寸与视频的尺寸是不一样的。我debug看了几款手机,
     * 通常摄像支持的尺寸少一点,照片会多一些。这样,我们就要通过刚刚方法给出的宽高,
     * 获取一个最佳匹配的预览尺寸.
     *
     * @param supportedVideoSizes Supported camera video sizes.
     * @param previewSizes Supported camera preview sizes.
     * @param w     The width of the view.
     * @param h     The height of the view.
     * @return Best match camera video size to fit in the view.
     */
    public static Camera.Size getOptimalVideoSize(List<Camera.Size> supportedVideoSizes,
                                                  List<Camera.Size> previewSizes, int w, int h) {
        // Use a very small tolerance because we want an exact match.
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;

        // Supported video sizes list might be null, it means that we are allowed to use the preview
        // sizes
        List<Camera.Size> videoSizes;
        if (supportedVideoSizes != null) {
            videoSizes = supportedVideoSizes;
        } else {
            videoSizes = previewSizes;
        }
        Camera.Size optimalSize = null;

        // Start with max value and refine as we iterate over available video sizes. This is the
        // minimum difference between view and camera height.
        double minDiff = Double.MAX_VALUE;

        // Target view height
        int targetHeight = h;

        // Try to find a video size that matches aspect ratio and the target view size.
        // Iterate over all available sizes and pick the largest size that can fit in the view and
        // still maintain the aspect ratio.
        for (Camera.Size size : videoSizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;
            if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        // Cannot find video size that matches the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : videoSizes) {
                if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

上述就是SurfaceView与Camera绑定,Camera初始化,Camera参数设置,Camera资源释放。

保存录制视频的路径

     /**
     * 创建目录与文件
     */
    private void createRecordDir() {
        mDirName = String.valueOf(System.currentTimeMillis()) +  String.valueOf( new Random().nextInt(1000));
        File FileDir = new File(BASE_PATH + mDirName);
        if (!FileDir.exists()) {
            FileDir.mkdirs();
        }
        // 创建文件
        try {
            mVecordFile = new File(FileDir.getAbsolutePath() + "/" + Utils.getDateNumber() +".mp4");
            Log.e("Path:", mVecordFile.getAbsolutePath());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

初始化MediaRecorder 并 设置MediaRecorder的参数

    /**
     * 录制前,初始化
     */
    private void initRecord() {
        try {
            //进入一个预览的拍摄页面了,该页面其实也可以用来做拍照。
            // 要想做拍摄,还要实例化MediaRecorder,然后传入camera并初始化相应的参数。
            if(mMediaRecorder == null){
                mMediaRecorder = new MediaRecorder();
            }
            if(mCamera != null){
                mCamera.unlock();
                mMediaRecorder.setCamera(mCamera);
            }

            mMediaRecorder.setOnErrorListener(this);

//            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//音频源  麦克风
//            mMediaRecorder.setAudioChannels(1);//单声道
//            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//音频格式

            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);//音频源  麦克风
            // 设置录制视频源为Camera(相机)
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//视频源
//            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//视频输出格式
//            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);//视频录制格式
            mMediaRecorder.setOrientationHint(90);//视频旋转90度

            // Use the same size for recording profile.
            CamcorderProfile mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
            mProfile.videoFrameWidth = mOptimalSize.width;
            mProfile.videoFrameHeight = mOptimalSize.height;
//
            mMediaRecorder.setProfile(mProfile);
            //该设置是为了抽取视频的某些帧,真正录视频的时候,不要设置该参数
//            mMediaRecorder.setCaptureRate(mFpsRange.get(0)[0]);//获取最小的每一秒录制的帧数


//            // 设置视频录制的分辨率。必须放在设置编码和格式的后面,否则报错,而且这个值要适配
//            //手机,不然也会在后面stop方法报错!
//            mMediaRecorder.setVideoSize(1280,720);
//            // 设置录制的视频帧率。必须放在设置编码和格式的后面,否则报错,这样设置变清晰
//            mMediaRecorder.setVideoEncodingBitRate(10*1024*1024);

            mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());

            mMediaRecorder.prepare();
            mMediaRecorder.start();
        } catch (Exception e) {
            e.printStackTrace();
            releaseRecord();
        }
    }

开始录制视频

开始录制视频时并计时,到达制定时间就停止录制。

    /**
     * 开始录制视频
     */
    public void startRecord(final OnRecordFinishListener onRecordFinishListener) {
        this.mOnRecordFinishListener = onRecordFinishListener;
        isStarting = true;
        lay_tool.setVisibility(View.INVISIBLE);
        tag_start.setVisibility(View.VISIBLE);
        anim.start();
        createRecordDir();
        try {
            initRecord();
            mTimeCount = 0;// 时间计数器重新赋值
            mTimer = new Timer();
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    mTimeCount++;
                    mProgressBar.setProgress(mTimeCount);
                    if (mTimeCount == mRecordMaxTime) {// 达到指定时间,停止拍摄
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                stop();
                                if (mOnRecordFinishListener != null){
                                    mOnRecordFinishListener.onRecordFinish();
                                }
                            }
                        });
                    }
                }
            };
            mTimer.schedule(timerTask, 0, 100);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

停止录制视频

停止录制视频时一定要释放视频资源及相机资源

    /**
     * 停止拍摄
     */
    public void stop() {
        stopRecord();
        releaseRecord();
        releaseCameraResource();
    }

    /**
     * 停止录制
     */
    public void stopRecord() {
        mProgressBar.setProgress(0);
        isStarting = false;
        tag_start.setVisibility(View.GONE);
        anim.stop();
        lay_tool.setVisibility(View.VISIBLE);
        if(timerTask != null)
            timerTask.cancel();
        if (mTimer != null)
            mTimer.cancel();
        if (mMediaRecorder != null) {
            try {
                mMediaRecorder.stop();
                mMediaRecorder.reset();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (RuntimeException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

释放MediaRecorder资源

    /**
     * 释放资源
     */
    private void releaseRecord() {
        if (mMediaRecorder != null) {
            mMediaRecorder.setPreviewDisplay(null);
            mMediaRecorder.setOnErrorListener(null);
            try {
                mMediaRecorder.release();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mMediaRecorder = null;
    }

闪光灯关闭与开启

    //闪光灯关闭与开启
    private void flashLightToggle(){
        try {
            if(isFlashLightOn){
                mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);//关闭闪光灯
                mCamera.setParameters(mParameters);
                isFlashLightOn = false;
            }else {
                mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);//开启闪光灯
                mCamera.setParameters(mParameters);
                isFlashLightOn = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

前后摄像头切换

前后摄像头切换的代码还可以简化的,这里是没有简化的,大家看得懂就可以了

    //前后摄像头切换,就要重新初始化 camera实例
    private void switchCamera(){
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        int cameraCount = Camera.getNumberOfCameras();//得到摄像头的个数

        for(int i = 0; i < cameraCount; i++ ) {
            Camera.getCameraInfo(i, cameraInfo);//得到每一个摄像头的信息
            if(mCameraPosition == 1) {
                //现在是后置,变更为前置
                if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_FRONT) {//代表摄像头的方位,CAMERA_FACING_FRONT前置      CAMERA_FACING_BACK后置
                    mCamera.stopPreview();//停掉原来摄像头的预览
                    mCamera.release();//释放资源
                    mCamera = null;//取消原来摄像头
                    mCamera = Camera.open(i);//打开当前选中的摄像头
                    try {
                        mCamera.setDisplayOrientation(90);// 打开摄像头并将展示方向旋转90度
                        mCamera.setPreviewDisplay(mSurfaceHolder);//通过surfaceview显示取景画面
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    mCamera.setParameters(mParameters);// 设置相机参数
                    mCamera.startPreview();//开始预览
                    mCameraPosition = 0;
                    break;
                }
            } else {
                //现在是前置, 变更为后置
                if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_BACK) {//代表摄像头的方位,CAMERA_FACING_FRONT前置      CAMERA_FACING_BACK后置
                    mCamera.stopPreview();//停掉原来摄像头的预览
                    mCamera.release();//释放资源
                    mCamera = null;//取消原来摄像头
                    mCamera = Camera.open(i);//打开当前选中的摄像头
                    try {
                        mCamera.setDisplayOrientation(90);// 打开摄像头并将展示方向旋转90度
                        mCamera.setPreviewDisplay(mSurfaceHolder);//通过surfaceview显示取景画面
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    mCamera.setParameters(mParameters);// 设置相机参数
                    mCamera.startPreview();//开始预览
                    mCameraPosition = 1;
                    break;
                }
            }

        }
    }

添加权限

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    <!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />

总结

  • 要注意让SurfaceView保持常亮状态mSurfaceHolder.setKeepScreenOn(true)
  • Camera屏幕通过SurfaceHolder与SurfaceView 进行绑定
  • MediaRecorder设置参数时一定要注意部分参数设置的顺序,不然会报错。
  • 如果兼容横竖屏,注意相机方向与SufaceView的方向,视频旋转角度问题
  • 音频设置时首选AAC就行了,如果录音被抢占了释放掉或者选default就不会出现这种问题。
  • 一定不要忘记添加权限
  • 视频编码格式:default,H263,H264,MPEG_4_SP
  • 获得视频资源:default,CAMERA
  • 音频编码格式:default,AAC,AMR_NB,AMR_WB,
  • 获得音频资源:defalut,camcorder,mic,voice_call,voice_communication,voice_downlink, voice_recognition, voice_uplink;
  • 输出方式:amr_nb,amr_wb,default,mpeg_4,raw_amr,three_gpp.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容