好久没更新博客了,着实有点惭愧,以后不管工作是忙是闲都得坚持更新博客,持之以恒地做下去!
正式进入主题,今天我分享一个在工作中过程中遇到的一个技术难点以及我解决该难点的方案,该问题困扰了我许久,通过不断地研究和翻阅资料,终于在满足工作需求的情况下将该问题解决,希望我的经验能够对读者有所帮助。我们知道Android ApI提供了MediaRecorder和AudioRecord两个类给开发者来很方便地实现音视频的录制(前者可以实现音频和视频的录制,后者只能实现音频的录制)。这两个类都提供了start()和stop()方法用于开始和结束音频或视频的录制,但令人费解的是这两个类都没有提供pause()方法用于暂停录制音视频,因为在实际应用当中,暂停录制的功能是非常有必要的,暂不清楚Google工程师们在设计API时是如何考量的而没有添加这个方法,可能另有玄机吧。那既然Android自身没有提供这样一个方法,就只有我们自己来实现了,那么问题就来了,就是到底如何实现音频录制的暂停方法呢?别急,先讲一下我在工作中所遇到的需求,如下:需实现音频录制的暂停功能,并且生成的音频文件格式必须是m4a格式。为什么项目中音频文件一定要采用m4a格式的呢?有以下几点原因:
1. 录制相同时间的音频,使用m4a格式存储的文件的大小要比使用其它格式类型存储的文件的大小要小(通过实验多次,在相同采样率16000的情况下,一般录制5分钟的音频,采用m4a格式存储的音频文件只有1.2Mb,而采用arm、mp3及其它格式的一般都有2-5Mb),这样当用户需要下载或上传录制的音频文件时,可以节省流量,并且相同压缩率的前提下,m4a格式音频的音质相比其它格式的也更高;
2.产品同时拥有Android客户端和IOS客户端,那为了避免使用Android客户端的用户录制的音频上传到服务器之后,使用IOS客户端的用户下载下来发生无法播放的问题,我们需统一录制音频的存储格式。由于Iphone手机官方推荐的音频格式是m4a且对m4a格式的音频文件支持度较高,再综合第一点来看,于是我们选择m4a格式作为音频文件的存储格式。
好了,解释了为什么音频录制文件必须使用m4a存储格式之后,接下来我们来解决如何实现音频的录制的暂停功能。前面讲了,Android SDK API提供了MediaRecorder和AudioRecord两个类来完成音视频的录制方法,我们看下它们两者之间的特点和区别:
MediaRecorder:
特性:该类集成了录音、编码和压缩等功能,可根据设置的编码格式的参数直接生成各种格式的音频文件(如arm、 mp3或m4a等),由于集成度较高,因此使用起来简单,但灵活度不高,不能实现像AudioRecord那样进行音 频的实时处理。
AudioRecord:
特性:该类录制的音频为原始的PCM二进制音频数据,没有文件头和文件尾,生成的PCM文件不能直接使用 Mediaplayer播放,只能使用AudioTrack播放。使用AudioRecord可以实现边录边播的音频实时处理。
了解了这两个类的特性之后,起初我决定使用MediaRecorder类来解决录制暂停的问题,具体的思路如下:
(1)每次触发开始录制和暂停录制音频的事件时都单独保存一个m4a格式的音频文件,直到最后触发停止录制音频的事件时,将之前录制的若干m4a格式的音频文件合并成一个文件。如图下:
这种方法比较好理解,也容易想到,不过在实现过程中遇到了一个技术难点,那就是多个m4a格式的音频文件的合并并不是简单地将文件的内容拷贝到一个文件中,而是要通过分析每一个m4a格式的音频文件,计算出每个文件头的结构大小,并将文件头去掉,再将文件进行拷贝合并。通过查阅资料,发现m4a格式的音频文件头是由多个包含关系的ATOM结构组成,且每个不同的m4a格式的音频文件的文件头的大小都不一样,这样使得多个m4a文件头文件解析和合并变得较为复杂,若有多个m4a文件需要合并,那么会变得较为耗时。再者,对于没有足够音视频文件解析和编解码经验的开发者来讲,要精准地得解析一个m4a文件,挑战性太大(网上这方面的资料也寥寥无几),有兴趣的读者可以进行深入研究。
上述方法行不通,于是只好作罢,后来又想到了另外一种方法,也是我解决问题的最终方案,具体的思路如下:
(2)由于使用AudioRecord类提供的方法录制的音频是原始的PCM格式的二进制数据,该格式的文件没有文件头信息,那么我们在进行文件合并时就就无需解析文件结构去掉对应的文件头,这样就变成了二进制数据地简单拷贝和合并。我在这里实现的方式是在录制音频的过程中采用边录制边写入的方式不断地向同一个文件写入录制的二进制音频数据。当触发暂停录音事件时,停止录制停止写入二进制数据,当触发继续录音事件时,则继续录制和向文件中写入数据。最后停止写入数据时,将PCM二进制音频文件编码成m4a格式的音频文件。如图下:
上面方法描述中,实现边录制边写入的功能倒比较简单,关键难点是如何将PCM二进制数据编码成目标的m4a格式的音频数据,要实现音视频的编解码,一般都是使用第三方开源的编解码库,比较著名的有FFMpeg和Speex,这些库都提供了录制、转换以及流化音视频的完整解决方案,不过在此我的需求只是需要简单地实现编码工作,使用这些开源库体积太大,有点杀鸡用牛刀的感觉。因此,通过研究和查阅资料,我在github上找到了一个非常有用的编解码开源项目android-aac-enc(地址:https://github.com/timsu/android-aac-enc),该开源项目能完美地实现将原始的pcm格式的二进制数据编码成m4a格式的数据文件,相比于FFmpeg库,这个库有以下几点优点:
1. aac-enc库的体积比FFmpeg库的体积更小;
2. 相比FFMpeg, aac-enc实现格式转换更加简单和快速;
3. aac-enc比FFmpeg需要编译更少的底层的代码。
该开源项目使用起来也非常地简单,通过分析其示例代码我们可以通过以下四个步骤来实现音频的编码工作,代码如下:
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
|
/**
* 1.初始化编码配置
*
* 32000 : 音频的比特率
* 2 : 音频的声道
* sampleRateInHz : 音频采样率
* 16 :音频数据格式,PCM 16位每个样本
* FileUtils.getAAcFilePath(mAudioRecordFileName) : aac音频文件的存储路径
*/
encoder.init(
32000
,
2
, sampleRateInHz,
16
, FileUtils.
getAAcFilePath(mAudioRecordFileName));
/**
* 2.对二进制代码进行编码
*
* b :需要编码的二进制音频流
*/
encoder.encode(b);
/**
* 3. 从pcm二进制数据转aac音频文件编码完成
*
*/
encoder.uninit();
/**
* 4. 将aac文件转码成m4a文件
*
* FileUtils.getAAcFilePath(mAudioRecordFileName) :需要编码的aac文件路径
* FileUtils.getM4aFilePath(mAudioRecordFileName) :编码成m4a文件的目标路径
*/
new
AACToM4A().convert(mContext, FileUtils.getAAcFilePath(mAudioRecordFileName),
FileUtils.getM4aFilePath(mAudioRecordFileName));
|
基本上明确好思路和编码的实现方法后,接下来就是具体的实现过程了,我们将依据上面的思路和方法来实现一个具有暂停功能的音频录制Demo。首先看下Demo的项目结构,如下图:
如何使用AudioRecord类来实现音频的录制,这方面的资料很多,读者可以先学习,简单地入一下门。接下来我们先运行一下Demo,来看一下效果图:
(1)初始界面 (2)正在录制界面 (2)暂停界面
(4)播放界面 (5)暂停播放界面
粗略看了Demo的运行效果图后,接下来我们就要来实现,这里由于要使用aac-encode项目来实现音频的编码,则需将该项目以library的形式集成到我们的Demo中,做完该项工作后,我们就可以在Demo工程中写其它相关的逻辑代码了,下面看一下实现demo的关键代码,首先是RecordAct.java文件中的代码,该类为主界面类,主要实现了界面的初始化、音频的录制和音频播放的功能,具体的代码如下:
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
|
public
class
RecordAct
extends
Activity
implements
OnClickListener{
/**
* Status:录音初始状态
*/
private
static
final
int
STATUS_PREPARE =
0
;
/**
* Status:正在录音中
*/
private
static
final
int
STATUS_RECORDING =
1
;
/**
* Status:暂停录音
*/
private
static
final
int
STATUS_PAUSE =
2
;
/**
* Status:播放初始状态
*/
private
static
final
int
STATUS_PLAY_PREPARE =
3
;
/**
* Status:播放中
*/
private
static
final
int
STATUS_PLAY_PLAYING =
4
;
/**
* Status:播放暂停
*/
private
static
final
int
STATUS_PLAY_PAUSE =
5
;
private
int
status = STATUS_PREPARE;
/**
* 录音时间
*/
private
TextView tvRecordTime;
/**
* 录音按钮
*/
private
ImageView btnRecord;
// 录音按钮
private
PopupWindow popAddWindow;
/**
* 试听界面
*/
private
LinearLayout layoutListen;
/**
* 录音长度
*/
private
TextView tvLength;
private
TextView recordContinue;
/**
* 重置按钮
*/
private
View resetRecord;
/**
* 结束录音
*/
private
View recordOver;
private
ImageView audioRecordNextImage;
private
TextView audioRecordNextText;
/**
* 音频播放进度
*/
private
TextView tvPosition;
long
startTime =
0
;
/**
* 最大录音长度
*/
private
static
final
int
MAX_LENGTH =
300
*
1000
;
private
Handler handler =
new
Handler();
private
Runnable runnable;
/**
* 音频录音的总长度
*/
private
static
int
voiceLength;
/**
* 音频录音帮助类
*/
private
AudioRecordUtils mRecordUtils;
/**
* 播放进度条
*/
private
SeekBar seekBar;
/**
* 音频播放类
*/
private
Player player;
/**
* 录音文件名
*/
private
String audioRecordFileName;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super
.onCreate(savedInstanceState);
setContentView(R.layout.pop_add_record);
initView();
}
public
void
initView(){
//音频录音的文件名称
audioRecordFileName = TimeUtils.getTimestamp();
//初始化音频录音对象
mRecordUtils =
new
AudioRecordUtils(
this
,audioRecordFileName);
View view = LayoutInflater.from(
this
).inflate(R.layout.pop_add_record,
null
);
tvRecordTime = (TextView)findViewById(R.id.tv_time);
btnRecord = (ImageView)findViewById(R.id.iv_btn_record);
btnRecord.setOnClickListener(
this
);
recordContinue = (TextView)findViewById(R.id.record_continue_txt);
resetRecord = findViewById(R.id.btn_record_reset);
recordOver = findViewById(R.id.btn_record_complete);
resetRecord.setOnClickListener(
this
);
recordOver.setOnClickListener(
this
);
audioRecordNextImage = (ImageView)findViewById(R.id.recrod_complete_img);
audioRecordNextText = (TextView)findViewById(R.id.record_complete_txt);
layoutListen = (LinearLayout)findViewById(R.id.layout_listen);
tvLength = (TextView)findViewById(R.id.tv_length);
tvPosition = (TextView)findViewById(R.id.tv_position);
seekBar = (SeekBar)findViewById(R.id.seekbar_play);
seekBar.setOnSeekBarChangeListener(
new
SeekBarChangeEvent());
seekBar.setEnabled(
false
);
player =
new
Player(seekBar, tvPosition);
player.setMyPlayerCallback(
new
MyPlayerCallback() {
@Override
public
void
onPrepared() {
seekBar.setEnabled(
true
);
}
@Override
public
void
onCompletion() {
status = STATUS_PLAY_PREPARE;
seekBar.setEnabled(
false
);
seekBar.setProgress(
0
);
tvPosition.setText(
00
:
00
);
recordContinue.setBackgroundResource(R.drawable.record_audio_play);
}
});
popAddWindow =
new
PopupWindow(view, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
popAddWindow.setFocusable(
true
);
popAddWindow.setAnimationStyle(R.style.pop_anim);
popAddWindow.setBackgroundDrawable(
new
BitmapDrawable());
}
public
void
handleRecord(){
switch
(status){
case
STATUS_PREPARE:
mRecordUtils.startRecord();
btnRecord.setBackgroundResource(R.drawable.record_round_red_bg);
status = STATUS_RECORDING;
voiceLength =
0
;
timing();
break
;
case
STATUS_RECORDING:
pauseAudioRecord();
resetRecord.setVisibility(View.VISIBLE);
recordOver.setVisibility(View.VISIBLE);
btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);
recordContinue.setVisibility(View.VISIBLE);
status = STATUS_PAUSE;
break
;
case
STATUS_PAUSE:
mRecordUtils.startRecord();
resetRecord.setVisibility(View.INVISIBLE);
recordOver.setVisibility(View.INVISIBLE);
btnRecord.setBackgroundResource(R.drawable.record_round_red_bg);
recordContinue.setVisibility(View.INVISIBLE);
status = STATUS_RECORDING;
timing();
break
;
case
STATUS_PLAY_PREPARE:
player.playUrl(FileUtils.getM4aFilePath(audioRecordFileName));
recordContinue.setBackgroundResource(R.drawable.record_audio_play_pause);
status = STATUS_PLAY_PLAYING;
break
;
case
STATUS_PLAY_PLAYING:
player.pause();
recordContinue.setBackgroundResource(R.drawable.record_audio_play);
status = STATUS_PLAY_PAUSE;
break
;
case
STATUS_PLAY_PAUSE:
player.play();
recordContinue.setBackgroundResource(R.drawable.record_audio_play_pause);
status = STATUS_PLAY_PLAYING;
break
;
}
}
/**
* 暂停录音
*/
public
void
pauseAudioRecord(){
mRecordUtils.pauseRecord();
if
(handler !=
null
&& runnable !=
null
) {
handler.removeCallbacks(runnable);
runnable =
null
;
}
}
/**
* 停止录音
*/
public
void
stopAudioRecord(){
pauseAudioRecord();
mRecordUtils.stopRecord();
status = STATUS_PLAY_PREPARE;
showListen();
}
/**
* 重新录音参数初始化
*/
@SuppressLint
(NewApi)
public
void
resetAudioRecord(){
//停止播放音频
player.stop();
pauseAudioRecord();
mRecordUtils.reRecord();
status = STATUS_PREPARE;
voiceLength =
0
;
tvRecordTime.setTextColor(Color.WHITE);
tvRecordTime.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));
recordContinue.setText(R.string.record_continue);
recordContinue.setBackground(
null
);
recordContinue.setVisibility(View.GONE);
layoutListen.setVisibility(View.GONE);
tvRecordTime.setVisibility(View.VISIBLE);
audioRecordNextImage.setImageResource(R.drawable.btn_record_icon_complete);
audioRecordNextText.setText(R.string.record_over);
btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);
resetRecord.setVisibility(View.INVISIBLE);
recordOver.setVisibility(View.INVISIBLE);
}
/**
* 计时功能
*/
private
void
timing() {
runnable =
new
Runnable() {
@Override
public
void
run() {
voiceLength +=
100
;
if
(voiceLength >= (MAX_LENGTH -
10
*
1000
)) {
tvRecordTime.setTextColor(getResources().getColor(
R.color.red_n));
}
else
{
tvRecordTime.setTextColor(Color.WHITE);
}
if
(voiceLength > MAX_LENGTH) {
stopAudioRecord();
}
else
{
tvRecordTime.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));
handler.postDelayed(
this
,
100
);
}
}
};
handler.postDelayed(runnable,
100
);
}
@Override
public
void
onClick(View v) {
// TODO Auto-generated method stub
switch
(v.getId()) {
case
R.id.iv_btn_record:
handleRecord();
break
;
case
R.id.btn_record_reset:
resetAudioRecord();
break
;
case
R.id.btn_record_complete:
stopAudioRecord();
break
;
default
:
break
;
}
}
/**
* 显示播放界面
*/
private
void
showListen() {
layoutListen.setVisibility(View.VISIBLE);
tvLength.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));
tvRecordTime.setVisibility(View.GONE);
resetRecord.setVisibility(View.VISIBLE);
recordOver.setVisibility(View.INVISIBLE);
recordContinue.setVisibility(View.VISIBLE);
seekBar.setProgress(
0
);
tvPosition.setText(
00
:
00
);
btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);
recordContinue.setText(
null
);
recordContinue.setBackgroundResource(R.drawable.record_audio_play);
}
/**
*
* SeekBar进度条改变事件监听类
*/
class
SeekBarChangeEvent
implements
SeekBar.OnSeekBarChangeListener {
int
progress;
@Override
public
void
onProgressChanged(SeekBar seekBar,
int
progress,
boolean
fromUser) {
if
(
null
!= player && player.mediaPlayer !=
null
) {
this
.progress = progress * player.mediaPlayer.getDuration()
/ seekBar.getMax();
tvPosition.setText(TimeUtils
.convertMilliSecondToMinute2(player.currentPosition));
}
}
@Override
public
void
onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public
void
onStopTrackingTouch(SeekBar seekBar) {
if
(player.mediaPlayer !=
null
) {
player.mediaPlayer.seekTo(progress);
}
}
}
@Override
protected
void
onDestroy() {
// TODO Auto-generated method stub
super
.onDestroy();
player.stop();
}
}
|
上面代码注释比较清楚,且好理解,因此不多分析,读者自行学习。下面再来看一下AudioRecordUtils类的代码,该类是音频录制功能的主要实现代码,里面简单地封装了开始录音、暂停录音、停止录音和重新录音几个方法,在开发中只要调用就行,来看看具体的实现代码,如下:
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
|
public
class
AudioRecordUtils {
private
final
int
audioSource = MediaRecorder.AudioSource.MIC;
// 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
private
final
int
sampleRateInHz =
16000
;
// 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
private
final
int
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
// 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
private
final
int
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private
int
inBufSize =
0
;
private
AudioRecord audioRecord;
private
AACEncoder encoder =
null
;
private
ProgressDialog mProgressDialog =
null
;
private
boolean
isRecord =
false
;
private
Context mContext;
/**
* 录制的音频文件名称
*/
private
String mAudioRecordFileName;
private
static
final
int
RECORDED_INIT_DELETE =
0
;
private
static
final
int
RECORDED_COMPLETED_DELETE =
1
;
public
AudioRecordUtils(Context context,String audioRecordFileName){
mContext = context;
mAudioRecordFileName = audioRecordFileName;
initAudioRecord();
}
/**
* 初始化对象
*/
private
void
initAudioRecord(){
inBufSize = AudioRecord.getMinBufferSize(
sampleRateInHz,
channelConfig,
audioFormat);
audioRecord =
new
AudioRecord(
audioSource,
sampleRateInHz,
channelConfig,
audioFormat,
inBufSize);
encoder =
new
AACEncoder();
deleteAllFiles(RECORDED_INIT_DELETE);
mProgressDialog =
new
ProgressDialog(mContext);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setCanceledOnTouchOutside(
false
);
mProgressDialog.setCancelable(
false
);
mProgressDialog.setTitle(提示);
mProgressDialog.setMessage(正在保存录音,请耐心等候......);
}
/**
* 开始录音
*/
public
void
startRecord(){
new
AudioRecordTask().execute();
}
/**
* 暂停录音
*/
public
void
pauseRecord(){
isRecord =
false
;
}
/**
* 停止录音
*/
public
void
stopRecord(){
new
AudioEncoderTask().execute();
}
/**
* 重新录制
*/
public
void
reRecord(){
//重新录制时,删除录音文件夹中的全部文件
deleteAllFiles(RECORDED_INIT_DELETE);
}
private
void
encodeAudio(){
try
{
//读取录制的pcm音频文件
DataInputStream mDataInputStream =
new
DataInputStream(
new
FileInputStream(
FileUtils.getPcmFilePath(mAudioRecordFileName)));
byte
[] b =
new
byte
[(
int
)
new
File(FileUtils.
getPcmFilePath(mAudioRecordFileName)).length()];
mDataInputStream.read(b);
//初始化编码配置
encoder.init(
32000
,
2
, sampleRateInHz,
16
, FileUtils.
getAAcFilePath(mAudioRecordFileName));
//对二进制代码进行编码
encoder.encode(b);
//编码完成
encoder.uninit();
//关闭流
mDataInputStream.close();
try
{
//将aac文件转码成m4a文件
new
AACToM4A().convert(mContext, FileUtils.getAAcFilePath(mAudioRecordFileName),
FileUtils.getM4aFilePath(mAudioRecordFileName));
}
catch
(IOException e) {
Log.e(ERROR, error converting, e);
}
deleteAllFiles(RECORDED_COMPLETED_DELETE);
}
catch
(FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch
(IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
class
AudioRecordTask
extends
AsyncTask<
void
,
void
=
""
>{
@Override
protected
Void doInBackground(Void... params) {
// TODO Auto-generated method stub
if
(audioRecord ==
null
){
initAudioRecord();
}
RandomAccessFile mRandomAccessFile =
null
;
try
{
mRandomAccessFile =
new
RandomAccessFile(
new
File(
FileUtils.getPcmFilePath(mAudioRecordFileName)), rw);
byte
[] b =
new
byte
[inBufSize/
4
];
//开始录制音频
audioRecord.startRecording();
//判断是否正在录制
isRecord =
true
;
while
(isRecord){
audioRecord.read(b,
0
, b.length);
//向文件中追加内容
mRandomAccessFile.seek(mRandomAccessFile.length());
mRandomAccessFile.write(b,
0
, b.length);
}
//停止录制
audioRecord.stop();
mRandomAccessFile.close();
}
catch
(FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return
null
;
}
}
class
AudioEncoderTask
extends
AsyncTask<
void
,
long
=
""
>{
@Override
protected
void
onPreExecute() {
// TODO Auto-generated method stub
super
.onPreExecute();
if
(mProgressDialog !=
null
&& !mProgressDialog.isShowing()){
mProgressDialog.show();
}
}
@Override
protected
Long doInBackground(Void... params) {
// TODO Auto-generated method stub
encodeAudio();
return
null
;
}
@Override
protected
void
onPostExecute(Long result) {
// TODO Auto-generated method stub
super
.onPostExecute(result);
if
(mProgressDialog.isShowing()){
mProgressDialog.cancel();
mProgressDialog.dismiss();
}
}
}
/**
* 清空音频录制文件夹中的所有文件
* @param isRecorded
*/
public
void
deleteAllFiles(
int
isRecorded){
File[] files =
new
File(FileUtils.getAudioRecordFilePath()).listFiles();
switch
(isRecorded) {
case
RECORDED_INIT_DELETE:
for
(File file: files){
file.delete();
}
break
;
case
RECORDED_COMPLETED_DELETE:
for
(File file: files){
if
(!file.getName().equals(mAudioRecordFileName + Constants.M4A_SUFFIX)){
file.delete();
}
}
break
;
default
:
break
;
}
}
}
</
void
,></
void
,>
|
上面代码关键处都有注释,读者可自行学习。自此,我们基本熟悉了实现能够暂停录音功能的关键代码,代码没有全部贴出,想要完整的Demo可在文章末尾下载来仔细研究。最后我再补充一点,就是若读者对录制的音频格式没有严格的要求话,如录制的音频格式是arm格式,则没有必要考虑到音频的编解码问题,因为arm格式的音频文件的文件头信息固定是6个字节的大小,那这种情况读者可以采用文章开头所说的第一种方法,就是每次点击暂停事件都录制成一个arm文件,在最后合并的时候,只需要去掉第2至n个文件的前6个字节,然后进行文件的拷贝合并就行,
原文地址:http://www.2cto.com/kf/201410/347839.html