MediaRecorder結(jié)合SurfaceView錄制視頻

手機(jī)一般都有麥克風(fēng)和攝像頭,而Android系統(tǒng)就可以利用這些硬件來錄制音視頻了飞崖。為了增加對錄制音視頻的支持慰毅,Android系統(tǒng)提供了一個MediaRecorder的類。使用MediaRecorder結(jié)合SurfaceView錄制視頻嗅虏。下面先簡單了解一下MediaRecorder這個類洛姑。

MediaRecorder應(yīng)用實(shí)例

  • 使用MediaRecorder錄制音樂
  • 使用MediaRecorder錄制視頻

MediaRecorder功能設(shè)置(方法/作用)

  • getAudioSourceMax() 獲取音頻信號源的最高值。
  • getMaxAmplitude() 最后調(diào)用這個方法采樣的時候返回最大振幅的絕對值
  • getMetrics() 返回當(dāng)前Mediacorder測量的數(shù)據(jù)
  • getSurface() 當(dāng)使用Surface作為視頻源的時候皮服,返回Sufrace對象
  • pause() 暫停錄制
  • prepare() 準(zhǔn)備錄制
  • resume() 恢復(fù)錄制
  • release() 釋放與此MediaRecorder對象關(guān)聯(lián)的資源
  • reset() 重新啟動mediarecorder到空閑狀態(tài)
  • setAudioChannels(int numChannels) 設(shè)置錄制的音頻通道數(shù)
  • setAudioEncoder(int audio_encoder) 設(shè)置audio的編碼格式
  • setAudioEncodingBitRate(int bitRate) 設(shè)置錄制的音頻編碼比特率
  • setAudioSamplingRate(int samplingRate) 設(shè)置錄制的音頻采樣率
  • setAudioSource(int audio_source) 設(shè)置用于錄制的音源
  • setAuxiliaryOutputFile(String path) 輔助時間的推移視頻文件的路徑傳遞
  • setAuxiliaryOutputFile(FileDescriptor fd) 在文件描述符傳遞的輔助時間的推移視頻
  • setCamera(Camera c) 設(shè)置一個recording的攝像頭楞艾,此方法在API21被遺棄,被getSurface替代
  • setCaptureRate(double fps) 設(shè)置視頻幀的捕獲率
  • setInputSurface(Surface surface) 設(shè)置持續(xù)的視頻數(shù)據(jù)來源
  • setMaxDuration(int max_duration_ms) 設(shè)置記錄會話的最大持續(xù)時間(毫秒)
  • setMaxFileSize(long max_filesize_bytes) 設(shè)置記錄會話的最大大辛涔恪(以字節(jié)為單位)
  • setOutputFile(FileDescriptor fd) 傳遞要寫入的文件的文件描述符
  • setOutputFile(String path) 設(shè)置輸出文件的路徑
  • setOutputFormat(int output_format) 設(shè)置在錄制過程中產(chǎn)生的輸出文件的格式
  • setPreviewDisplay(Surface sv) 表面設(shè)置顯示記錄媒體(視頻)的預(yù)覽
  • setVideoEncoder(int video_encoder) 設(shè)置視頻編碼器硫眯,用于錄制
  • setVideoEncodingBitRate(int bitRate) 設(shè)置錄制的視頻編碼比特率
  • setVideoFrameRate(int rate) 設(shè)置要捕獲的視頻幀速率
  • setVideoSize(int width, int height) 設(shè)置要捕獲的視頻的寬度和高度
  • setVideoSource(int video_source) 開始捕捉和編碼數(shù)據(jù)到setOutputFile(指定的文件)
  • setLocation(float latitude, float longitude) 設(shè)置并存儲在輸出文件中的地理數(shù)據(jù)(經(jīng)度和緯度)
  • setProfile(CamcorderProfile profile) 指定CamcorderProfile對象
  • setOrientationHint(int degrees) 設(shè)置輸出的視頻播放的方向提示
  • setOnErrorListener(MediaRecorder.OnErrorListener l) 注冊一個用于記錄錄制時出現(xiàn)的錯誤的監(jiān)聽器
  • setOnInfoListener(MediaRecorder.OnInfoListener listener) 注冊一個用于記錄錄制時出現(xiàn)的信息事件

MediaRecorder內(nèi)的嵌套類

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

