Android使用MediaRecorder和Camera實(shí)現(xiàn)視頻錄制及播放功能整理

轉(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)之處爪幻,歡迎大家留言菱皆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挨稿,隨后出現(xiàn)的幾起案子仇轻,更是在濱河造成了極大的恐慌,老刑警劉巖奶甘,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篷店,死亡現(xiàn)場離奇詭異,居然都是意外死亡臭家,警方通過查閱死者的電腦和手機(jī)船庇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門吭产,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侣监,“玉大人鸭轮,你說我怎么就攤上這事¢厦梗” “怎么了窃爷?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姓蜂。 經(jīng)常有香客問我按厘,道長,這世上最難降的妖魔是什么钱慢? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任逮京,我火速辦了婚禮,結(jié)果婚禮上束莫,老公的妹妹穿的比我還像新娘懒棉。我一直安慰自己,他們只是感情好览绿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布策严。 她就那樣靜靜地躺著,像睡著了一般饿敲。 火紅的嫁衣襯著肌膚如雪妻导。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天怀各,我揣著相機(jī)與錄音倔韭,去河邊找鬼。 笑死瓢对,一個(gè)胖子當(dāng)著我的面吹牛寿酌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沥曹,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼份名,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了妓美?” 一聲冷哼從身側(cè)響起僵腺,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎壶栋,沒想到半個(gè)月后辰如,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贵试,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年琉兜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凯正。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,768評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豌蟋,死狀恐怖廊散,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梧疲,我是刑警寧澤允睹,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站幌氮,受9級特大地震影響缭受,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜该互,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一米者、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宇智,春花似錦蔓搞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至太防,卻和暖如春妻顶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜒车。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工讳嘱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酿愧。 一個(gè)月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓沥潭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嬉挡。 傳聞我的和親對象是個(gè)殘疾皇子钝鸽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評論 2 350

推薦閱讀更多精彩內(nèi)容