手機(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.