安卓开发:挑战每天发布一个封装类02--Wav录音封装类AudioChannel 1.0

简介

库名称:AudioChannel

版本:1.0

由于项目需求录音并base64编码存到服务器中,就顺手改装了一个别人的封装类

原封装类地址:Android AudioRecord音频录制wav文件输出 - 简书 (jianshu.com)

描述:此封装类基于AudioRecord实现wav的音频录制,本封装类对原版进行了以下修改:

1.部分修正

(1).可以看到,原封装类继承Thread,代码逻辑很清晰,因此改动过程也较轻松,单次运行能够正常,但是在二次运行,发现报错:

D/CompatibilityChangeReporter: Compat change id reported: 147798919; UID 10428; state: ENABLED
W/System.err: java.lang.IllegalThreadStateException
W/System.err:     at java.lang.Thread.start(Thread.java:960)at com.yy.audiochannaldemo.AudioChannel.startLive(AudioChannel.java:84)

经过跟踪发现,在二次运行的时候,线程的state变为TERMINATED,这意味着线程已经完成了它的执行并且已经退出。一旦线程终止,不能重新启动,因此新版封装类不再继承Thread,而是通过priavate线程重建函数initThread来实现。

(2).首先AudioRecord不能够直接保存录音为wav,因此必须先保存为pcm文件,再通过头部写入数据,转换为wav文件,在这个过程中注意到原封装库,没有对保存pcm的文件进行删除处理,后续可能导致容量过大

(3).构建函数,传入context,以此就无需动态授权外部存储写入权限,也方便后续需要context的操作部分

2.权限控制

在使用过程注意到,原版库并没有处理权限申请,在改版加上了,6.0以上安卓加入了权限控制,另外去除了使用外部存储,需要额外动态授权的情况,直接存入cache

3.功能实现

在原先基础上加入了音高、声音贝计算,并通过onResult接口回调这三个变量,不过db和hz都有一定偏差


一、配置部分

需要先在清单中加入这两项:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

此封装库需要配置的部分就这么多。

需要在build.gradle加入以下依赖添加代码

    implementation 'com.github.wendykierp:JTransforms:3.1'

二、代码部分

1.PcmToWavUtil.java:Pcm转Wav工具类

public class PcmToWavUtil {private int mBufferSize;  //缓存的音频大小private int mSampleRate = 8000;// 8000|16000private int mChannelConfig = AudioFormat.CHANNEL_IN_STEREO;   //立体声private int mChannelCount = 2;private int mEncoding = AudioFormat.ENCODING_PCM_16BIT;public PcmToWavUtil() {this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannelConfig, mEncoding);}public PcmToWavUtil(int sampleRate, int channelConfig, int channelCount, int encoding) {this.mSampleRate = sampleRate;this.mChannelConfig = channelConfig;this.mChannelCount = channelCount;this.mEncoding = encoding;this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannelConfig, mEncoding);}public void pcmToWav(String inFilename, String outFilename) {FileInputStream in;FileOutputStream out;long totalAudioLen;long totalDataLen;long longSampleRate = mSampleRate;int channels = mChannelCount;long byteRate = 16 * mSampleRate * channels / 8;byte[] data = new byte[mBufferSize];try {in = new FileInputStream(inFilename);out = new FileOutputStream(outFilename);totalAudioLen = in.getChannel().size();totalDataLen = totalAudioLen + 36;//44-8(RIFF+dadasize(4个字节))writeWaveFileHeader(out, totalAudioLen, totalDataLen,longSampleRate, channels, byteRate);while (in.read(data) != -1) {out.write(data);}in.close();out.close();}  catch (IOException e) {e.printStackTrace();}}/*** 加入wav文件头*/private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,long totalDataLen, long longSampleRate, int channels, long byteRate)throws IOException {byte[] header = new byte[44];header[0] = 'R'; // RIFF/WAVE headerheader[1] = 'I';header[2] = 'F';header[3] = 'F';header[4] = (byte) (totalDataLen & 0xff);header[5] = (byte) ((totalDataLen >> 8) & 0xff);header[6] = (byte) ((totalDataLen >> 16) & 0xff);header[7] = (byte) ((totalDataLen >> 24) & 0xff);header[8] = 'W';  //WAVEheader[9] = 'A';header[10] = 'V';header[11] = 'E';header[12] = 'f'; // 'fmt ' chunkheader[13] = 'm';header[14] = 't';header[15] = ' ';header[16] = 16;  // 4 bytes: size of 'fmt ' chunkheader[17] = 0;header[18] = 0;header[19] = 0;header[20] = 1;   // format = 1header[21] = 0;header[22] = (byte) channels;header[23] = 0;header[24] = (byte) (longSampleRate & 0xff);header[25] = (byte) ((longSampleRate >> 8) & 0xff);header[26] = (byte) ((longSampleRate >> 16) & 0xff);header[27] = (byte) ((longSampleRate >> 24) & 0xff);header[28] = (byte) (byteRate & 0xff);header[29] = (byte) ((byteRate >> 8) & 0xff);header[30] = (byte) ((byteRate >> 16) & 0xff);header[31] = (byte) ((byteRate >> 24) & 0xff);header[32] = (byte) (2 * 16 / 8); // block alignheader[33] = 0;header[34] = 16;  // bits per sampleheader[35] = 0;header[36] = 'd'; //dataheader[37] = 'a';header[38] = 't';header[39] = 'a';header[40] = (byte) (totalAudioLen & 0xff);header[41] = (byte) ((totalAudioLen >> 8) & 0xff);header[42] = (byte) ((totalAudioLen >> 16) & 0xff);header[43] = (byte) ((totalAudioLen >> 24) & 0xff);out.write(header, 0, 44);}
}

