美文网首页Android开发探索
MediaRecorder结合SurfaceView录制视频

MediaRecorder结合SurfaceView录制视频

作者: 安仔夏天勤奋 | 来源:发表于2018-08-07 16:37 被阅读82次

    手机一般都有麦克风和摄像头,而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.

    相关文章

      网友评论

        本文标题:MediaRecorder结合SurfaceView录制视频

        本文链接:https://www.haomeiwen.com/subject/niaovftx.html