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

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;

作者:帅得不敢出门

相关文章:

  • 铭记之日(3)——4.28
  • 【软件工程】需求分析详解
  • maven私服配置
  • 利用Python打印有符号十进制数的二进制原码、反码、补码
  • std::print 和 std::println
  • 万亿参数大模型网络瓶颈突破:突破90%网络利用率的技术实践
  • 【力扣刷题实战】丢失的数字
  • Java大师成长计划之第6天:Java流式API(Stream API)
  • Redis 小记
  • Cursor + Figma-Context-MCP ,让 Cursor 获取 Figma 设计图信息,实现 AI 生成页面的高度还原
  • 【3分钟准备前端面试】Hybrid开发 谷歌浏览器调试安卓app
  • ViTa-Zero:零样本视觉触觉目标 6D 姿态估计
  • 深入解析 Babylon.js 中的 TransformNode.lookAt 方法
  • 【Unity】 Dropdown默认选择不选择任何选项
  • 怎么把Ubuntu系统虚拟环境中启动命令做成系统服务可以后台运行?
  • 【“星瑞” O6 评测】 — llm CPU部署对比高通骁龙CPU
  • Flutter 学习之旅 之 flutter 作为 module ,在 Android 端主动唤起 Flutter 开发的界面 简单的整理
  • DBeaver CE 24.1.3 (Windows 64位) 详细安装教程
  • .net 常用
  • 基于C++实现人工智能—五子棋的目标识别
  • 长三角议事厅·周报|长三角游戏出海,关键在“生态输出”
  • 人到中年为何腰围变粗?科学家发现腹部脂肪增加的细胞元凶
  • 俄罗斯总统普京:5月8日零时至11日零时实施停火
  • 庆祝中华全国总工会成立100周年暨全国劳动模范和先进工作者表彰大会隆重举行,习近平发表重要讲话
  • 体坛联播|利物浦提前4轮夺冠,安切洛蒂已向皇马更衣室告别
  • 我国首个核电工业操作系统发布,将在华龙一号新机组全面应用