MediaRecorder.AudioEncoder

大家都知道在錄音的時候都要調(diào)用setAudioEncoder()方法,這個方法里面總有不同的參數(shù)择同,這個類就是參數(shù)的值两入,這里說一下各個不同值的區(qū)別:

default: 默認(rèn)值。
AAC: 高級音頻編碼敲才,簡單說下優(yōu)缺點(diǎn):

AAC優(yōu)點(diǎn):相對于mp3裹纳,AAC格式的音質(zhì)更佳,文件更小紧武。
AAC不足:AAC屬于有損壓縮的格式剃氧,與時下流行的APE、FLAC等無損格式相比音質(zhì)存在”本質(zhì)上”的差距阻星。加之她我,傳輸速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC頭上”小巧”的光環(huán)不復(fù)存在迫横。

HE_AAC: HE-AAC混合了AAC與SBR技術(shù)番舆。
AAC_ELD: 低延時的AAC音頻編解碼器。
AMR_NB: 編碼的是無視頻純聲音3gp文件就是amr,他的文件比AAC的小矾踱,音樂效果沒ACC的好恨狈。
AMR_WB: VMR-WB 是新型可變速率多模式寬帶語音編解碼器,專為無線 CDMA 2000標(biāo)準(zhǔn)而設(shè)計呛讲,目的在于在 50 至 7000 HZ 的頻帶上進(jìn)行語音編碼禾怠,采樣率為 16 KHZ返奉。VMR-WB 基于 3GPP AMR-WB (G722.2) 編解碼器,在每秒速率12.65 Kbit 上可實(shí)現(xiàn)互操作吗氏。
VORBIS: Vorbis是一種新的音頻壓縮格式芽偏,類似于MP3等現(xiàn)有的音樂格式。但有一點(diǎn)不同的是弦讽,它是完全免費(fèi)污尉、開放和沒有專利限制的。OGG Vorbis有一個很出眾的特點(diǎn)往产,就是支持多聲道被碗,隨著它的流行驳阎,以后用隨身聽來聽DTS編碼的多聲道作品將不會是夢想木柬。

MediaRecorder.AudioSource

這個類對應(yīng)setAudioSource(int) 方法,主要用來設(shè)置音頻源荆姆; MediaRecorder.AudioSource音頻參數(shù)說明如下:
MediaRecorder.AudioSource.CAMCORDER 設(shè)定錄音來源于同方向的相機(jī)麥克風(fēng)相同蔼囊,若相機(jī)無內(nèi)置相機(jī)或無法識別焚志,則使用預(yù)設(shè)的麥克風(fēng)
MediaRecorder.AudioSource.DEFAULT 默認(rèn)音頻源
MediaRecorder.AudioSource.MIC 設(shè)定錄音來源為主麥克風(fēng)。
MediaRecorder.AudioSource.VOICE_CALL設(shè)定錄音來源為語音撥出的語音與對方說話的聲音
MediaRecorder.AudioSource.VOICE_COMMUNICATION 攝像頭旁邊的麥克風(fēng)
MediaRecorder.AudioSource.VOICE_DOWNLINK 下行聲音
MediaRecorder.AudioSource.VOICE_RECOGNITION 語音識別
MediaRecorder.AudioSource.VOICE_UPLINK 上行聲音
MediaRecorder.VideoEncoder

通過setVideoEncoder(int)來設(shè)置視頻編碼格式畏鼓。
default: 默認(rèn)編碼
H263: H.263 多用于視頻傳輸娩嚼,其優(yōu)點(diǎn)是壓縮后體積小,占用帶寬少滴肿;
MPEG_4_SP: 碼率低代表它無需高碼率即可有很好的視頻效果岳悟,H264就更好了
H264 也是用于網(wǎng)絡(luò)視頻傳輸,優(yōu)點(diǎn)也和H263差不多泼差;再是H264會比前兩者更優(yōu)秀一點(diǎn)贵少,不過一般用在標(biāo)清或者高清壓縮比較多。
VP8: 據(jù)說比H264優(yōu)秀堆缘。
HEVC: 一種新的視頻壓縮標(biāo)準(zhǔn)滔灶。可以替代H.264/ AVC編碼標(biāo)準(zhǔn)吼肥。它將在H.264標(biāo)準(zhǔn)2至4倍的復(fù)雜度基礎(chǔ)上录平,將壓縮效率提升一倍以上。

