轉(zhuǎn)載請注明出處:http://blog.csdn.net/woshizisezise/article/details/51878566
這兩天產(chǎn)品經(jīng)理向我丟來一個(gè)新需求,需要在項(xiàng)目里添加一個(gè)視頻錄制的功能胜蛉,正好是我沒做過的浑玛,于是研究了一番鼓寺。在網(wǎng)上搜索了一些案例,但是都是不完整的,要不就是分辨率有問題的监透,要不就是聲音有問題的蛋辈,要不就是實(shí)現(xiàn)了視頻錄制但是沒有播放功能的属拾,所以我就想自己做一個(gè)将谊,整合一下,來個(gè)較完整版的渐白。
PM的要求如下:實(shí)現(xiàn)錄像功能尊浓,錄完后可以預(yù)覽播放,視頻清晰并且大小不能大纯衍,支持刪除視頻功能……
好吧栋齿,開始干活了,首先來分析一下原理襟诸,現(xiàn)在安卓手機(jī)實(shí)現(xiàn)錄像的功能無非就兩種方式瓦堵,第一是實(shí)用系統(tǒng)自帶的照相機(jī)/攝像機(jī)進(jìn)行錄制,然后通過回調(diào)的方式將源返回歌亲,例如:
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);//設(shè)置視頻錄制的最長時(shí)間intent.putExtra (MediaStore.EXTRA_DURATION_LIMIT,30);//設(shè)置視頻錄制的畫質(zhì)intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);startActivityForResult (intent, VIDEO_WITH_CAMERA);
1
2
3
4
5
6
回調(diào)如下所示:
@Overrideprotected 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
這種方式是直接調(diào)用手機(jī)的攝像功能菇用,所以就和你打開相機(jī)攝像是一模一樣的,但是這樣就產(chǎn)生問題了陷揪,現(xiàn)在的手機(jī)攝像頭像素越來越高惋鸥,拍攝效果越來越清晰,很多都達(dá)到了720p甚至是1080p悍缠,這樣短暫的10s時(shí)長內(nèi)存占用就達(dá)到了20M卦绣,顯然這樣是不可能的,并且intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);這行代碼飞蚓,在設(shè)置EXTRA_VIDEO_QUALITY為1的情況下滤港,視頻格式保存為mp4,然而無論怎么修改EXTRA_VIDEO_QUALITY為0.幾的時(shí)候玷坠,視頻保存格式為3gp蜗搔,并且視頻錄像效果很差,所以后來我放棄了這種方式而改用第二種方式八堡。
第二種方法就是利用安卓自帶的MediaRecorder來錄制視頻樟凄,并制定視頻保存路徑,并且可以通過Camera來播放錄制的視頻兄渺,下面我們來具體講解一下這種實(shí)現(xiàn)的方式缝龄。
首先來看一下效果圖吧,很粗糙的
布局很簡單挂谍,一個(gè)開始錄制/停止錄制按鈕叔壤,一個(gè)播放按鈕,一個(gè)錄制時(shí)間計(jì)數(shù)器口叙,布局文件代碼如下:
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
下面是主要的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 { // 這兩項(xiàng)需要放在setOutputFormat之前 mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // Set output file format mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 這兩項(xiàng)需要放在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); //設(shè)置記錄會話的最大持續(xù)時(shí)間(毫秒) 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必須設(shè)置妄田,要不出錯(cuò). holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } @Override protected void onResume() { super.onResume(); if (!mStartedFlg) { mImageView.setVisibility(View.VISIBLE); } } /** * 獲取系統(tǒng)時(shí)間 * * @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); // 小時(shí) 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俺亮,這個(gè)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
同時(shí),別忘了在AndroidManifest.xml文件中添加相應(yīng)的權(quán)限:
<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
代碼里我覺得有幾個(gè)地方需要注意一下:
1.視頻質(zhì)量的問題
mRecorder.setVideoSize(640, 480); mRecorder.setVideoFrameRate(30); mRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);
mRecorder.setVideoSize(640, 480);
這是設(shè)置視頻的分辨率脚曾,在手機(jī)上看不出什么區(qū)別东且,可能在大屏幕上投影或者電腦上觀看的時(shí)候就有差距了。
mRecorder.setVideoFrameRate(30);
這是設(shè)置視頻錄制的幀率本讥,即1秒鐘30幀珊泳。
mRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);
這個(gè)屬性很重要,這個(gè)也直接影響到視頻錄制的大小拷沸,這個(gè)設(shè)置的越大色查,視頻越清晰,我做了簡單的比較堵漱,可以參考下表:
所以大家可以根據(jù)自己的實(shí)際情況具體選擇設(shè)置了综慎。
2.視頻錄制時(shí)預(yù)覽界面角度問題
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); if (camera != null) { camera.setDisplayOrientation(90); camera.unlock(); mRecorder.setCamera(camera); }
這個(gè)是在開發(fā)過程中實(shí)際遇到的問題,我網(wǎng)上找了一下勤庐,剛開始以為是哪個(gè)參數(shù)沒有設(shè)置導(dǎo)致的示惊,但是看網(wǎng)友也有類似的反應(yīng),明明是豎屏錄制的愉镰,但是界面確實(shí)橫屏米罚,在給mediarecorder設(shè)置mRecorder.setOrientationHint(90);也無濟(jì)于事,后來丈探,必須得設(shè)置Camera的預(yù)覽角度才行录择,也就是這行代碼:camera.setDisplayOrientation(90);這樣再進(jìn)行測試,攝像機(jī)的預(yù)覽角度終于是豎屏的了碗降,并且保存的文件播放時(shí)也是豎屏的隘竭。
最后,一個(gè)簡單的視頻錄制及播放的案例寫完了讼渊,測試沒有其他的問題动看,完成的功能也比較完整,如有不當(dāng)之處爪幻,歡迎大家留言菱皆。