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;
作者:帅得不敢出门