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

android录音生成wav

站在巨人的肩膀上

已经有人实现了,代码位置github

使用

实现源码主要是这几个文件
在 record/src/main/java/com/dreamfish/record/下的
AudioRecorder.java
FileUtil.java
PcmToWav.java
RecordStreamListener.java
WaveHeader.java

调用如下

private AudioRecorder audioRecorder;
audioRecorder = AudioRecorder.getInstance();
mRecordingFile = "test";try {mRecording = true;if (audioRecorder.getStatus() == AudioRecorder.Status.STATUS_NO_READY) {//初始化录音String fileName = "ntt_record_test";audioRecorder.createAudio(fileName, MediaRecorder.AudioSource.MIC,16000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);audioRecorder.startRecord(null);mChronometer.setBase(SystemClock.elapsedRealtime());mChronometer.start();mHandler.sendEmptyMessageDelayed(MSG_START_PLAY_FOR_RECORD, 2000);} else {//停止录音audioRecorder.stopRecord();mRecording = false;}} catch (IllegalStateException e) {mRecording = false;Toast.makeText(PhoneLoopBackTest.this, e.getMessage(), Toast.LENGTH_SHORT).show();}

录音参数

采样率

定义了每秒从连续信号中提取并组成离散信号的采样个数,单位是赫兹(Hz)。采样率越高,声音越接近原始数据。常见的采样率有 8000Hz(电话所用采样率)、11025Hz、22050Hz(无线电广播所用采样率)、32000Hz(miniDV 数码视频 camcorder、DAT (LP mode) 所用采样率)、44100Hz(音频 CD、也常用于 MPEG-1 音频(VCD、SVCD、MP3)所用采样率)、48000Hz(miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率)、96000Hz 或 192000Hz(DVD - Audio、一些 LPCM DVD 音轨、Blu - ray Disc(蓝光盘)音轨、和 HD - DVD(高清晰度 DVD)音轨所用所用采样率)。在 Android 中,AudioRecord 默认值是 22050Hz,并且采样率必须位于 (4000, 48000) Hz 之间,44100Hz 是目前唯一一个能够在所有的设备上使用的频率。

采样位深

指每个采样点数据的位数,常见的有 16Bit、20Bit、24Bit 等。位深度较高时,有更大的动态范围可利用,可以记录更低电平的细节。例如,16Bit 可以记录大概 96 分贝的动态范围,每一个比特大约可以记录 6 分贝的声音;20Bit 可记录的动态范围大概是 120dB;24Bit 就大概是 144dB。

比特率

指每秒传送的比特(bit)数,单位为 bps(Bit Per Second)。比特率 = 采样率 × 采样位数 × 声道数。比特率越高,传送的数据越大,音质越好。如采样率为 44.1KHz,以 16bit 采样,声道数为 2,其音频比特率为 44100×16×2 = 1411200bps = 1378kbps,1 秒钟的数据量为 1411200/8 = 176400 个字节(B)。不同的应用场景有不同的推荐比特率,如 16Kbps 为电话音质,128Kbps 适合磁带,192Kbps 为 CD 音质,256Kbps 适用于 Studio 音乐工作室。

声道

指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号。声道数也就是声音录制时的音源数量或回放时相应的扬声器数量,常见的有单声道和双声道。单声道默认一个声道,双声道默认两个声道。在 Android 中,单声道(CHANNEL_IN_MONO)在所有设备上都支持,双声道立体声(CHANNEL_IN_STEREO)则可提供更丰富的声音空间感。

音频编码格式

是音频量化的策略,常用的编码方式如 PCM 能够达到最高保真,很多编码都是在 PCM 上重新处理。此外,还有一些压缩编码格式,如 MP3、AAC、OGG、WMA、m4a、AMR 等,用于减少音频数据的存储空间。例如,MediaRecorder 可以直接将采集到的音频数据转化为指定的编码格式并保存,而 AudioRecord 最后得到的文件为 pcm 格式文件,若要播放,可能需要转化为其他格式。

录左右声道

这个代码示例中录的是单声道,改成双通道后,会发现播放出来的录音变的很缓慢,定位原因是wav头设置不正常, 原代码录音参数都是写死的,需要进行修改。

MainActivity.java中,修改createDefaultAudio,使用createAudio传CHANNEL_IN_STEREO进行双通道录音。另外传sampleRate与bit rate等参数进行自定义。

-                        audioRecorder.createDefaultAudio(fileName);
+                        //audioRecorder.createDefaultAudio(fileName);
+                        audioRecorder.createAudio(fileName, MediaRecorder.AudioSource.MIC,
+                                16000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);

自定义录音参数

diff --git a/record/src/main/java/com/dreamfish/record/AudioRecorder.java b/record/src/main/java/com/dreamfish/record/AudioRecorder.java
index 8d750e0..d34ca79 100644
--- a/record/src/main/java/com/dreamfish/record/AudioRecorder.java
+++ b/record/src/main/java/com/dreamfish/record/AudioRecorder.java
@@ -25,9 +25,11 @@ public class AudioRecorder {//采用频率//44100是目前的标准,但是某些设备仍然支持22050,16000,11025//采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
-    private final static int AUDIO_SAMPLE_RATE = 16000;
+    //private final static int AUDIO_SAMPLE_RATE = 16000;
+    private static int audioSampleRate = 16000;//声道 单声道private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
+//编码private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;// 缓冲区字节大小
@@ -68,11 +70,13 @@ public class AudioRecorder {* 创建录音对象*/public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
-        // 获得缓冲区字节大小
+        audioSampleRate = sampleRateInHz;
+                // 获得缓冲区字节大小bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
-                channelConfig, channelConfig);
+                channelConfig, audioFormat);audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);this.fileName = fileName;
+        status = Status.STATUS_READY;}/**
@@ -81,10 +85,11 @@ public class AudioRecorder {* @param fileName 文件名*/public void createDefaultAudio(String fileName) {
+        audioSampleRate = 16000;// 获得缓冲区字节大小
-        bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,
+        bufferSizeInBytes = AudioRecord.getMinBufferSize(audioSampleRate,AUDIO_CHANNEL, AUDIO_ENCODING);
-        audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);
+        audioRecord = new AudioRecord(AUDIO_INPUT, audioSampleRate, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);this.fileName = fileName;status = Status.STATUS_READY;}
@@ -155,8 +160,10 @@ public class AudioRecorder {}//清除filesName.clear();
+
+                // 不同参数的录音转成wav时,也要设置好相应的wav头,不然可能会导致声音异常,比如过快或者很慢的现象//将多个pcm文件转化为wav文件
-                mergePCMFilesToWAVFile(filePaths);
+                mergePCMFilesToWAVFile(filePaths, (short) 16, audioSampleRate);} else {//这里由于只要录音过filesName.size都会大于0,没录音时fileName为null
@@ -253,11 +260,12 @@ public class AudioRecorder {** @param filePaths*/
-    private void mergePCMFilesToWAVFile(final List<String> filePaths) {
+    private void mergePCMFilesToWAVFile(final List<String> filePaths, final short bitPerSample, final int samplesPerSec) {new Thread(new Runnable() {@Overridepublic void run() {
-                if (PcmToWav.mergePCMFilesToWAVFile(filePaths, FileUtil.getWavFileAbsolutePath(fileName))) {
+                if (PcmToWav.mergePCMFilesToWAVFile(filePaths, bitPerSample, samplesPerSec,
+                        FileUtil.getWavFileAbsolutePath(fileName), false)) {//操作成功} else {//操作失败
@@ -272,11 +280,14 @@ public class AudioRecorder {/*** 将单个pcm文件转化为wav文件*/
-    private void makePCMFileToWAVFile() {
+    private void makePCMFileToWAVFile(final short bitPerSample, final int samplesPerSec) {new Thread(new Runnable() {@Overridepublic void run() {
-                if (PcmToWav.makePCMFileToWAVFile(FileUtil.getPcmFileAbsolutePath(fileName), FileUtil.getWavFileAbsolutePath(fileName), true)) {
+                if (PcmToWav.makePCMFileToWAVFile(FileUtil.getPcmFileAbsolutePath(fileName),
+                        bitPerSample, samplesPerSec,
+                        FileUtil.getWavFileAbsolutePath(fileName),
+                        true)) {//操作成功} else {//操作失败
diff --git a/record/src/main/java/com/dreamfish/record/FileUtil.java b/record/src/main/java/com/dreamfish/record/FileUtil.java
index 27debf5..69044bf 100644
--- a/record/src/main/java/com/dreamfish/record/FileUtil.java
+++ b/record/src/main/java/com/dreamfish/record/FileUtil.java
@@ -15,13 +15,13 @@ import java.util.List;*/public class FileUtil {-    private static String rootPath = "pauseRecordDemo";
+    private static String rootPath = "/sdcard/Android/data/test/cache";//原始文件(不能播放)
-    private final static String AUDIO_PCM_BASEPATH = "/" + rootPath + "/pcm/";
+    private final static String AUDIO_PCM_BASEPATH = "/pcm/";//可播放的高质量音频文件
-    private final static String AUDIO_WAV_BASEPATH = "/" + rootPath + "/wav/";
+    private final static String AUDIO_WAV_BASEPATH = "/wav/";-    private static void setRootPath(String rootPath) {
+    public static void setRootPath(String rootPath) {FileUtil.rootPath = rootPath;}@@ -37,7 +37,7 @@ public class FileUtil {if (!fileName.endsWith(".pcm")) {fileName = fileName + ".pcm";}
-            String fileBasePath = Environment.getExternalStorageDirectory().getAbsolutePath() + AUDIO_PCM_BASEPATH;
+            String fileBasePath = rootPath + AUDIO_PCM_BASEPATH;File file = new File(fileBasePath);//创建目录if (!file.exists()) {
@@ -62,7 +62,7 @@ public class FileUtil {if (!fileName.endsWith(".wav")) {fileName = fileName + ".wav";}
-            String fileBasePath = Environment.getExternalStorageDirectory().getAbsolutePath() + AUDIO_WAV_BASEPATH;
+            String fileBasePath = rootPath + AUDIO_WAV_BASEPATH;File file = new File(fileBasePath);//创建目录if (!file.exists()) {
@@ -92,7 +92,7 @@ public class FileUtil {*/public static List<File> getPcmFiles() {List<File> list = new ArrayList<>();
-        String fileBasePath = Environment.getExternalStorageDirectory().getAbsolutePath() + AUDIO_PCM_BASEPATH;
+        String fileBasePath = rootPath + AUDIO_PCM_BASEPATH;File rootFile = new File(fileBasePath);if (!rootFile.exists()) {
@@ -115,7 +115,7 @@ public class FileUtil {*/public static List<File> getWavFiles() {List<File> list = new ArrayList<>();
-        String fileBasePath = Environment.getExternalStorageDirectory().getAbsolutePath() + AUDIO_WAV_BASEPATH;
+        String fileBasePath = rootPath + AUDIO_WAV_BASEPATH;File rootFile = new File(fileBasePath);if (!rootFile.exists()) {
diff --git a/record/src/main/java/com/dreamfish/record/PcmToWav.java b/record/src/main/java/com/dreamfish/record/PcmToWav.java
index 9d18d94..6c6d9a5 100644
--- a/record/src/main/java/com/dreamfish/record/PcmToWav.java
+++ b/record/src/main/java/com/dreamfish/record/PcmToWav.java
@@ -28,7 +28,10 @@ public class PcmToWav {* @return true|false*/public static boolean mergePCMFilesToWAVFile(List<String> filePathList,
-                                                 String destinationPath) {
+                                                 short bitPerSample,
+                                                 int samplesPerSec,
+                                                 String destinationPath,
+                                                 boolean deletePcmFile) {File[] file = new File[filePathList.size()];byte buffer[] = null;@@ -46,10 +49,10 @@ public class PcmToWav {// 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)header.fileLength = TOTAL_SIZE + (44 - 8);header.FmtHdrLeth = 16;
-        header.BitsPerSample = 16;
+        header.BitsPerSample = bitPerSample;header.Channels = 2;header.FormatTag = 0x0001;
-        header.SamplesPerSec = 8000;
+        header.SamplesPerSec = samplesPerSec;header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;header.DataHdrLeth = TOTAL_SIZE;
@@ -96,7 +99,8 @@ public class PcmToWav {Log.e("PcmToWav", ioe.getMessage());return false;}
-        clearFiles(filePathList);
+        if (deletePcmFile)
+            clearFiles(filePathList);Log.i("PcmToWav", "mergePCMFilesToWAVFile  success!" + new SimpleDateFormat("yyyy-MM-dd hh:mm").format(new Date()));return true;@@ -110,7 +114,11 @@ public class PcmToWav {* @param deletePcmFile   是否删除源文件* @return*/
-    public static boolean makePCMFileToWAVFile(String pcmPath, String destinationPath, boolean deletePcmFile) {
+    public static boolean makePCMFileToWAVFile(String pcmPath,
+                                               short bitPerSample,
+                                               int samplesPerSec,
+                                               String destinationPath,
+                                               boolean deletePcmFile) {byte buffer[] = null;int TOTAL_SIZE = 0;File file = new File(pcmPath);
@@ -124,10 +132,10 @@ public class PcmToWav {// 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)header.fileLength = TOTAL_SIZE + (44 - 8);header.FmtHdrLeth = 16;
-        header.BitsPerSample = 16;
+        header.BitsPerSample = bitPerSample;header.Channels = 2;header.FormatTag = 0x0001;
-        header.SamplesPerSec = 8000;
+        header.SamplesPerSec = samplesPerSec;header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;header.DataHdrLeth = TOTAL_SIZE;

作者:帅得不敢出门

http://www.xdnf.cn/news/2663.html

相关文章:

  • Spring Boot定时任务
  • 深入浅出JavaScript常见设计模式:从原理到实战(2)
  • 一文读懂Tomcat应用之 CentOS安装部署Tomcat服务
  • rabbitmq-集群部署
  • 当所有人都用上先进ai,如何保持你的优势?
  • 用Postman验证IAM Token的实际操作
  • Error和Exception的区别
  • 第9讲:坐标轴美学深度优化——刻度线、网格线与边框控制
  • mapbox V3 新特性,室内楼层多层同时三维展示(可单层切换),类似蜂鸟视图效果
  • 深度学习任务评估指标
  • 从普查到防控:ArcGIS洪水灾害全流程分析技术实战——十大专题覆盖风险区划/淹没制图/水文分析/洪水分析/淹没分析/项目交流,攻克防洪决策数据瓶颈!
  • QT:自定义ComboBox
  • 自动驾驶领域专业词汇(专业术语)整理
  • leetcode 206. 反转链表
  • 湖北理元理律师事务所:债务管理领域的平台化创新探索
  • 回归预测 | Matlab实现DBO-LightGBM蜣螂算法优化轻量级梯度提升机多输入单输出回归预测,作者:机器学习之心
  • 嵌入式开发面试典型编程题解析:排序算法、指针操作、字符处理、递归原理等基础原理的深度解析。
  • 第33周JavaSpringCloud微服务 分布式综合应用
  • echarts+标签+指引线
  • 【javascript】竞速游戏前端优化:高频操作与并发请求的解决方案
  • 开源模型应用落地-全能音频新纪元-Kimi-Audio-7B-Instruct-重塑多模态交互边界
  • Transformer数学推导——Q29 推导语音识别中流式注意力(Streaming Attention)的延迟约束优化
  • 核心要点:线程
  • 解决MacOS端口被占用问题
  • 升级xcode15 报错Error (Xcode): Cycle inside Runner
  • Visual Studio 技能:调整软件界面布局
  • 区块链vs实体经济:一场金融、医疗、政务与物流的“效率革命”
  • C++——入门基础
  • 人工智能大语言模型与AI芯片新进展:技术演进与商业化路径
  • 防火墙拦截DNS请求-原理解析