轉載請註明出處: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);
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
回調如下所示:
@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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
這種方式是直接調用手機的攝像功能,所以就和你打開相機攝像是一模一樣的,但是這樣就產生問題了,現在的手機攝像頭像素越來越高,拍攝效果越來越清晰,很多都達到了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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
下面是主要的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;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
同時,別忘了在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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
代碼裏我覺得有幾個地方需要注意一下:
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);這樣再進行測試,攝像機的預覽角度終於是豎屏的了,並且保存的文件播放時也是豎屏的。
最後,一個簡單的視頻錄製及播放的案例寫完了,測試沒有其他的問題,完成的功能也比較完整,如有不當之處,歡迎大家留言。