轉載請注明出處:http://blog.csdn.net/woshizisezise/article/details/51878566
這兩天產品經理向我丟來一個新需求,需要在項目里添加一個視頻錄制的功能,正好是我沒做過的,於是研究了一番。在網上搜索了一些案例,但是都是不完整的,要不就是分辨率有問題的,要不就是聲音有問題的,要不就是實現了視頻錄制但是沒有播放功能的,所以我就想自己做一個,整合一下,來個較完整版的。
PM的要求如下:實現錄像功能,錄完后可以預覽播放,視頻清晰並且大小不能大,支持刪除視頻功能……
好吧,開始干活了,首先來分析一下原理,現在安卓手機實現錄像的功能無非就兩種方式,第一是實用系統自帶的照相機/攝像機進行錄制,然后通過回調的方式將源返回,例如:
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); //設置視頻錄制的最長時間 intent.putExtra (MediaStore.EXTRA_DURATION_LIMIT,30); //設置視頻錄制的畫質 intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); startActivityForResult (intent, VIDEO_WITH_CAMERA);
回調如下所示:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { try{ if (resultCode == Activity.RESULT_OK && requestCode == VIDEO_WITH_CAMERA){ Uri uri = data.getData(); Log.e(TAG, "onActivityResult: " + uri.toString()); } }catch (Exception e){ e.printStackTrace(); } }
這種方式是直接調用手機的攝像功能,所以就和你打開相機攝像是一模一樣的,但是這樣就產生問題了,現在的手機攝像頭像素越來越高,拍攝效果越來越清晰,很多都達到了720p甚至是1080p,這樣短暫的10s時長內存占用就達到了20M,顯然這樣是不可能的,並且intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);這行代碼,在設置EXTRA_VIDEO_QUALITY為1的情況下,視頻格式保存為mp4,然而無論怎么修改EXTRA_VIDEO_QUALITY為0.幾的時候,視頻保存格式為3gp,並且視頻錄像效果很差,所以后來我放棄了這種方式而改用第二種方式。
第二種方法就是利用安卓自帶的MediaRecorder來錄制視頻,並制定視頻保存路徑,並且可以通過Camera來播放錄制的視頻,下面我們來具體講解一下這種實現的方式。
- 首先來看一下效果圖吧,很粗糙的
布局很簡單,一個開始錄制/停止錄制按鈕,一個播放按鈕,一個錄制時間計數器,布局文件代碼如下:
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <SurfaceView android:id="@+id/surfaceview" android:layout_width="match_parent" android:layout_marginBottom="60dp" android:layout_height="match_parent" /> <ImageView android:id="@+id/imageview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="60dp" android:src="@drawable/ic_launcher"/> <Button android:id="@+id/btnStartStop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" android:text="Start"/> <Button android:id="@+id/btnPlayVideo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toRightOf="@id/btnStartStop" android:text="Play" android:layout_marginLeft="20dp"/> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="25sp" android:text="0" android:layout_alignParentBottom="true" android:layout_marginBottom="12dp" android:layout_marginLeft="20dp"/> </RelativeLayout>
下面是主要的activity界面代碼,控制MediaRecorder工作的邏輯,代碼如下:
- MainActivity.java
package com.example.mediarecorder; import android.app.Activity; import android.hardware.Camera; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.net.Uri; import android.os.Environment; import android.os.Bundle; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import java.io.File; import java.util.Calendar; public class MainActivity extends Activity implements SurfaceHolder.Callback { private static final String TAG = "MainActivity"; private SurfaceView mSurfaceview; private Button mBtnStartStop; private Button mBtnPlay; private boolean mStartedFlg = false;//是否正在錄像 private boolean mIsPlay = false;//是否正在播放錄像 private MediaRecorder mRecorder; private SurfaceHolder mSurfaceHolder; private ImageView mImageView; private Camera camera; private MediaPlayer mediaPlayer; private String path; private TextView textView; private int text = 0; private android.os.Handler handler = new android.os.Handler(); private Runnable runnable = new Runnable() { @Override public void run() { text++; textView.setText(text+""); handler.postDelayed(this,1000); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); mSurfaceview = (SurfaceView) findViewById(R.id.surfaceview); mImageView = (ImageView) findViewById(R.id.imageview); mBtnStartStop = (Button) findViewById(R.id.btnStartStop); mBtnPlay = (Button) findViewById(R.id.btnPlayVideo); textView = (TextView)findViewById(R.id.text); mBtnStartStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mIsPlay) { if (mediaPlayer != null) { mIsPlay = false; mediaPlayer.stop(); mediaPlayer.reset(); mediaPlayer.release(); mediaPlayer = null; } } if (!mStartedFlg) { handler.postDelayed(runnable,1000); mImageView.setVisibility(View.GONE); if (mRecorder == null) { mRecorder = new MediaRecorder(); } camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); if (camera != null) { camera.setDisplayOrientation(90); camera.unlock(); mRecorder.setCamera(camera); } try { // 這兩項需要放在setOutputFormat之前 mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // Set output file format mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 這兩項需要放在setOutputFormat之后 mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); mRecorder.setVideoSize(640, 480); mRecorder.setVideoFrameRate(30); mRecorder.setVideoEncodingBitRate(3 * 1024 * 1024); mRecorder.setOrientationHint(90); //設置記錄會話的最大持續時間(毫秒) mRecorder.setMaxDuration(30 * 1000); mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); path = getSDPath(); if (path != null) { File dir = new File(path + "/recordtest"); if (!dir.exists()) { dir.mkdir(); } path = dir + "/" + getDate() + ".mp4"; mRecorder.setOutputFile(path); mRecorder.prepare(); mRecorder.start(); mStartedFlg = true; mBtnStartStop.setText("Stop"); } } catch (Exception e) { e.printStackTrace(); } } else { //stop if (mStartedFlg) { try { handler.removeCallbacks(runnable); mRecorder.stop(); mRecorder.reset(); mRecorder.release(); mRecorder = null; mBtnStartStop.setText("Start"); if (camera != null) { camera.release(); camera = null; } } catch (Exception e) { e.printStackTrace(); } } mStartedFlg = false; } } }); mBtnPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mIsPlay = true; mImageView.setVisibility(View.GONE); if (mediaPlayer == null) { mediaPlayer = new MediaPlayer(); } mediaPlayer.reset(); Uri uri = Uri.parse(path); mediaPlayer = MediaPlayer.create(MainActivity.this, uri); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDisplay(mSurfaceHolder); try{ mediaPlayer.prepare(); }catch (Exception e){ e.printStackTrace(); } mediaPlayer.start(); } }); SurfaceHolder holder = mSurfaceview.getHolder(); holder.addCallback(this); // setType必須設置,要不出錯. holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } @Override protected void onResume() { super.onResume(); if (!mStartedFlg) { mImageView.setVisibility(View.VISIBLE); } } /** * 獲取系統時間 * * @return */ public static String getDate() { Calendar ca = Calendar.getInstance(); int year = ca.get(Calendar.YEAR); // 獲取年份 int month = ca.get(Calendar.MONTH); // 獲取月份 int day = ca.get(Calendar.DATE); // 獲取日 int minute = ca.get(Calendar.MINUTE); // 分 int hour = ca.get(Calendar.HOUR); // 小時 int second = ca.get(Calendar.SECOND); // 秒 String date = "" + year + (month + 1) + day + hour + minute + second; Log.d(TAG, "date:" + date); return date; } /** * 獲取SD path * * @return */ public String getSDPath() { File sdDir = null; boolean sdCardExist = Environment.getExternalStorageState() .equals(android.os.Environment.MEDIA_MOUNTED); // 判斷sd卡是否存在 if (sdCardExist) { sdDir = Environment.getExternalStorageDirectory();// 獲取跟目錄 return sdDir.toString(); } return null; } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { mSurfaceHolder = surfaceHolder; } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { // 將holder,這個holder為開始在onCreate里面取得的holder,將它賦給mSurfaceHolder mSurfaceHolder = surfaceHolder; } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { mSurfaceview = null; mSurfaceHolder = null; handler.removeCallbacks(runnable); if (mRecorder != null) { mRecorder.release(); mRecorder = null; Log.d(TAG, "surfaceDestroyed release mRecorder"); } if (camera != null) { camera.release(); camera = null; } if (mediaPlayer != null){ mediaPlayer.release(); mediaPlayer = null; } } }
同時,別忘了在AndroidManifest.xml文件中添加相應的權限:
<!--硬件支持--> <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera.autofocus"/> <uses-permission android:name="android.permission.CAMERA" > </uses-permission> <uses-permission android:name="android.permission.RECORD_AUDIO" > </uses-permission> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" > </uses-permission>
代碼里我覺得有幾個地方需要注意一下:
1.視頻質量的問題
mRecorder.setVideoSize(640, 480); mRecorder.setVideoFrameRate(30); mRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);
- mRecorder.setVideoSize(640, 480);
這是設置視頻的分辨率,在手機上看不出什么區別,可能在大屏幕上投影或者電腦上觀看的時候就有差距了。
- mRecorder.setVideoFrameRate(30);
這是設置視頻錄制的幀率,即1秒鍾30幀。
- mRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);
這個屬性很重要,這個也直接影響到視頻錄制的大小,這個設置的越大,視頻越清晰,我做了簡單的比較,可以參考下表:
所以大家可以根據自己的實際情況具體選擇設置了。
2.視頻錄制時預覽界面角度問題
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); if (camera != null) { camera.setDisplayOrientation(90); camera.unlock(); mRecorder.setCamera(camera); }
這個是在開發過程中實際遇到的問題,我網上找了一下,剛開始以為是哪個參數沒有設置導致的,但是看網友也有類似的反應,明明是豎屏錄制的,但是界面確實橫屏,在給mediarecorder設置mRecorder.setOrientationHint(90);也無濟於事,后來,必須得設置Camera的預覽角度才行,也就是這行代碼:camera.setDisplayOrientation(90);這樣再進行測試,攝像機的預覽角度終於是豎屏的了,並且保存的文件播放時也是豎屏的。
最后,一個簡單的視頻錄制及播放的案例寫完了,測試沒有其他的問題,完成的功能也比較完整,如有不當之處,歡迎大家留言。