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就有重复部分,运行时同时抢占资源从而导致出错。

这边也是对程序设计思维的转变,我接触并发编程实际上比较少,希望这是一个好的开头,在并发多线程这边可以有更多的思考和尝试。

------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值