技术标签: Android
前言
Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。
实现流程
获取权限
初始化获取每一帧流的Size
初始化音频录制AudioRecord
开始录制与保存录制音频文件
停止录制
给音频文件添加头部信息,并且转换格式成wav
释放AudioRecord,录制流程完毕
获取权限
<!--音频录制权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--读取和写入存储权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
如果是Android5.0以上,以上3个权限需要动态授权
初始化获取每一帧流的Size
private Integer mRecordBufferSize;
private void initMinBufferSize(){
//获取每一帧的字节流大小
mRecordBufferSize = AudioRecord.getMinBufferSize(8000
, AudioFormat.CHANNEL_IN_MONO
, AudioFormat.ENCODING_PCM_16BIT);
}
第一个参数sampleRateInHz 采样率(赫兹),方法注释里有说明
只能在4000到192000的范围内取值
在AudioFormat类里
public static final int SAMPLE_RATE_HZ_MIN = 4000; 最小4000
public static final int SAMPLE_RATE_HZ_MAX = 192000; 最大192000
第二个参数channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。
在AudioFormat类录
public static final int CHANNEL_IN_LEFT = 0x4;//左声道
public static final int CHANNEL_IN_RIGHT = 0x8;//右声道
public static final int CHANNEL_IN_FRONT = 0x10;//前声道
public static final int CHANNEL_IN_BACK = 0x20;//后声道
public static final int CHANNEL_IN_LEFT_PROCESSED = 0x40;
public static final int CHANNEL_IN_RIGHT_PROCESSED = 0x80;
public static final int CHANNEL_IN_FRONT_PROCESSED = 0x100;
public static final int CHANNEL_IN_BACK_PROCESSED = 0x200;
public static final int CHANNEL_IN_PRESSURE = 0x400;
public static final int CHANNEL_IN_X_AXIS = 0x800;
public static final int CHANNEL_IN_Y_AXIS = 0x1000;
public static final int CHANNEL_IN_Z_AXIS = 0x2000;
public static final int CHANNEL_IN_VOICE_UPLINK = 0x4000;
public static final int CHANNEL_IN_VOICE_DNLINK = 0x8000;
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;//单声道
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);//立体声道(左右声道)
第三个参数audioFormat 音频格式 表示音频数据的格式。
注意!一般的手机设备可能只支持 16位PCM编码,如果其他的都会报错为坏值.
public static final int ENCODING_PCM_16BIT = 2; //16位PCM编码
public static final int ENCODING_PCM_8BIT = 3; //8位PCM编码
public static final int ENCODING_PCM_FLOAT = 4; //4位PCM编码
public static final int ENCODING_AC3 = 5;
public static final int ENCODING_E_AC3 = 6;
public static final int ENCODING_DTS = 7;
public static final int ENCODING_DTS_HD = 8;
public static final int ENCODING_MP3 = 9; //MP3编码 此格式可能会因为不设备不支持报错
public static final int ENCODING_AAC_LC = 10;
public static final int ENCODING_AAC_HE_V1 = 11;
public static final int ENCODING_AAC_HE_V2 = 12;
初始化音频录制AudioRecord
private AudioRecord mAudioRecord;
private void initAudioRecord(){
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC
, 8000
, AudioFormat.CHANNEL_IN_MONO
, AudioFormat.ENCODING_PCM_16BIT
, mRecordBufferSize);
}
第一个参数audioSource 音频源 这里选择使用麦克风:MediaRecorder.AudioSource.MIC
第二个参数sampleRateInHz 采样率(赫兹) 与前面初始化获取每一帧流的Size保持一致
第三个参数channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。 与前面初始化获取每一帧流的Size保持一致
第四个参数audioFormat 音频格式 表示音频数据的格式。 与前面初始化获取每一帧流的Size保持一致
第五个参数缓存区大小,就是上面我们配置的AudioRecord.getMinBufferSize
开始录制与保存录制音频文件
private boolean mWhetherRecord;
private File pcmFile;
private void startRecord(){
pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
mWhetherRecord = true;
new Thread(new Runnable() {
@Override
public void run() {
mAudioRecord.startRecording();//开始录制
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(pcmFile);
byte[] bytes = new byte[mRecordBufferSize];
while (mWhetherRecord){
mAudioRecord.read(bytes, 0, bytes.length);//读取流
fileOutputStream.write(bytes);
fileOutputStream.flush();
}
Log.e(TAG, "run: 暂停录制" );
mAudioRecord.stop();//停止录制
fileOutputStream.flush();
fileOutputStream.close();
addHeadData();//添加音频头部信息并且转成wav格式
} catch (FileNotFoundException e) {
e.printStackTrace();
mAudioRecord.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
这里说明一下为什么用布尔值,来关闭录制.有些小伙伴会发现AudioRecord是可以获取到录制状态的.那么肯定有人会用状态来判断while是否还需要处理流.这种是错误的做法.因为MIC属于硬件层任何硬件的东西都是异步的而且会有很大的延时.所以回调的状态也是有延时的,有时候流没了,但是状态还是显示为正在录制.
停止录制
就是调用mAudioRecord.stop();方法来停止录制,但是因为我在上面的保存流后做了调用停止视频录制,所以我这里只需要切换布尔值就可以关闭音频录制
private void stopRecord(){
mWhetherRecord = false;
}
给音频文件添加头部信息,并且转换格式成wav
音频录制完成后,这个时候去存储目录找到音频文件部分,会提示无法播放文件.其实是因为没有加入音频头部信息.一般通过麦克风采集的录音数据都是PCM格式的,即不包含头部信息,播放器无法知道音频采样率、位宽等参数,导致无法播放,显然是非常不方便的。pcm转换成wav,我们只需要在pcm的文件起始位置加上至少44个字节的WAV头信息即可。
偏移地址 命名 内容
00-03 ChunkId “RIFF”
04-07 ChunkSize 下个地址开始到文件尾的总字节数(此Chunk的数据大小)
08-11 fccType “WAVE”
12-15 SubChunkId1 "fmt ",最后一位空格。
16-19 SubChunkSize1 一般为16,表示fmt Chunk的数据块大小为16字节
20-21 FormatTag 1:表示是PCM 编码
22-23 Channels 声道数,单声道为1,双声道为2
24-27 SamplesPerSec 采样率
28-31 BytesPerSec 码率 :采样率 * 采样位数 * 声道个数,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels
32-33 BlockAlign 每次采样的大小:位宽*声道数/8
34-35 BitsPerSample 位宽
36-39 SubChunkId2 “data”
40-43 SubChunkSize2 音频数据的长度
44-… data 音频数据
private void addHeadData(){
pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
handlerWavFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord_handler.wav");
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);
pcmToWavUtil.pcmToWav(pcmFile.toString(),handlerWavFile.toString());
}
写入头部信息的工具类
注意输入File和输出File不能同一个,因为没有做缓存.
public class PcmToWavUtil {
private static final String TAG = "PcmToWavUtil";
/**
* 缓存的音频大小
*/
private int mBufferSize;
/**
* 采样率
*/
private int mSampleRate;
/**
* 声道数
*/
private int mChannel;
/**
* @param sampleRate sample rate、采样率
* @param channel channel、声道
* @param encoding Audio data format、音频格式
*/
PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
}
/**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;//总录音长度
long totalDataLen;//总数据长度
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
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;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
out.flush();
}
Log.e(TAG, "pcmToWav: 停止处理");
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];
// RIFF/WAVE header
header[0] = 'R';
header[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);
//WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[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);
// block align
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
// bits per sample
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
header[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);
}
}
释放AudioRecord,录制流程完毕
调用release()方法释放资源
mAudioRecord.release();
最后你就可以在指定目录下找到音频文件播放了
最后介绍下其他API
获取AudioRecord初始化状态
public int getState() {
return mState;
}
注意!这里是初始化状态,不是录制状态,它只会返回2个状态
AudioRecord#STATE_INITIALIZED //已经初始化
AudioRecord#STATE_UNINITIALIZED //没有初始化
获取AudioRecord录制状态
public int getRecordingState() {
synchronized (mRecordingStateLock) {
return mRecordingState;
}
}
返回录制状态,它只返回2个状态
AudioRecord#RECORDSTATE_STOPPED //停止录制
AudioRecord#RECORDSTATE_RECORDING //正在录制
1、 SpringBoot内外部配置文件加载和优先级!前言:Spring这么流星很重要的一个思想是:配置化思想。从而达到解耦的目的,很多都不需要硬编码,配置一下即可完成,可谓大大提高了编码的效率。Spring支持的配置文件加载的方式繁多,一方面是扩大了灵活性,另一方面其实也带来了较大的复杂性。因此本文旨在用一篇文章解决读者朋友的烦恼,同时有时候也是你我的烦恼。我们知道SpringBoot支持三种配置文件:application*.ymlapplication*.yamlapplication*
参考文章:http://bbs.eeworld.com.cn/thread-478103-1-1.html28335的启动过程是指 上电后或者芯片复位后,芯片的内部运行过程,直到引导程序进入main函数。之前看过ARm芯片的引导过程,但是没看懂。所以通过学习对DSP的启动过程有一个初步的了解。在网上找到一篇《学习TI的28335启动过程以及代码搬运》思路清晰,简单易懂。在这里只是重述一篇启动过程。...
IDEA中的Springboot配置文件不被使用,配置显示灰色1.启动Springboot项目,出现‘url’ attribute is not specified and no embedded datasource could be configured.具体错误信息详见下图所示:配置显示灰色,不能获取到配置连接。2.原因是resources没有没有被标志为Resources Root,标记的具体步骤为:选中reources文件右键-->Mark Directory as-->Reso
1 概要WebClient webClient = new WebClient();Stream stream = webClient.OpenRead("http://www.baidu.com");StreamReader streamReader = new StreamReader(stream);2 代码using System;using System.Collections.Generic;using System.Linq;using System.Text;..
DSP_F28335中断系统学习
Where_is_my_FUMO开局是给了一个反弹shell的命令执行<?phpfunction chijou_kega_no_junnka($str) { $black_list = [">", ";", "|", "{", "}", "/", " "]; return str_replace($black_list, "", $str);}if (isset($_GET['DATA'])) { $data = $_GET['DATA']; $add
TI的DSP为了提高安全性能,将很多关键寄存器作了保护处理。通过状态寄存器1(ST1)的位6设置与复位,来决定是否允许DSP指令对关键寄存器进行操作。这些关键寄存器包括:器件仿真寄存器、FLASH寄存器、CSM寄存器、PIE矢量表、系统控制寄存器、GPIOMux寄存器、eCAN寄存器的一部分。DSP由于在上电复位之后,状态寄存器基本上都是清零,而这样的状态下正是上述特殊寄存器禁止改写的
最近在做一个项目中需要将系统中操作的SQL自动化维护到远程服务器上,git是一个非常强大的代码维护工具,遂对其自动化push做了一些研究,供大家学习参考。 java git api - jgit的资料还是比较多的,而且git的操作比svn更容易理解,所以毅然决然的在git的道路上越走越远。 如果你想在一个 Java 程序中使用 Git ,有一个功能齐全的 Git 库...
#2020云栖大会#阿里云海量offer来啦!投简历、赢阿里云限量礼品及阿里云ACA认证免费考试资格!>>> ...
今天在计算顶点法向量的时候法线,不论怎么计算总有黑色的面存在,后面才发现是与顶点的顺序有关,查找了一些资料后发现利用鞋带公式计算多边形的面积可以很方便的判断点集的顺序。具体的鞋带公式可以查看维基百科上的介绍:https://en.wikipedia.org/wiki/Shoelace_formula该公式为A是多边形的面数,去掉绝对值,得到A数值大于0,则点集顺序为逆时针;反之小于0,点集为顺时针。代码:double getSort(){ ptsOriginal->pus
简介Git 是什么?Git 是一个开源的分布式版本控制系统。什么是版本控制?版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。什么是分布式版本控制系统?介绍分布式版本控制系统前,有必要先了解一下传统的集中式版本控制系统。集中化的版本控制系统,诸如 CVS,Subversion 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的...
本题要求编写程序将一个百分制成绩转换为五分制成绩。转换规则:大于等于90分为A;小于90且大于等于80为B;小于80且大于等于70为C;小于70且大于等于60为D;小于60为E。输入格式:输入在一行中给出一个整数的百分制成绩。输出格式:在一行中输出对应的五分制成绩。输入样例:90输出样例:A#include <stdio.h>int main(){ int score; scanf("%d", &score); if (sc