Android音視頻錄制之MediaRecorder+camera

前言

本篇介紹使用Android 中視頻錄制,錄制工具是:

  • MediaRecorder : 視頻編碼封裝
  • camera : 視頻畫(huà)面原始數(shù)據(jù)采集
  • TextureView : 提供預(yù)覽畫(huà)面

MediaRecorder基本api介紹

MediaRecorder是android中面向應(yīng)用層的封裝,用于提供便捷的音視頻編碼封裝操作,在使用的過(guò)程中要嚴(yán)格按照官方指定的生命周期調(diào)用順序,即下圖所示的使用步驟

image.png

從圖中可以看出,MediaRecorder的生命周期有以下幾個(gè)階段,并且必須按順序執(zhí)行

  • initial : 在MediaRecorder被創(chuàng)建(剛new 出來(lái))或者滴啊用reset()方法后,會(huì)處于該狀態(tài)
  • initialized : 當(dāng)調(diào)用setAudioSource()或者setVideoSource()后,處于該狀態(tài)。這兩個(gè)方法主要用于設(shè)置音視頻的源配置,通常音頻是麥克風(fēng),視頻是攝像頭。該狀態(tài)可以通過(guò)調(diào)用reset()方法回到initial狀態(tài)
  • DataSourceConfigured : 當(dāng)調(diào)用setOutputFormat()方法后,會(huì)處于該狀態(tài)。該方法主要用于設(shè)置輸出的文件格式,可以是音視頻如MP4,也可以是單獨(dú)的音頻如mp3。當(dāng)處于該狀態(tài)之后,可以進(jìn)一步設(shè)置音頻和視頻的配置參數(shù),例如音頻封裝格式,采樣率,視頻碼率,幀率等等該狀態(tài)可以通過(guò)調(diào)用reset()方法回到initial狀態(tài)
  • Prepared : 在上面幾個(gè)步驟都配置好之后,可以通過(guò)調(diào)用prepare()方法進(jìn)入該狀態(tài),只有處于該狀態(tài),才能調(diào)用start()
  • Recording : 通過(guò)調(diào)用start()方法進(jìn)入該狀態(tài),該狀態(tài)就是真正開(kāi)始進(jìn)行視頻錄制編碼的階段,通過(guò)調(diào)用stop()或者reset()可以回到initial狀態(tài)
  • error狀態(tài) : 當(dāng)錄制過(guò)程中發(fā)生次錯(cuò)誤時(shí),會(huì)進(jìn)入該狀態(tài),調(diào)用reset()方法回到initial狀態(tài)。
  • release : 只有在initial狀態(tài)才可以通過(guò)調(diào)用release()方法進(jìn)入該狀態(tài),釋放所占用的系統(tǒng)資源

編碼的步驟

在開(kāi)始編碼之前,應(yīng)該先規(guī)劃一下編碼的步驟,因?yàn)殇浿埔曨l需要預(yù)覽畫(huà)面,所以我們肯定需要構(gòu)建一個(gè)預(yù)覽的界面,通過(guò)camera+TextureView可以實(shí)現(xiàn),音頻不需要預(yù)覽。構(gòu)建好預(yù)覽畫(huà)面之后,就是開(kāi)始配置MediaRecorder開(kāi)始錄制了。

配置Camera

demo只演示后置攝像頭的捕捉,因?yàn)槭褂肕ediaRecorder進(jìn)行錄制無(wú)法處理幀數(shù)據(jù),在切換前置攝像頭之后,視頻會(huì)出現(xiàn)左右翻轉(zhuǎn)的問(wèn)題,無(wú)法通過(guò)單純的旋轉(zhuǎn)解決,暫時(shí)還沒(méi)找到方法。

camera的配置重點(diǎn)

  • 預(yù)覽尺寸,預(yù)覽尺寸的寬高比應(yīng)該盡量和TextureView的寬高比一致,這樣可以保證畫(huà)面不變形;
  • 對(duì)焦模式,對(duì)焦模式一般首選FOCUS_MODE_CONTINUOUS_VIDEO,如果機(jī)型不包含,則選擇FOCUS_MODE_CONTINUOUS_PICTURE,如果還不包含,則選擇FOCUS_MODE_AUTO,然后通過(guò)手指點(diǎn)擊重新對(duì)焦(手指點(diǎn)擊TextureView,調(diào)用mCamera.autoFocus(null););
  • 預(yù)覽界面旋轉(zhuǎn),由于傳感器方向和手機(jī)自然方向不一致,所以需要調(diào)整預(yù)覽界面進(jìn)行一定的旋轉(zhuǎn),旋轉(zhuǎn)的角度大小可以通過(guò)google官方提供的方法計(jì)算,查看mCamera.setDisplayOrientation(mRotationDegree);方法源碼查看注釋里有這段代碼(shift/command+鼠標(biāo)左鍵點(diǎn)擊方法)

示例代碼

