前言
本篇介紹使用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ù)有兩種方式;
- 通過(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);
}
}
- 自定義參數(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)頭方便查找!