当前位置: 首页 > article >正文

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

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

------


http://www.kler.cn/a/444936.html

相关文章:

  • 【学习总结|DAY021】Java 多线程
  • 谷歌浏览器的扩展市场使用指南
  • es使用knn向量检索中numCandidates和k应该如何配比更合适
  • 【Jenkins】pipeline 的基础语法以及快速构建一个 jenkinsfile
  • 数据压缩比 38.65%,TDengine 重塑 3H1 的存储与性能
  • leecode494.目标和
  • Java全栈项目 - 学生档案管理系统
  • 网络安全等级保护—定级
  • 我在广州学 Mysql 系列——有关 Mysql 函数的练习
  • 发送webhook到飞书机器人
  • Kingbase数据库备份还原操作手册
  • 解锁 Jenkins+Ant+Jmeter 自动化框架搭建新思路
  • 【Ubuntu】设置静态Ip
  • HTML5+CSS3+JS制作精美的宠物主题网站(内附源码,含5个页面)
  • 前端之CSS光速入门
  • 在Win11系统上安装Android Studio
  • 【C#】方法参数的修饰符ref 与 out
  • 华纳云:虚拟服务器之间如何分布式运行?
  • PostgreSQL的交互式终端使用一系列命令来获取有关文本搜索配置对象的信息
  • WPF Binding 绑定
  • linux常用命令(touch、cat、less、head、tail)
  • Scala的惰性求值:深入理解与实践
  • 回归预测模型 | LSTM、CNN、Transformer、TCN、串行、并行模型集合
  • 最大子数组和 最大子数组和(有长度限制) 最大m段子数组和
  • windows openssl编译x64版libssl.lib,编译x64版本libcurl.lib,支持https,vs2015编译器
  • 【NVIDIA】启动ubuntu后显卡驱动丢失