MediaRecorder.VideoSource

通過setVideoSource(int)方法缀皱,設(shè)置視頻的來源斗这。
CAMERA: 視頻數(shù)據(jù)來源攝像頭
DEFAULT: 系統(tǒng)默認(rèn)
SURFACE: 視頻數(shù)據(jù)來源于Surface

MediaRecorder.OutputFormat

通過setOutputFormat(int)方法來控制視頻輸出的格式:同理列舉下各個參數(shù)的說明:
AAC_ADTS: ADTS的全稱是Audio Data Transport Stream。是AAC音頻的傳輸流格式啤斗。是AAC的一種非常常見的傳輸格式表箭,
AMR_NB: 編碼的是無視頻純聲音3gp文件就是amr,他的文件比AAC的小,他的音樂效果沒ACC的好
AMR_WB: VMR-WB 是新型可變速率多模式寬帶語音編解碼器钮莲,專為無線 CDMA 2000標(biāo)準(zhǔn)而設(shè)計免钻,目的在于在 50 至 7000 HZ 的頻帶上進(jìn)行語音編碼彼水,采樣率為 16 KHZ。VMR-WB 基于 3GPP AMR-WB (G722.2) 編解碼器极舔,在每秒速率12.65 Kbit 上可實(shí)現(xiàn)互操作凤覆。
DEFAULT: 默認(rèn)輸出
MPEG_4: 這將指定錄制的文件為mpeg-4格式,可以保護(hù)Audio和Video
RAW_AMR: 錄制原始文件拆魏,這只支持音頻錄制盯桦,同時要求音頻編碼為AMR_NB
THREE_GPP: 錄制后文件是一個3gp文件,支持音頻和視頻錄制
WEBM: 編碼為VP8/VORBIS的輸出格式稽揭。
輸出格式,大同小異肥卡,這里也沒有做特別詳細(xì)的講解溪掀,將一下基本用法就可以了。一般情況下使用輸出格式為MPEG_4的即可步鉴。

上述主要介紹了MediaRecorder的方法揪胃,作用以及部分參數(shù)的定義,下面進(jìn)行MediaRecorder與SurfaceView結(jié)合使用并進(jìn)行錄制視頻氛琢,視頻格式為mp4喊递。

MediaRecorder結(jié)合SurfaceView錄制視頻的步驟

  • SurfaceView與Camera進(jìn)行綁定

    • 實(shí)現(xiàn)SurfaceHolder.Callback(他的生命周期有三個)回調(diào)
    • 在surfaceCreated中做相機(jī)的初始化操作
    • 在surfaceChanged中設(shè)置相機(jī)的相關(guān)參數(shù)
    • 在surfaceDestroyed中釋放相機(jī)資源
  • 創(chuàng)建存放錄制視頻的相關(guān)路徑

  • 初始化MediaRecorder

    • 釋放Camera鎖(Camera.unlock()),并設(shè)置MediaRecorder與Camera進(jìn)行綁定
    • 設(shè)置MediaRecorder的相關(guān)參數(shù)
    • 錄制前的準(zhǔn)備
  • 停止錄制

SurfaceView與Camera進(jìn)行綁定

