前言
MediaRecorder是Android SDK提供用於錄制音視頻,關於音頻的錄制在我另一篇博客里已經介紹.傳送門: https://www.cnblogs.com/guanxinjing/p/10976026.html ,而這篇博客將介紹MediaRecorder視頻錄制的入門和一些MediaRecorder視頻錄制的深坑.為什么只介紹簡單的錄制視頻的入門操作,因為MediaRecorder在實際開發的時候肯定還需要配合Camera來使用.而Camera這個大坑又有Camera1和Camera2,所以我們需要分篇幅來介紹Camera1和Camera2與MediaRecorder的使用方式.
雖然是入門但是還是需要分2個錄制操作來說明
- MediaRecorder簡單的錄制視頻(不設置Camera)
- MediaRecorder設置Camera的錄制視頻(這里指的是Camera1)
雖然這2個在簡單錄制視頻的時候操作沒啥區別,但是設置Camera錄制有一個坑,秉承着寫博客就是為了記錄深坑避免下次掉坑的精神,所以我要抓出來單獨說明.
MediaRecorder簡單的錄制視頻
實現流程
- 開啟硬件加速(因為個人使用TextureView)
- 獲取權限
- 初始化MediaRecorder
- 配置MediaRecorder
- 開始錄制視頻
- 停止錄制視頻
- 暫停錄制與恢復錄制
- 銷毀釋放
開啟硬件加速
因為TextureView來預覽圖像使用效果會比SurfaceView與SurfaceTexture,在預覽的時候更不會那么卡頓.但是使用它需要開啟硬件加速,但是現在的手機設備上基本上都支持硬件加速,所以主流是使用TextureView.下面就是開啟硬件加速的方式,在AndroidManifest.xml里給需要硬件加速的activity添加android:hardwareAccelerated="true" 屬性
<activity android:name=".MediaRecorderVideoActivity" android:hardwareAccelerated="true"></activity>
獲取權限
<!--音頻錄制權限 --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!--相機權限--> <uses-permission android:name="android.permission.CAMERA" /> <!--讀取和寫入存儲權限--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
如果是Android5.0版本這4個權限是需要動態授權的.
初始化MediaRecorder
private void initMediaRecorder(){ mMediaRecorder = new MediaRecorder(); }
很簡單就是new一個MediaRecorder
配置MediaRecorder
private void configMediaRecorder(){ File videoFile = new File(getExternalCacheDir(),"video.mp4"); Log.e(TAG, "文件路徑="+videoFile.getAbsolutePath()); if (videoFile.exists()){ videoFile.delete(); } mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);//設置音頻輸入源 也可以使用 MediaRecorder.AudioSource.MIC mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//設置視頻輸入源 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//音頻輸出格式 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//設置音頻的編碼格式 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);//設置圖像編碼格式 // mMediaRecorder.setVideoFrameRate(30);//要錄制的視頻幀率 幀率越高視頻越流暢 如果設置設備不支持的幀率會報錯 按照注釋說設備會支持自動幀率所以一般情況下不需要設置 // mMediaRecorder.setVideoSize(1280,1920);//設置錄制視頻的分辨率 如果設置設備不支持的分辨率會報錯 mMediaRecorder.setVideoEncodingBitRate(8*1920*1080); //設置比特率,比特率是每一幀所含的字節流數量,比特率越大每幀字節越大,畫面就越清晰,算法一般是 5 * 選擇分辨率寬 * 選擇分辨率高,一般可以調整5-10,比特率過大也會報錯 mMediaRecorder.setOrientationHint(90);//設置視頻的攝像頭角度 只會改變錄制的視頻文件的角度(對預覽圖像角度沒有效果) Surface surface = new Surface(mTextureView.getSurfaceTexture()); mMediaRecorder.setPreviewDisplay(surface);//設置拍攝預覽 mMediaRecorder.setOutputFile(videoFile.getAbsolutePath());//MP4文件保存路徑 }
配置MediaRecorder是坑比較多的地方,現在我們來一一說下這些坑
- 坑1: 首先MediaRecorder的配置是有順序要求的,隨便配置參數將會報錯,一般順序是 設置音頻輸入源與視頻輸入源 > 設置音頻編碼格式/視頻編碼格式/音頻輸出格式 > 設置分辨率/幀率/攝像頭角度 > 添加預覽 > 添加保存文件路徑
- 坑2: 在上面的代碼上我注釋了setVideoFrameRate(int rate) 與 setVideoSize(int width, int height) 這2個方法,設置錄制視頻幀率與設置視頻分辨率.因為這2個設置的參數都是需要手機設備支持所輸入的的值的,隨便設置將會拋出 start failed -19 的錯誤
- 坑3: setOrientationHint()方法設置角度不會改變預覽圖像的角度
- 坑4: setPreviewDisplay()設置預覽,其實只預覽錄制過程中的圖像,停止錄制,相機圖像預覽也停止了.如果想要一直預覽就需要操作Camera來實現
- 坑5: 關於各種參數設置的格式(設置視頻輸入源,音頻輸入源,音頻輸出格式,音頻編碼格式,圖像編碼),這里如果你只是實現demo推薦全部使用DEFAULT,上面的demo圖像編碼和音頻編碼使用MP4格式只是一個demo告訴你有很多其他格式.很多情況系統雖然提供了很多格式,但是下各個設備支持的格式是不同的.如果你是大量適配機型最好使用默認格式或者AAC音頻編碼格式與H264視頻編碼格式(目前這2個格式最通用),如果是只做單一設備開發,可以使用指定格式.
- 坑6 當你選擇默認編碼格式錄制視頻后發現在Ios設備上無法自動播放? 其實是因為默認編碼格式選擇了IOS設備不支持的格式,Ios只支持AAC音頻編碼格式與H264視頻編碼格式,在正常開發的時候建議使用這2個編碼格式(他們是目前最通用的編碼格式)
注意!上面的坑5關於參數配置的問題,當你發現出現MediaRecorder: start failed: -38 或者其他數值的報錯,一定會去百度,這時候-38報錯 百度可能會告訴是MIC沒釋放或者Camera沒釋放(Camera沒釋放的問題我下面那個demo會說明),這的確有可能,但是還有另外一種可能是設置setAudioEncoder或者setVideoEncoder在或者setAudioSource 上出現了問題,比如一些設備是支持setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);但是換調另外一個設備上就不支持了,所以這個時候請切換成setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);默認模式,使用還是重新提醒你如果有需要適配大量機型的APP那么盡量選擇默認格式.如果你只是為單一Android設備開發你可以指定格式.
小提醒~如果你想一進入Activity就配置MediaRecorder好,那么你就需要設置TextureView的監聽setSurfaceTextureListener(SurfaceTextureListener listener),必需它先啟動准備好之后在配置MediaRecorder,否則會報錯的因為TextureView在啟動activity后需要一段時間初始化啟動
如果你需要切換攝像頭,那么你還需要注意重新配置MediaRecorder時還需要切換角度:
if (mCurrentCameraFacing){ //相機前后面向 mMediaRecorder.setOrientationHint(270); }else { mMediaRecorder.setOrientationHint(90); }
開始錄制視頻
private void startRecorder(){ configMediaRecorder();//配置MediaRecorder 因為每一次停止錄制后調用重置方法后都會取消配置,所以每一次開始錄制都需要重新配置一次 try { mMediaRecorder.prepare();//准備 mMediaRecorder.start();//開啟 } catch (IOException e) { e.printStackTrace(); } }
注意!每一次停止錄制后調用重置方法后都會取消配置,所以每一次開始錄制都需要重新配置一次
停止錄制視頻
private void stopRecorder(){ mMediaRecorder.stop();//暫停 mMediaRecorder.reset();//重置 重置后將進入空閑狀態,再次啟動錄制需要重新配置MediaRecorder }
暫停錄制與恢復錄制
暫停錄制,注意這里是pause()方法,不是stop()
private void pauseRecorder(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mMediaRecorder.pause();//暫停 } }
恢復錄制
private void resumeRecorder(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mMediaRecorder.resume();//恢復 } }
銷毀釋放
private void destroy(){ if (mMediaRecorder != null){ mMediaRecorder.stop(); mMediaRecorder.release();//釋放 釋放之前需要先調用stop() mMediaRecorder = null; } }
MediaRecorder設置Camera的錄制視頻
添加Camera來錄制視頻本來是另外一個篇幅應該說的事情,但是有個坑不得不在這里先說. 其他初始化和開啟/停止/都與上面的介紹一致,下面我們來說說萬惡的MediaRecorder配置:
private void configMediaRecorder(){ File videoFile = new File(getExternalCacheDir(),"video.mp4"); Log.e(TAG, "文件路徑="+videoFile.getAbsolutePath()); if (videoFile.exists()){ videoFile.delete(); } Camera camera = Camera.open(); camera.unlock(); mMediaRecorder.setCamera(camera); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//設置音頻輸入源 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//設置視頻輸入源 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//音頻輸出格式 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//設置音頻的編碼格式 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);//設置圖像編碼格式 mMediaRecorder.setOrientationHint(90);//設置視頻的攝像頭角度 只會改變錄制的視頻角度 Surface surface = new Surface(mTextureView.getSurfaceTexture()); mMediaRecorder.setPreviewDisplay(surface); mMediaRecorder.setOutputFile(videoFile.getAbsolutePath()); }
也是一個簡單的配置,也就是多了有攝像頭的代碼,如果你需要選擇攝像頭可以自己獲取攝像頭信息來判斷前后在獲取對應id,打開對應攝像頭,作為簡單的demo就暫時不演示選擇攝像頭的代碼.
然后重點來了這里有一個關鍵,你不會想到Camera.open();執行后攝像頭居然是鎖定狀態的,就算是明白打開后就是鎖定,但是你也不會發現在設置mMediaRecorder.setCamera(camera);之前是需要將攝像頭解除鎖定 camera.unlock();! 是的,就是這行代碼困擾我了一下午!!!!!! 所以重點!重點!重點! 就是camera.unlock(); 最蛋疼的是如果你不做解除鎖定的操作報錯的提示是 start failed -19, 這個報錯提示成功的讓我反反復復研究是不是分辨率尺寸設置有問題........
而這個Camera.unlock()在官方注釋里也有說明在啟動在設置之前先需要解鎖攝像頭,如下:
* <p>Use this function to switch quickly between preview and capture mode without a teardown of * the camera object. {@link android.hardware.Camera#unlock()} should be called before * this. Must call before {@link #prepare}.</p>
setProfile
這個功能其實是相機預設的一些錄制質量,格式和分辨率,官方注釋說:
使用CamcorderProfile對象中的設置進行錄制。這種方法應該在設置視頻和音頻源之后和setOutputfile()之前調用。如果使用延時攝像機配置文件,音頻相關源或錄制參數被忽略。
我們看到關鍵CamcorderProfile對象,那么看看什么CamcorderProfile的解釋:
攝像機應用程序的預定義攝像機配置文件設置,這些設置是只讀的。其實就是預設的一些攝像頭配置設置
預設的屬性有這些:
- QUALITY_LOW
- QUALITY_HIGH
- QUALITY_QCIF
- QUALITY_CIF
- QUALITY_480P
- QUALITY_720P
- QUALITY_1080P
- QUALITY_2160P
- QUALITY_TIME_LAPSE_LOW
- QUALITY_TIME_LAPSE_HIGH
- QUALITY_TIME_LAPSE_QCIF
- QUALITY_TIME_LAPSE_CIF
- QUALITY_TIME_LAPSE_480P
- QUALITY_TIME_LAPSE_720P
- QUALITY_TIME_LAPSE_1080P
- QUALITY_TIME_LAPSE_2160P
- QUALITY_HIGH_SPEED_LOW
- QUALITY_HIGH_SPEED_HIGH
- QUALITY_HIGH_SPEED_480P
- QUALITY_HIGH_SPEED_720P
- QUALITY_HIGH_SPEED_1080P
- QUALITY_HIGH_SPEED_2160P
使用代碼:
if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P); profile.videoBitRate = mPreviewSize.getWidth() * mPreviewSize.getHeight(); mMediaRecorder.setProfile(profile); mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture())); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P); profile.videoBitRate = mPreviewSize.getWidth() * mPreviewSize.getHeight(); mMediaRecorder.setProfile(profile); mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture())); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)) { mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA)); mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture())); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_CIF)) { mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_CIF)); mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture())); } else { mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mMediaRecorder.setVideoEncodingBitRate(10000000); mMediaRecorder.setVideoFrameRate(30); mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); }
end