Android定时任务实现每隔一段时间切分录音文件,audiorecord实现录音
为了避免音频大文件传输影响网络传输效率,选择边录边切分进行传输,比如我从开始录音的button按钮到结束录音的button按钮的点击 大概间隔了70s,而我需要的是30s切分成一段pcm文件,每隔30s存储,那么这70s就会被切分成30s,30s,10s
1.按钮点击事件绑定
首先页面的绘制不做过多讲述,使用mvvm框架,两个按钮绑定了viewmodel里面的方法,又一个textview是辅助我查看点击了哪些按钮,注释掉的两个是为了删除音频存储文件夹里面的全部文件。
public class RecordRadioViewModel extends ViewModel {
public MutableLiveData<String> buttonClickedMessage = new MutableLiveData<>();
public RecordRadioViewModel() {
buttonClickedMessage.setValue("未点击任何按钮,尚未进行任何处理");
}
public void onButtonStartClicked() {
// LameUtils lameUtils=new LameUtils();
buttonClickedMessage.setValue("点击了开始录音");
Radiorecorder.getInstance().start();
// File mvtImgDirectory=new File(AudioFileUtils.getInstance().getRootPath()); 测试使用 删除文件夹里面的所有文件
// AudioFileUtils.getInstance().deleteFile(mvtImgDirectory);
}
public void onButtonStopClicked() {
buttonClickedMessage.setValue("点击了停止录音");
Radiorecorder.getInstance().stop();
}
}
2.audiorecord封装
有很多audiorecord封装的教程,我这边就不做赘述,只写关于核心的方法
开启录音 开启录音的同时写入文件,这边写入文件我没有再开线程,开线程在点击开始录音处
public void startRecording(String pcmFileName) {
if (audioRecord != null) {
isrecording = true;
while (isrecording) {
audioRecord.startRecording();
readFile = true;
this.saveFile(pcmFileName);
}
}
}
//录音后存储文件
public void saveFile(String pcmFileName) {
byte data[] = new byte[bufferSizeInBytes];
Log.d(TAG, "存储的文件路径:" + pcmFileName);
int read;
try {
os = new FileOutputStream(pcmFileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
while (getAudioRecord().getRecordingState() == AudioRecord.RECORDSTATE_RECORDING && readFile) {
read = getAudioRecord().read(data, 0, bufferSizeInBytes);
//清除缓冲区
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
// 关闭输出流
try {
Log.i(TAG, "关闭输出流!");
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
关闭录音
public void stopRecording() {
if (audioRecord != null && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
audioRecord.stop();
isrecording = false;
readFile = false;
}
}
把以上三个方法封入一个AudioRecordManager类内
3.按钮点击事件实现
一开始绑定的时候绑到了一个AudioRecorder类的start和stop方法,以下的方法只关系到这个类
public void start() {
isRecording = true;
currentFileName.set(getFileName());
recordingThread.start();//录音线程
stoprecordThread.start();//定时关闭线程
}
public void stop() {
Log.d(TAG, "请求停止录音");
AudioRecordManager.getInstance().stopRecording();
concurrentLinkedQueue.add(currentFileName.get());
Log.d(TAG, "点击了暂停 加入队列的值为 currentFileName.get()" + currentFileName.get());
//顺序不能乱 不然最后一段录音可能会丢失
isRecording = false;
isForcedStop = true;
AudioRecordManager.getInstance().release();
scheduledExecutorService.shutdown();
}
//处理录音线程
private Thread recordingThread = new Thread(() -> {
while (isRecording) {
// 开启录音
Log.d(TAG, "开始录音");
AudioRecordManager.getInstance().startRecording(currentFileName.get());
}
});
private Thread stoprecordThread = new Thread(() -> {
// //失败原因:无法中断主线程的IO占用
scheduledExecutorService.scheduleWithFixedDelay(() -> {
Log.d(TAG, "5分钟已过,检查是否需要停止录音");
Log.d(TAG, "isRecording:" + isRecording + " isForcedStop:" + isForcedStop);
if (isRecording && !isForcedStop) {
Log.d(TAG, "自动停止当前的录音,分割文件");
concurrentLinkedQueue.add(currentFileName.get());//存好的pcm文件加入队列
currentFileName.set(getFileName());
AudioRecordManager.getInstance().stopRecording();
// Log.d(TAG, "再次开始新的录音");
// AudioRecordManager.getInstance().startRecording(currentFileName.get());
}
}, 5, 5, TimeUnit.MINUTES); // 5 分钟
});
注释掉的两句大概是一开始失败的思路,以及也是现在想要记录的主要原因。
首先 scheduledExecutorService.scheduleWithFixedDelay不能放在主线程内,就是录音线程内然后去停止他的io写入,会停不下来。
大概原因如下
public class SeduledTask {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
new Thread(()->{
while (true){
System.out.println("主线程运行,运行时间:"+Calendar.getInstance().getTime());
try {
Thread.sleep(9000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
new Thread(()->{
scheduledExecutorService.scheduleWithFixedDelay(()->{
System.out.println("定时每隔4s线程运行,运行时间:"+Calendar.getInstance().getTime());
},4,4, TimeUnit.SECONDS);
}).start();
}
}
如果schedule定时线程放在主线程内会因为定时(第一个参数是运行开始时间后每隔第二个参数间隔运行,第三个参数是时间单位。
如果放进主线程运行内,主线程运行时间为9s,定时线程只会进入排队,等待主线程运行完毕,无法对主线程进行任何操作,只有如上修改,把定时任务再放入新的线程内,保证定时任务和主线程可以并行运行,以便可以阻断主线程的操作(定时任务体内定义)
修改为两个线程后按间隔定时可以实现,理论成立。
而在我的定时停止录音线程中还有一段被注释掉的
// Log.d(TAG, "再次开始新的录音");
// AudioRecordManager.getInstance().startRecording(currentFileName.get());
这一段排查花费很久,因为在两个线程可以停止录音的理论成立后,录音切分代码运行后一直内存溢出,飘红一大片,当时以为是第二个线程不可以中断第一个线程的io操作,实际上是一个逻辑的混淆。
在这里我预设的主线程需要完成的任务是:开启录音并存储文件
而在radiorecord封装类中,存储文件只有收到停止信号时是可以完整存下来的。
所以我预设的定时任务需要完成的任务是:中断录音,主线程的io也随之断了->再次开启新的录音,写入文件。
实际上这边有个逻辑的混淆,线程a已经在做A操作,另一个线程需要做的就只是B操作,而不是B+A,否则的话他跟线程a就有重复部分,运行时同时抢占资源从而导致出错。
这边也是对程序设计思维的转变,我接触并发编程实际上比较少,希望这是一个好的开头,在并发多线程这边可以有更多的思考和尝试。
------