2.AudioChannel.java:录音封装类主体

package com.yy.audiochannaldemo;import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
import android.widget.Toast;import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import org.jtransforms.fft.DoubleFFT_1D;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;public class AudioChannel {private int sampleRate;private int channelConfig;private int minBufferSize;private byte[] buffer;private Thread recordThread;private AudioRecord audioRecord;private boolean isRecoding;private SimpleDateFormat sdf;String filename;Context context;long startTime;private onResult onResult;private DoubleFFT_1D fft;public AudioChannel(int sampleRate, int channels, Context context) {this.sampleRate = sampleRate;this.context = context;channelConfig = channels == 2 ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);Log.i("AudioChannel", "minBufferSize: " + minBufferSize);buffer = new byte[minBufferSize];sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");fft = new DoubleFFT_1D(minBufferSize/ 2);}private double calculateRMS(byte[] audioBuffer) {double sum = 0.0;for (byte sample : audioBuffer) {sum += sample * sample;}return Math.sqrt(sum / audioBuffer.length);}// 将RMS值转换为分贝值的方法private double rmsToDB(double rms) {// 假设参考值为1(通常是最小可听声音的RMS值)double reference = 1.0;return 20 * Math.log10(rms / reference);}private double calculateHZ(byte[] buffer) {// Convert byte array to double array for FFTdouble[] fftBuffer = new double[buffer.length / 2];for (int i = 0; i < buffer.length; i += 2) {short sample = (short) ((buffer[i] << 8) | (buffer[i + 1] & 0xFF));fftBuffer[i / 2] = sample;}// 执行 FFTfft.realForward(fftBuffer);double maxAmplitude = 0.0;int pitchIndex = 0;for (int i = 0; i < fftBuffer.length-1; i++) {double amplitude = fftBuffer[i] * fftBuffer[i] + fftBuffer[i + 1] * fftBuffer[i + 1];if (i < fftBuffer.length / 2 && amplitude > maxAmplitude) {maxAmplitude = amplitude;pitchIndex = i;}}//计算hz,不过偏差比较大double frequency = (double) pitchIndex * sampleRate / (fftBuffer.length / 2) ;return frequency /100;}void initPremission() {ActivityCompat.requestPermissions((Activity)context, new String[]{Manifest.permission.RECORD_AUDIO}, 169);}void initThread() {this.recordThread=new Thread(){ //开线程@Overridepublic void run() {audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT, minBufferSize);audioRecord.startRecording();FileOutputStream writer = null;Date current = new Date();String time = sdf.format(current);byte[] audioBuffer = new byte[minBufferSize];  // 创建一个缓冲区try {filename = context.getCacheDir() + "/" + time + ".pcm"; //cache目录不需要权限writer = new FileOutputStream(filename, true);while (!Thread.currentThread().isInterrupted() && isRecoding) { //如果线程没有Interrupted而且isRecording变量为True代表在录制状态的情况if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {audioRecord.read(audioBuffer, 0, minBufferSize); // 读取音频数据到缓冲区double rms = calculateRMS(audioBuffer);double db = rmsToDB(rms); //db的值double hz = calculateHZ(audioBuffer);int seaconds =(int) (System.currentTimeMillis() -startTime) /1000;if (isRecoding)  {((Activity)context).runOnUiThread(new Runnable() {@Overridepublic void run() {onResult.update(seaconds,db,hz); //如果还在线程运行状态把信息回调出来}});}writer.write(audioBuffer);}}} catch (IOException e) {e.printStackTrace();} finally {audioRecord.stop();audioRecord.release();audioRecord = null;try {writer.close();} catch (IOException e) {e.printStackTrace();}}new PcmToWavUtil(44100,  AudioFormat.CHANNEL_IN_STEREO, 2, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(filename, filename.replace("pcm","wav"));}};}public void startLive() { //录制initThread();initPremission();if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) ==PackageManager.PERMISSION_GRANTED) {isRecoding = true;recordThread.start();startTime = System.currentTimeMillis();} else {Toast.makeText(context,"没有录音权限",Toast.LENGTH_LONG).show();}}public void stopLive(int mode) { //mode为-1时代表取消,为0代表取消if (!isRecoding) return;try {isRecoding = false;recordThread.join();} catch (Exception e){isRecoding = false;e.printStackTrace();}new File(filename).delete();switch (mode) {case 0:onResult.finish(filename.replace("pcm","wav")); //正常结束后pcm会被转换成wavbreak;case -1:new File(filename.replace("pcm","wav")).delete();onResult.cancel(); //取消回调break;}}public interface onResult { //三个回调void update(int seaconds,double db,double hz);void finish(String filename);void cancel();}public void onResult(onResult onResult) { //功能点击this.onResult = onResult;}}

三.Demo部分

Demo下载地址:

gitee地址:

AudioChannel/demo · keyxh/AndroidUtils - 码云 - 开源中国 (gitee.com)

csdn地址:【免费】安卓开发:挑战每天发布一个封装类02-Wav录音封装类AudioChannel1.0资源资源-CSDN文库

在Demo中有两个Actvity

1.MainActvity:简易demo,示范调用

MainActvity的案例是普通调用,调用过程会将参数打印出来,结束时会将音频转base64,界面和logcat如下图所示,MainActvity的demo是没有任何交互

第二个demo:PPActvity(音高测试仪)

本来想做音高测试仪的,后来音高频率转换(例如440HZ转A4)没有整出来,后面有空再修改投放gitee,目前最终效果如下:

由福州职业技术学校温辉编写,欢迎搬运帮助更多人,但请带上以上这句。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2799774.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

2024年雾锁王国Enshrouded服务器搭建方法,最新教程

阿里云雾锁王国服务器搭建教程是基于计算巢服务&#xff0c;3分钟即可成功创建Enshrouded游戏服务器&#xff0c;阿里云8核32G雾锁王国专用游戏服务器90元1个月、271元3个月&#xff0c;阿里云服务器网aliyunfuwuqi.com亲自整理雾锁王国服务器详细搭建教程&#xff1a; 一、前…

MyBatis-Plus:通用分页实体封装

分页查询实体&#xff1a;PageQuery package com.example.demo.demos.model.query;import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.Data; import org.springframework.util.St…

设计模式浅析(六) ·命令模式

设计模式浅析(六) 命令模式 日常叨逼叨 java设计模式浅析&#xff0c;如果觉得对你有帮助&#xff0c;记得一键三连&#xff0c;谢谢各位观众老爷&#x1f601;&#x1f601; 命令模式 概念 命令模式&#xff08;Command Pattern&#xff09;是一种行为设计模式&#xff0c…

实战打靶集锦-025-HackInOS

文章目录 1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查5. 提权5.1 枚举系统信息5.2 探索一下passwd5.3 枚举可执行文件5.4 查看capabilities位5.5 目录探索5.6 枚举定时任务5.7 Linpeas提权 靶机地址&#xff1a;https://download.vulnhub.com/hackinos/HackInOS.ova 1. 主机…

万界星空科技MES系统,实现数字化智能工厂

万界星空科技帮助制造型企业解决生产过程中遇到的生产过程不透明&#xff0c;防错成本高&#xff0c;追溯困难&#xff0c;品质不可控&#xff0c;人工效率低下&#xff0c;库存积压&#xff0c;交期延误等问题&#xff0c;从而达到“降本增效”的目标。打通各个信息孤岛&#…

Android进阶(二十九) 走近 IntentFilter

文章目录 一、什么是IntentFilter &#xff1f;二、IntentFilter 如何过滤隐式意图&#xff1f;2.1 动作测试2.2 类别测试2.3 数据测试 一、什么是IntentFilter &#xff1f; 如果一个 Intent 请求在一片数据上执行一个动作&#xff0c; Android 如何知道哪个应用程序&#xf…

力扣 面试题 05.06. 整数转换

思路&#xff1a; 牵扯到二进制数&#xff0c;基本上要考虑位运算符&#xff0c;相关知识可以见http://t.csdnimg.cn/fzts7 之前做过类似的题目&#xff0c;大致思路就是先用按位异或^找出不同位&#xff0c;再用n&&#xff08;n-1&#xff09;计算出不同位的个数&#x…

恒峰—高压森林应急消防泵:保障森林安全

在自然环境中&#xff0c;森林起着至关重要的作用&#xff0c;它们不仅为我们提供木材和清新的空气&#xff0c;还是众多生物的家园。然而&#xff0c;随着人类活动的增长&#xff0c;森林面临着越来越严重的火灾威胁。为了保护这些宝贵的资源&#xff0c;我们需要一种高效、可…

10.CSS3的calc函数

CSS3 的 calc 函数 经典真题 CSS 的计算属性知道吗&#xff1f; CSS3 中的 calc 函数 calc 是英文单词 calculate&#xff08;计算&#xff09;的缩写&#xff0c;是 CSS3 的一个新增的功能。 MDN 的解释为可以用在任何长度、数值、时间、角度、频率等处&#xff0c;语法如…

为全志D1开发板移植LVGL日历控件和显示天气

利用TCP封装HTTP包请求天气信息 Linux还真是逐步熟悉中&#xff0c;现在才了解到Linux即没有原生的GUI&#xff0c;也没有应用层协议栈&#xff0c;所以要实现HTTP应用&#xff0c;必须利用TCP然后自己封装HTTP数据包。本篇即记录封装HTTP数据包&#xff0c;到心知天气请求天气…

【Linux】再谈进程地址空间

目录 一、引入 二、物理内存和外设空间的交互 三、解决页表过大问题 一、引入 我们在往期的博客中有讲解过进程地址空间&#xff1a;【Linux】进程地址空间 但是在上述博客中我们只是对进程地址空间的左边部分详细进行了讲解&#xff0c;下面我们就来谈谈右边的部分&#…

git版本回退在eclipse和命令中的操作

一.背景 老程序员了&#xff0c;熟悉eclipsesvn&#xff0c;git用的不溜。近几年用了git&#xff0c;偶尔修改了某个文件希望放弃本次修改重新恢复到最新版本重新修改。或者回退到某个版本&#xff0c;再修改。记录一下Eclipse中的操作&#xff0c;和命令操作的情况。 二.Ecli…

操作系统导论-课后作业-ch19

1. 本书在第6章中有过介绍&#xff0c;gettimeofday函数最多精确到us&#xff0c;并且大致精确&#xff08;并不完全精确&#xff09;&#xff0c;需要多迭代几次减少误差&#xff0c;循环次数太多也会导致结束时间小于开始时间&#xff08;即回滚&#xff09;的现象&#xff…

每日shell脚本之超级整合程序3.0

每日shell脚本之超级整合程序3.0 本期带来之前的升级版2.0整合脚本程序,学习工作小利器,同时模块化构建方便二次开发。 上图 上源码 #!/usr/bin/bash # *******************************************# # * CDDN : M乔木 # # * qq邮箱 …

社区分享|中华保险基于MeterSphere开展接口自动化测试

中华联合保险集团股份有限公司&#xff08;以下简称为“中华保险”&#xff09;始创于1986年&#xff0c;是全国唯一一家以“中华”冠名的国有控股保险公司。截至2022年12月底&#xff0c;中华保险总资产为1006.06亿元&#xff0c;在全国拥有超过2900个营业网点&#xff0c;员工…

06 分频器设计

分频器简介 实现分频一般有两种方法&#xff0c;一种方法是直接使用 PLL 进行分频&#xff0c;比如在 FPGA 或者 ASIC 设计中&#xff0c;都可以直接使用 PLL 进行分频。但是这种分频有时候受限于 PLL 本身的特性&#xff0c;无法得到频率很低的时钟信号&#xff0c;比如输入 …

Windows安装PHP及在VScode中配置插件,使用PHP输出HelloWorld

安装PHP PHP官网下载地址(8.3版本)&#xff1a;PHP For Windows&#xff1a;二进制文件和源代码发布 点击下载.zip格式压缩包&#xff1a; 历史版本在Old archives中下载。推荐在Documentation download中下载官方文档&#xff0c;方便学习。 下载完成后在一个顺眼的地方解压压…

Point.java

Point.java 让我们来找茬&#xff0c;(⊙o⊙)看看哪里不一样咯&#xff01;&#xff01;&#xff01; package algorithm_graphics_2;/** Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject t…

Zabbix 6.2.1 安装

目录 1、监控介绍 监控的重要性 网站的可用性 监控范畴 如何监控 2、Zabbix 介绍 zabbix 简介 zabbix 主要功能 zabbix 监控范畴 Zabbix 监控组件 zabbix 常见进程 zabbix agentd 工作模式 zabbix 环境监控中概念 3、搭建LNMP 拓扑规划 安装MySQL 安装 Nginx …

【视频编解码】M-JPEG压缩、H.264压缩 对比

简介 参考这篇文章&#xff1a;https://blog.csdn.net/qq_41248872/article/details/83590337 写的比较好&#xff0c;这里就不赘述了。 我们在视频传输的时候&#xff0c;需要压缩&#xff0c;常见的压缩包括: jpeg 压缩h264 压缩 当然使用最多的还是 264, 毕竟他的压缩比…