在SurfaceView與Camera進(jìn)行綁定前一定要先取得holder即mSurfaceHolder = mSurfaceView.getHolder()阳似;并且保證屏幕常亮mSurfaceHolder.setKeepScreenOn(true)骚勘。
實(shí)現(xiàn)SurfaceHolder.Callback回調(diào)(mSurfaceHolder.addCallback(SurfaceHolder.Callback)),其回調(diào)有兩種實(shí)現(xiàn)方式:

  • 直接在類上實(shí)現(xiàn)SurfaceHolder.Callback接口
  • 自定義一個Callback類去實(shí)現(xiàn)SurfaceHolder.Callback接口
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            if (mCamera == null) {
                openCamera();
            }
            if (null != mCamera) {
                mCamera.setPreviewDisplay(mSurfaceHolder);//Camera屏幕通過SurfaceHolder與SurfaceView 進(jìn)行綁定
                mCamera.startPreview();//開始預(yù)覽
            }
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this, "打開相機(jī)失敗", 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();
    }
    /**
     * 打開相機(jī)
     */
    private void openCamera() {
        if (null != mCamera) {
            releaseCameraResource();
        }
        try {
            if (!checkCameraFacing(0) && !checkCameraFacing(1)) {
                Toast.makeText(MainActivity.this, "未發(fā)現(xiàn)有可用攝像頭", 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;
    }

    //設(shè)置相機(jī)參數(shù)
    private void setCameraParameters(){
        try {
//            mCamera = Camera.open();// 打開攝像頭
            if (mCamera == null)
                return;
//            mCamera.setDisplayOrientation(90);//將展示方向旋轉(zhuǎn)90度
//            mCamera.setPreviewDisplay(mSurfaceHolder);//Surface 預(yù)覽
            //可以通過獲取相機(jī)的參數(shù)實(shí)例撮奏,設(shè)置里面各種效果俏讹,包括剛剛的預(yù)覽圖,前置攝像頭畜吊,閃光燈等
            mParameters = mCamera.getParameters();// 獲得相機(jī)參數(shù)

//            //設(shè)置圖片格式
//            mParameters.setPictureFormat(ImageFormat.JPEG);
//            mParameters.setJpegQuality(100);
//            mParameters.setJpegThumbnailQuality(100);

//            mParameters.setPictureFormat(PixelFormat.JPEG);//設(shè)定圖片格式為JPEG 默認(rèn)為NV21
//            mParameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);//設(shè)置預(yù)覽版式為YCbCr_420_SP 默認(rèn)為NV21

            //該方法返回了SurfaceView的寬與高泽疆,根據(jù)給出的尺寸與寬高比例,獲取一個最適配的預(yù)覽尺寸
            List<Camera.Size> mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes();
            List<Camera.Size> mSupportedVideoSizes = mParameters.getSupportedVideoSizes();
            mOptimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
                    mSupportedPreviewSizes, mScreenWidth, mScreenHeight);
            //該方法是獲取最佳的預(yù)覽與攝像尺寸玲献。然后設(shè)置預(yù)覽圖像大小
            mParameters.setPreviewSize(mOptimalSize.width, mOptimalSize.height); // 設(shè)置預(yù)覽圖像大小

            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);// 設(shè)置相機(jī)參數(shù)
//            mCamera.startPreview();// 開始預(yù)覽


            //假設(shè)要支持自動對焦功能殉疼,則在需要的情況下,或者在上述surfaceChanged調(diào)用完startPreview函數(shù)后捌年,可以調(diào)用Camera::autoFocus函數(shù)來設(shè)置自動對焦回調(diào)函數(shù)瓢娜,該步是可選操作,有些設(shè)備可能不支持礼预,可以通過Camera::getFocusMode函數(shù)查詢恋腕。代碼可以參考如下:
            // 自動對焦
//            mCamera.autoFocus(new Camera.AutoFocusCallback(){
//                @Override
//                public void onAutoFocus(boolean success, Camera camera){
//                    if (success){
//                        // success為true表示對焦成功,改變對焦?fàn)顟B(tài)圖像
//                        ivFocus.setImageResource(R.drawable.focus2);
//                    }
//                }
//            });


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


    private int getDegree() {
        //獲取當(dāng)前屏幕旋轉(zhuǎn)的角度
        int rotating = this.getWindowManager().getDefaultDisplay().getRotation();
        int degree = 0;//度數(shù)
        //根據(jù)手機(jī)旋轉(zhuǎn)的角度逆瑞,來設(shè)置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;
        }
    }

最優(yōu)尺寸

    /**
     * 這兩個隊列分別是 該相機(jī)支持的 預(yù)覽大熊佟(一般就是拍照時照片的大谢锏ァ),另外一個就是支持適配的大小哈肖,
     * 因?yàn)槎际顷犃形怯f明相機(jī)支持很多組尺寸,而且淤井,照片的尺寸與視頻的尺寸是不一樣的布疼。我debug看了幾款手機(jī),
     * 通常攝像支持的尺寸少一點(diǎn)币狠,照片會多一些游两。這樣,我們就要通過剛剛方法給出的寬高漩绵,
     * 獲取一個最佳匹配的預(yù)覽尺寸.
     *
     * @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參數(shù)設(shè)置止吐,Camera資源釋放宝踪。

保存錄制視頻的路徑

     /**
     * 創(chuàng)建目錄與文件
     */
    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();
        }
        // 創(chuàng)建文件
        try {
            mVecordFile = new File(FileDir.getAbsolutePath() + "/" + Utils.getDateNumber() +".mp4");
            Log.e("Path:", mVecordFile.getAbsolutePath());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

初始化MediaRecorder 并 設(shè)置MediaRecorder的參數(shù)

    /**
     * 錄制前,初始化
     */
    private void initRecord() {
        try {
            //進(jìn)入一個預(yù)覽的拍攝頁面了碍扔,該頁面其實(shí)也可以用來做拍照瘩燥。
            // 要想做拍攝,還要實(shí)例化MediaRecorder不同,然后傳入camera并初始化相應(yīng)的參數(shù)厉膀。
            if(mMediaRecorder == null){
                mMediaRecorder = new MediaRecorder();
            }
            if(mCamera != null){
                mCamera.unlock();
                mMediaRecorder.setCamera(mCamera);
            }

            mMediaRecorder.setOnErrorListener(this);

//            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//音頻源  麥克風(fēng)
//            mMediaRecorder.setAudioChannels(1);//單聲道
//            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//音頻格式

            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);//音頻源  麥克風(fēng)
            // 設(shè)置錄制視頻源為Camera(相機(jī))
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//視頻源
//            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//視頻輸出格式
//            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);//視頻錄制格式
            mMediaRecorder.setOrientationHint(90);//視頻旋轉(zhuǎn)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);
            //該設(shè)置是為了抽取視頻的某些幀,真正錄視頻的時候二拐,不要設(shè)置該參數(shù)
//            mMediaRecorder.setCaptureRate(mFpsRange.get(0)[0]);//獲取最小的每一秒錄制的幀數(shù)


//            // 設(shè)置視頻錄制的分辨率站蝠。必須放在設(shè)置編碼和格式的后面,否則報錯,而且這個值要適配
//            //手機(jī)卓鹿,不然也會在后面stop方法報錯菱魔!
//            mMediaRecorder.setVideoSize(1280,720);
//            // 設(shè)置錄制的視頻幀率。必須放在設(shè)置編碼和格式的后面吟孙,否則報錯澜倦,這樣設(shè)置變清晰
//            mMediaRecorder.setVideoEncodingBitRate(10*1024*1024);

            mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());

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

開始錄制視頻

開始錄制視頻時并計時,到達(dá)制定時間就停止錄制杰妓。

    /**
     * 開始錄制視頻
     */
    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;// 時間計數(shù)器重新賦值
            mTimer = new Timer();
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    mTimeCount++;
                    mProgressBar.setProgress(mTimeCount);
                    if (mTimeCount == mRecordMaxTime) {// 達(dá)到指定時間藻治,停止拍攝
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                stop();
                                if (mOnRecordFinishListener != null){
                                    mOnRecordFinishListener.onRecordFinish();
                                }
                            }
                        });
                    }
                }
            };
            mTimer.schedule(timerTask, 0, 100);

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