/**
     * 初始化相機(jī)
     */
    private void initCamera() {
        if (mSurfaceTexture == null) return;
        if (mCamera != null) {
            releaseCamera();
        }

        mCamera = Camera.open(mCameraId);
        if (mCamera == null) {
            Toast.makeText(this, "沒(méi)有可用相機(jī)", Toast.LENGTH_SHORT).show();
            return;
        }

        try {
            mCamera.setPreviewTexture(mSurfaceTexture);
            mRotationDegree = CameraUtil.getCameraDisplayOrientation(this, mCameraId);
            mCamera.setDisplayOrientation(mRotationDegree);
            setCameraParameter(mCamera);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 設(shè)置相機(jī)的參數(shù)
     *
     * @param camera
     */
    private void setCameraParameter(Camera camera) {
        if (camera == null) return;
        Camera.Parameters parameters = camera.getParameters();
        //獲取相機(jī)支持的>=20fps的幀率,用于設(shè)置給MediaRecorder
        //因?yàn)楂@取的數(shù)值是*1000的,所以要除以1000
        List<int[]> previewFpsRange = parameters.getSupportedPreviewFpsRange();
        for (int[] ints : previewFpsRange) {
            if (ints[0] >= 20000) {
                mFps = ints[0] / 1000;
                break;
            }
        }
        //設(shè)置聚焦模式
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        } else {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }


        //設(shè)置預(yù)覽尺寸,因?yàn)轭A(yù)覽的尺寸和最終是錄制視頻的尺寸無(wú)關(guān),所以我們選取最大的數(shù)值
        //通常最大的是手機(jī)的分辨率,這樣可以讓預(yù)覽畫(huà)面盡可能清晰并且尺寸不變形,前提是TextureView的尺寸是全屏或者接近全屏
        List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        parameters.setPreviewSize(supportedPreviewSizes.get(0).width, supportedPreviewSizes.get(0).height);
        //縮短Recording啟動(dòng)時(shí)間
        parameters.setRecordingHint(true);
        //是否支持影像穩(wěn)定能力,支持則開(kāi)啟
        if (parameters.isVideoStabilizationSupported())
            parameters.setVideoStabilization(true);
        camera.setParameters(parameters);
    }

配置MediaRecorder

按照生命周期進(jìn)行每一步的配置,重點(diǎn)關(guān)注

  • 編碼參數(shù),配置MediaRecorder的編碼參數(shù)有兩種方式;
  1. 通過(guò)系統(tǒng)提供的CamcorderProfile類,搭配mMediaRecorder.setProfile(profile);方法進(jìn)行設(shè)置,CamcorderProfile對(duì)象包含了輸出封裝格式,視頻編碼格式,幀率,碼率,分辨率,音頻采樣率,聲道數(shù),碼率等參數(shù)。

示例代碼

 /**
     * 通過(guò)系統(tǒng)的CamcorderProfile設(shè)置MediaRecorder的錄制參數(shù)
     * 首先查看系統(tǒng)是否包含對(duì)應(yīng)質(zhì)量的封裝參數(shù),然后再設(shè)置,根據(jù)具體需要的視頻質(zhì)量進(jìn)行判斷和設(shè)置
     */
    private void setProfile() {
        CamcorderProfile profile = null;
        if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
        }
        if (profile != null) {
            mMediaRecorder.setProfile(profile);
        }
    }
  1. 自定義參數(shù),通過(guò)profile可以一步到位配置各個(gè)參數(shù),但是缺點(diǎn)是沒(méi)辦法設(shè)置自己想要的視頻清晰度,因?yàn)橐曨l清晰度是根據(jù)碼率和分辨率決定的,而每個(gè)profile已經(jīng)固定了碼率和分辨率,所以無(wú)法進(jìn)行調(diào)整。這種情況我們就可以自己配置參數(shù)。

需要注意

  • 幀率不可以隨便定義,如果系統(tǒng)不支持就會(huì)報(bào)錯(cuò)。應(yīng)該先通過(guò)camera獲取支持的幀率,然后再設(shè)置。
  • 視頻尺寸的大小,可以根據(jù)需要的質(zhì)量,比如需要高清720的尺寸,那么先獲取系統(tǒng)720p的profile,然后取profile.videoFrameWidth; profile.videoFrameHeight作為輸出寬高。我這里為了方便直接寫了1280:720,大部分手機(jī)都尺寸這個(gè)參數(shù)。

示例代碼

/**
     * 自定義MediaRecorder的錄制參數(shù)
     */
    private void setConfig() {
        //設(shè)置封裝格式 默認(rèn)是MP4
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
        //音頻編碼
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        //圖像編碼
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //聲道
        mMediaRecorder.setAudioChannels(1);
        //設(shè)置最大錄像時(shí)間 單位:毫秒
        mMediaRecorder.setMaxDuration(60 * 1000);
        //設(shè)置最大錄制的大小60M 單位,字節(jié)
        mMediaRecorder.setMaxFileSize(60 * 1024 * 1024);
        //再用44.1Hz采樣率
        mMediaRecorder.setAudioEncodingBitRate(22050);
        //設(shè)置幀率,該幀率必須是硬件支持的,可以通過(guò)Camera.CameraParameter.getSupportedPreviewFpsRange()方法獲取相機(jī)支持的幀率
        mMediaRecorder.setVideoFrameRate(mFps);
        //設(shè)置碼率
        mMediaRecorder.setVideoEncodingBitRate(500 * 1024 * 8);
        //設(shè)置視頻尺寸,通常搭配碼率一起使用,可調(diào)整視頻清晰度
        mMediaRecorder.setVideoSize(1280, 720);
    }
錄制的控制

控制流代碼如下 chronometer是用于計(jì)時(shí)的

/**
     * 開(kāi)始錄制和停止錄制
     *
     * @param v
     */
    public void control(View v) {
        if (mStatus == RecorderStatus.RECORDING) {
            stopRecord();
        } else {
            startRecord();
        }
    }

    /**
     * 開(kāi)始錄制
     */
    private void startRecord() {
        initCamera();
        mCamera.unlock();
        initMediaRecorder();
        try {
            mMediaRecorder.prepare();
            mMediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

        chronometer.setBase(SystemClock.elapsedRealtime());
        chronometer.start();
        mStatus = RecorderStatus.RECORDING;
    }

    /**
     * 停止錄制
     */
    private void stopRecord() {
        releaseMediaRecorder();
        releaseCamera();
    }

視頻錄制好之后可以在手機(jī)目錄aaamedia文件夾下找到,以aaa開(kāi)頭方便查找!

完整代碼git地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。