請捂着你的良心說話,對於貧窮的做者(我)不是應該打賞麼 ̄へ ̄!,接下來工做又要忙起來了,更新應該是放緩了呢╮(╯_╰)╭,好傷心。
javascript
想想,咱們聊過AudioReord,AudioTrack,MediaPlayer,那多媒體四大金剛,就剩下了MediaRecorder了(SoundPool?我這裏信號很差···)。其實MediaRecorder我的用的也很少,好久前用它在拍攝視頻上確實趟過無視次坑,那今天就聊它吧,把它聊到躺下(ノQ益Q)ノ彡┻━┻。java
通常用在多媒體錄製上面,固然若是你只是簡單的想錄制音頻,用它最合適不過,不過若是你想更多樣化的錄製這裏推薦《Android MP3錄製,波形顯示,音頻權限兼容與播放》。今天的主題是錄製視頻,用的仍是老式通用的Camera,不是新的camera2(這就尷尬了.....((/- -)/),反正我的秉承能用是王道的作法(懶)。以前也嘗試過FFMPEG的錄製合成音頻,大小和效果也不錯,只是有時候的兼容性確實有些問題,最主要仍是資料很少,很差改啊 ̄へ ̄(懶)。git
既然是錄製視頻,那麼少不了Camera,這貨也是讓人又愛又恨(哪裏有愛了┑( ̄Д  ̄)┍?),也許是由於Android碎片化的緣由,因此用起來也是坑坑窪窪的,接下來就讓咱們結束廢話吧:github
舊項目用的都是SurfaceView,此次就就它吧。這裏咱們須要首先是implements SurfaceHolder.Callback,這樣咱們才能在surface建立的時候初始化相機渲染畫面,在畫面銷燬的時候銷燬相機(畫面都沒有要初始化相機何用)。api
SurfaceHolder holder = cameraShowView.getHolder();
holder.addCallback(this);
// setType必須設置,要不出錯.
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
···此處略過無數只草泥馬
@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceHolder = holder;
//更具當前的相機類型(前,後)初始化相機,閃光燈不啓動
initCamera(cameraType, false);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
surfaceHolder = holder;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//結束錄製
endRecord();
//是否相機
releaseCamera();
}複製代碼
除了有點坑外,流程上仍是比較簡單的:ide
if (camera != null) {
//若是已經初始化過,就先釋放
releaseCamera();
}
try {
//根據先後攝像頭打開攝像頭
camera = Camera.open(type);
if (camera == null) {
//拿不到多是沒權限
showCameraPermission();
return;
}
camera.lock();
//Point screen = new Point(getScreenWidth(this), getScreenHeight(this));
//如今不用獲取最高的顯示效果
//Point show = getBestCameraShow(camera.getParameters(), screen);
Camera.Parameters parameters = camera.getParameters();
if (type == 0) {
//基本是都支持這個比例
parameters.setPreviewSize(SIZE_1, SIZE_2);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//1連續對焦
camera.cancelAutoFocus();// 2若是要實現連續的自動對焦,這一句必須加上
}
camera.setParameters(parameters);
FlashLogic(camera.getParameters(), flashType, flashDo);
if (cameraType == 1) {
frontCameraRotate();
camera.setDisplayOrientation(frontRotate);
} else {
camera.setDisplayOrientation(90);
}
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
camera.unlock();
} catch (Exception e) {
e.printStackTrace();
releaseCamera();
}複製代碼
這裏須要注意坑(畫面變形)問題,那就是你配置的相機分辨率畫面,在錄製的時候可能會由於和錄製的分辨率畫面不一致,致使開始錄製的時候畫面奇怪的突變,因此Camera和MediaRecorder的分辨率最好一致。性能
問題又來了Camera和MediaRecorder不是什麼分辨率都支持的,他們分別都有對應的接口:getSupportedPreviewSizes和CamcorderProfile等來獲取對應支持的分辨率的,路迢迢啊。
通過輪番的嘗試,還有上傳對大小要求,因此最終選擇寫死,對,寫死了640 * 480這樣的大小,這個分辨率基本都支持(不支持那手機的尊嚴何在( ‵o′)凸),對於十來秒的視頻,這個分辨率的尺寸還算能夠(若是對畫質有須要能夠另外配置,若是FFMPEG壓縮性能堪憂啊)。測試
那麼問題又來了(哪來那麼多問題),可是手機屏幕大部分狀況下是16:9,而這個分辨率明顯是4:3(萬惡的需求啊(ノQ益Q)ノ彡┻━┻)。這時候由於Surface的最外層是FrameLayout(搞不懂爲何超出屏幕的時候RelativeLayout有時候會有問題),我的的作法是調整surface的比例。若是是不充滿屏幕高度的,就經過屏幕寬度比例算出surface的高度;若是充滿屏幕高度,就算出surface的寬度。動畫
如此以來,不變形啦,在點擊錄製的瞬間也不跳動啦,惟一有點小問題的就是充滿高度的時候,畫面是超過了屏幕寬度的一點的,因此可能錄到了什麼不想錄制的♂,可是恰好沒看到︿( ̄︶ ̄)︿。this
int screenWidth = getScreenWidth(this);
int screenHeight = getScreenHeight(this);
//根據比例設置surface的寬度
setViewSize(cameraShowView, screenWidth * SIZE_1 / SIZE_2, screenHeight);複製代碼
當時看到IOS微博的視頻錄製是能夠支持橫豎屏錄製,以爲挺有意思的,這裏用的是OrientationEventListener,具體的以前IJKPlayer視頻文章裏已經說過(懶),有興趣的能夠去看看。咱們是在畫面旋轉的時候把對應的logo用屬性動畫也旋轉了,而後獲得當前的旋轉角度,告訴MediaRecorder,拍攝出來的視頻元信息裏就帶有了角度信息,播放的時候畫面會就旋轉爲橫屏或者豎屏啦。
orientationEventListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int rotation) {
if (!flagRecord) {
if (((rotation >= 0) && (rotation <= 30)) || (rotation >= 330)) {
// 豎屏拍攝
if (rotationFlag != 0) {
//旋轉logo
rotationAnimation(rotationFlag, 0);
//這是豎屏視頻須要的角度
rotationRecord = 90;
//這是記錄當前角度的flag
rotationFlag = 0;
}
} else if (((rotation >= 230) && (rotation <= 310))) {
// 橫屏拍攝
if (rotationFlag != 90) {
//旋轉logo
rotationAnimation(rotationFlag, 90);
//這是正橫屏視頻須要的角度
rotationRecord = 0;
//這是記錄當前角度的flag
rotationFlag = 90;
}
} else if (rotation > 30 && rotation < 95) {
// 反橫屏拍攝
if (rotationFlag != 270) {
//旋轉logo
rotationAnimation(rotationFlag, 270);
//這是反橫屏視頻須要的角度
rotationRecord = 180;
//這是記錄當前角度的flag
rotationFlag = 270;
}
}
//倒過來就算了,你又不是小米MIX
}
}
};
orientationEventListener.enable();複製代碼
前置攝像頭
此處有坑,還不止一個,若是你還須要支持前置攝像頭(能說不嗎?),直接使用上面的rotationRecord去配置MediaRecorder是會有問題的。
首先說Camera,若是測試說你的前置Camera在某些手機上畫面角度不對,這時候你能夠偷偷把手機砸了,由於這是兼容問題。若是你沒有勇氣砸手機,看下面。
傳說中,只要拿下面的frontRotate去配置Camera就正常顯示啦,偉人說的!而其中的frontOri,咱們是用到配置後面MediaRecorder,具體看代碼的,這是調出來的結果(。・・)ノ。
/** * 旋轉前置攝像頭爲正的 */
private void frontCameraRotate() {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(1, info);
int degrees = getDisplayRotation(this);
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
frontOri = info.orientation;
frontRotate = result;
}
/** * 獲取旋轉角度 */
private int getDisplayRotation(Activity activity) {
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
switch (rotation) {
case Surface.ROTATION_0:
return 0;
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
}
return 0;
}
···此處無數字草泥馬
//配置錄製角度
int frontRotation;
if (rotationRecord == 180) {
//反向橫屏的前置角度
frontRotation = 180;
} else {
//豎屏和正向橫屏的前置角度
//錄製下來的視屏選擇角度,此處爲前置
frontRotation = (rotationRecord == 0) ? 270 - frontOri : frontOri;
}
//根據先後攝像頭給角度
recorder.setOrientationHint((cameraType == 1) ? frontRotation : rotationRecord);複製代碼
閃光燈的打開關閉遇到過一個問題,就是有的手機尚未開啓錄製,一配置打開它就亮了。(砸手機)最後解決的是在配置的時候標誌類型,設置好MediaRecorder以後拍攝纔開始閃光燈。(其餘的什麼一閃一閃的模式就算了吧= =)
至於旋轉切換相機,主要仍是針對前置camera須要作如上面所說的畫面預覽旋轉。
/** * 閃光燈邏輯 * * @param p 相機參數 * @param type 打開仍是關閉 * @param isOn 是否當即啓動 */
private void FlashLogic(Camera.Parameters p, int type, boolean isOn) {
flashType = type;
if (type == 0) {
if (isOn) {
p.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
camera.setParameters(p);
}
videoFlashLight.setImageResource(R.drawable.flash_off);
} else {
if (isOn) {
p.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
camera.setParameters(p);
}
videoFlashLight.setImageResource(R.drawable.flash);
}
if (cameraFlag == 0) {
videoFlashLight.setVisibility(View.GONE);
} else {
videoFlashLight.setVisibility(View.VISIBLE);
}
}
/** * 切換攝像頭 */
public void switchCamera() {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
int cameraCount = Camera.getNumberOfCameras();//獲得攝像頭的個數0或者1;
try {
for (int i = 0; i < cameraCount; i++) {
Camera.getCameraInfo(i, cameraInfo);//獲得每個攝像頭的信息
if (cameraFlag == 1) {
//後置到前置
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {//表明攝像頭的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK後置
frontCameraRotate();//前置旋轉攝像頭度數
switchCameraLogic(i, 0, frontRotate);
break;
}
} else {
//前置到後置
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {//表明攝像頭的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK後置
switchCameraLogic(i, 1, 90);
break;
}
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
}複製代碼
這裏最坑的就是MediaRecorder的配置參數是有先後關係的,先生小孩後再洞房這種綠色模式是不行的,具體順序參照下方代碼,碼率和幀數都是配置相對較小,適合拍攝上傳。此處還須要注意,若是應用沒有獲取到錄音權限,在錄製的時候是會走catch裏面的。
中止錄製相對就簡單了,只要順序正常便可,以後就能夠把視頻傳到VideoView快速實現預覽啦。做爲谷歌親兒子,VideoView自帶對setOrientationHint的角度解析,只要根據視頻大小配置好界面顯示的效果便可。比起 以前本人擼的播放器 ,兒子仍是本身的親┑( ̄Д  ̄)┍,若是需求不高用起來仍是能夠閉着眼睛的用的。(以前還有小夥伴本身用MediaPlayer播放呢)
//開始
private boolean startRecord() {
//懶人模式,根據閃光燈和攝像頭先後從新初始化一遍,開期閃光燈工做模式
initCamera(cameraType, true);
if (recorder == null) {
recorder = new MediaRecorder();
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
|| camera == null || recorder == null) {
camera = null;
recorder = null;
//仍是沒權限啊
showCameraPermission();
return false;
}
try {
recorder.setCamera(camera);
// 這兩項須要放在setOutputFormat以前
recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// Set output file format,輸出格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//必須在setEncoder以前
recorder.setVideoFrameRate(15); //幀數 一分鐘幀,15幀就夠了
recorder.setVideoSize(SIZE_1, SIZE_2);//這個大小就夠了
// 這兩項須要放在setOutputFormat以後
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
recorder.setVideoEncodingBitRate(3 * SIZE_1 * SIZE_2);//第一個數字越大,清晰度就越高,考慮文件大小的緣故,就調整爲1
int frontRotation;
if (rotationRecord == 180) {
//反向的前置
frontRotation = 180;
} else {
//正向的前置
frontRotation = (rotationRecord == 0) ? 270 - frontOri : frontOri; //錄製下來的視屏選擇角度,此處爲前置
}
recorder.setOrientationHint((cameraType == 1) ? frontRotation : rotationRecord);
//把攝像頭的畫面給它
recorder.setPreviewDisplay(surfaceHolder.getSurface());
//建立好視頻文件用來保存
videoDir();
if (videoFile != null) {
//設置建立好的輸入路徑
recorder.setOutputFile(videoFile.getPath());
recorder.prepare();
recorder.start();
//不能旋轉啦
orientationEventListener.disable();
flagRecord = true;
}
} catch (Exception e) {
//通常沒有錄製權限或者錄製參數出現問題都走這裏
e.printStackTrace();
//仍是沒權限啊
recorder.reset();
recorder.release();
recorder = null;
showCameraPermission();
FileUtils.deleteFile(videoFile.getPath());
return false;
}
return true;
}
//結束錄製
private void endRecord() {
//反正屢次進入,好比surface的destroy和界面onPause
if (!flagRecord) {
return;
}
flagRecord = false;
try {
if (recorder != null) {
recorder.stop();
recorder.reset();
recorder.release();
orientationEventListener.enable();
recorder = null;
}
} catch (Exception e) {
e.printStackTrace();
}
videoTime.stop();
videoTime.setBase(SystemClock.elapsedRealtime());
Intent intent = new Intent(this, PlayActivity.class);
intent.putExtra(PlayActivity.DATA, videoFile.getAbsolutePath());
startActivityForResult(intent, 2222);
overridePendingTransition(R.anim.fab_in, R.anim.fab_out);
}複製代碼
總的來講,錄製視頻仍是蠻簡單的,主要仍是視頻的角度問題須要考慮:
<( ̄︶ ̄)↗知道你想說什麼,DMEO在這裏 : github.com/CarGuo/Vide…