停止錄制視頻

停止錄制視頻時一定要釋放視頻資源及相機(jī)資源

    /**
     * 停止拍攝
     */
    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;
    }

閃光燈關(guān)閉與開啟

    //閃光燈關(guān)閉與開啟
    private void flashLightToggle(){
        try {
            if(isFlashLightOn){
                mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);//關(guān)閉閃光燈
                mCamera.setParameters(mParameters);
                isFlashLightOn = false;
            }else {
                mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);//開啟閃光燈
                mCamera.setParameters(mParameters);
                isFlashLightOn = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

前后攝像頭切換

前后攝像頭切換的代碼還可以簡化的,這里是沒有簡化的巷挥,大家看得懂就可以了

    //前后攝像頭切換桩卵,就要重新初始化 camera實(shí)例
    private void switchCamera(){
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        int cameraCount = Camera.getNumberOfCameras();//得到攝像頭的個數(shù)

        for(int i = 0; i < cameraCount; i++ ) {
            Camera.getCameraInfo(i, cameraInfo);//得到每一個攝像頭的信息
            if(mCameraPosition == 1) {
                //現(xiàn)在是后置,變更為前置
                if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_FRONT) {//代表攝像頭的方位,CAMERA_FACING_FRONT前置      CAMERA_FACING_BACK后置
                    mCamera.stopPreview();//停掉原來攝像頭的預(yù)覽
                    mCamera.release();//釋放資源
                    mCamera = null;//取消原來攝像頭
                    mCamera = Camera.open(i);//打開當(dāng)前選中的攝像頭
                    try {
                        mCamera.setDisplayOrientation(90);// 打開攝像頭并將展示方向旋轉(zhuǎn)90度
                        mCamera.setPreviewDisplay(mSurfaceHolder);//通過surfaceview顯示取景畫面
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    mCamera.setParameters(mParameters);// 設(shè)置相機(jī)參數(shù)
                    mCamera.startPreview();//開始預(yù)覽
                    mCameraPosition = 0;
                    break;
                }
            } else {
                //現(xiàn)在是前置雏节, 變更為后置
                if(cameraInfo.facing  == Camera.CameraInfo.CAMERA_FACING_BACK) {//代表攝像頭的方位胜嗓,CAMERA_FACING_FRONT前置      CAMERA_FACING_BACK后置
                    mCamera.stopPreview();//停掉原來攝像頭的預(yù)覽
                    mCamera.release();//釋放資源
                    mCamera = null;//取消原來攝像頭
                    mCamera = Camera.open(i);//打開當(dāng)前選中的攝像頭
                    try {
                        mCamera.setDisplayOrientation(90);// 打開攝像頭并將展示方向旋轉(zhuǎn)90度
                        mCamera.setPreviewDisplay(mSurfaceHolder);//通過surfaceview顯示取景畫面
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    mCamera.setParameters(mParameters);// 設(shè)置相機(jī)參數(shù)
                    mCamera.startPreview();//開始預(yù)覽
                    mCameraPosition = 1;
                    break;
                }
            }

        }
    }

添加權(quán)限

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    <!-- 寫入擴(kuò)展存儲,向擴(kuò)展卡寫入數(shù)據(jù)钩乍,用于寫入離線定位數(shù)據(jù) -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />

總結(jié)

  • 要注意讓SurfaceView保持常亮狀態(tài)mSurfaceHolder.setKeepScreenOn(true)
  • Camera屏幕通過SurfaceHolder與SurfaceView 進(jìn)行綁定
  • MediaRecorder設(shè)置參數(shù)時一定要注意部分參數(shù)設(shè)置的順序辞州,不然會報錯。
  • 如果兼容橫豎屏寥粹,注意相機(jī)方向與SufaceView的方向变过,視頻旋轉(zhuǎn)角度問題
  • 音頻設(shè)置時首選AAC就行了,如果錄音被搶占了釋放掉或者選default就不會出現(xiàn)這種問題涝涤。
  • 一定不要忘記添加權(quán)限
  • 視頻編碼格式: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.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市还最,隨后出現(xiàn)的幾起案子墓阀,更是在濱河造成了極大的恐慌,老刑警劉巖拓轻,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斯撮,死亡現(xiàn)場離奇詭異,居然都是意外死亡扶叉,警方通過查閱死者的電腦和手機(jī)勿锅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枣氧,“玉大人溢十,你說我怎么就攤上這事〈锿蹋” “怎么了张弛?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我吞鸭,道長寺董,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任瞒大,我火速辦了婚禮螃征,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘透敌。我一直安慰自己盯滚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布酗电。 她就那樣靜靜地躺著魄藕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撵术。 梳的紋絲不亂的頭發(fā)上背率,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音嫩与,去河邊找鬼寝姿。 笑死,一個胖子當(dāng)著我的面吹牛划滋,可吹牛的內(nèi)容都是我干的饵筑。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼处坪,長吁一口氣:“原來是場噩夢啊……” “哼根资!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起同窘,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤玄帕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后想邦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裤纹,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年丧没,在試婚紗的時候發(fā)現(xiàn)自己被綠了鹰椒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡骂铁,死狀恐怖吹零,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拉庵,我是刑警寧澤灿椅,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響茫蛹,放射性物質(zhì)發(fā)生泄漏操刀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一婴洼、第九天 我趴在偏房一處隱蔽的房頂上張望骨坑。 院中可真熱鬧,春花似錦柬采、人聲如沸欢唾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽礁遣。三九已至,卻和暖如春肩刃,著一層夾襖步出監(jiān)牢的瞬間祟霍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工盈包, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沸呐,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓呢燥,卻偏偏與公主長得像崭添,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疮茄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容