java audiorecord_Android 录音实现(AudioRecord)

90c4071c7768

上一篇文章介绍了使用 MediaRecorder 实现录音功能 Android录音实现(MediaRecorder) ,下面我们继续看看使用 AudioRecord 实现录音功能。

AudioRecord

首先看看Android帮助文档中对该类的简单概述: AndioRecord 类的主要功能是让各种 Java 应用能够管理音频资源,以便它们通过此类能够录制平台的声音输入硬件所收集的声音。此功能的实现就是通过 "pulling 同步"(reading读取)AudioRecord 对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过后面三个类方法中的一个去及时地获取 AudioRecord 对象的录音数据。 AudioRecord 类提供的三个获取声音数据的方法分别是 read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int)。无论选择使用那一个方法都必须事先设定方便用户的声音数据的存储格式。

开始录音的时候,一个 AudioRecord 需要初始化一个相关联的声音buffer,这个 buffer 主要是用来保存新的声音数据。这个 buffer 的大小,我们可以在对象构造期间去指定。它表明一个 AudioRecord 对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化 buffer 容量的数据。

采集工作很简单,我们只需要构造一个AudioRecord对象,然后传入各种不同配置的参数即可。一般情况下录音实现的简单流程如下:

音频源:我们可以使用麦克风作为采集音频的数据源。

采样率:一秒钟对声音数据的采样次数,采样率越高,音质越好。

音频通道:单声道,双声道等,

音频格式:一般选用PCM格式,即原始的音频样本。

缓冲区大小:音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区。(将音频采集到缓冲区中然后再从缓冲区中读取)。

代码实现如下:

import android.media.AudioFormat;

import android.media.AudioRecord;

import android.media.MediaRecorder;

import android.text.TextUtils;

import android.util.Log;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

/**

* 实现录音

*

* @author chenmy0709

* @version V001R001C01B001

*/

public class AudioRecorder {

//音频输入-麦克风

private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;

//采用频率

//44100是目前的标准,但是某些设备仍然支持22050,16000,11025

//采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级

private final static int AUDIO_SAMPLE_RATE = 16000;

//声道 单声道

private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO;

//编码

private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

// 缓冲区字节大小

private int bufferSizeInBytes = 0;

//录音对象

private AudioRecord audioRecord;

//录音状态

private Status status = Status.STATUS_NO_READY;

//文件名

private String fileName;

//录音文件

private List filesName = new ArrayList<>();

/**

* 类级的内部类,也就是静态类的成员式内部类,该内部类的实例与外部类的实例

* 没有绑定关系,而且只有被调用时才会装载,从而实现了延迟加载

*/

private static class AudioRecorderHolder {

/**

* 静态初始化器,由JVM来保证线程安全

*/

private static AudioRecorder instance = new AudioRecorder();

}

private AudioRecorder() {

}

public static AudioRecorder getInstance() {

return AudioRecorderHolder.instance;

}

/**

* 创建录音对象

*/

public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {

// 获得缓冲区字节大小

bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,

channelConfig, channelConfig);

audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);

this.fileName = fileName;

}

/**

* 创建默认的录音对象

*

* @param fileName 文件名

*/

public void createDefaultAudio(String fileName) {

// 获得缓冲区字节大小

bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,

AUDIO_CHANNEL, AUDIO_ENCODING);

audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);

this.fileName = fileName;

status = Status.STATUS_READY;

}

/**

* 开始录音

*

* @param listener 音频流的监听

*/

public void startRecord(final RecordStreamListener listener) {

if (status == Status.STATUS_NO_READY || TextUtils.isEmpty(fileName)) {

throw new IllegalStateException("录音尚未初始化,请检查是否禁止了录音权限~");

}

if (status == Status.STATUS_START) {

throw new IllegalStateException("正在录音");

}

Log.d("AudioRecorder", "===startRecord===" + audioRecord.getState());

audioRecord.startRecording();

new Thread(new Runnable() {

@Override

public void run() {

writeDataTOFile(listener);

}

}).start();

}

/**

* 暂停录音

*/

public void pauseRecord() {

Log.d("AudioRecorder", "===pauseRecord===");

if (status != Status.STATUS_START) {

throw new IllegalStateException("没有在录音");

} else {

audioRecord.stop();

status = Status.STATUS_PAUSE;

}

}

/**

* 停止录音

*/

public void stopRecord() {

Log.d("AudioRecorder", "===stopRecord===");

if (status == Status.STATUS_NO_READY || status == Status.STATUS_READY) {

throw new IllegalStateException("录音尚未开始");

} else {

audioRecord.stop();

status = Status.STATUS_STOP;

release();

}

}

/**

* 释放资源

*/

public void release() {

Log.d("AudioRecorder", "===release===");

//假如有暂停录音

try {

if (filesName.size() > 0) {

List filePaths = new ArrayList<>();

for (String fileName : filesName) {

filePaths.add(FileUtil.getPcmFileAbsolutePath(fileName));

}

//清除

filesName.clear();

//将多个pcm文件转化为wav文件

mergePCMFilesToWAVFile(filePaths);

} else {

//这里由于只要录音过filesName.size都会大于0,没录音时fileName为null

//会报空指针 NullPointerException

// 将单个pcm文件转化为wav文件

//Log.d("AudioRecorder", "=====makePCMFileToWAVFile======");

//makePCMFileToWAVFile();

}

} catch (IllegalStateException e) {

throw new IllegalStateException(e.getMessage());

}

if (audioRecord != null) {

audioRecord.release();

audioRecord = null;

}

status = Status.STATUS_NO_READY;

}

/**

* 取消录音

*/

public void canel() {

filesName.clear();

fileName = null;

if (audioRecord != null) {

audioRecord.release();

audioRecord = null;

}

status = Status.STATUS_NO_READY;

}

/**

* 将音频信息写入文件

*

* @param listener 音频流的监听

*/

private void writeDataTOFile(RecordStreamListener listener) {

// new一个byte数组用来存一些字节数据,大小为缓冲区大小

byte[] audiodata = new byte[bufferSizeInBytes];

FileOutputStream fos = null;

int readsize = 0;

try {

String currentFileName = fileName;

if (status == Status.STATUS_PAUSE) {

//假如是暂停录音 将文件名后面加个数字,防止重名文件内容被覆盖

currentFileName += filesName.size();

}

filesName.add(currentFileName);

File file = new File(FileUtil.getPcmFileAbsolutePath(currentFileName));

if (file.exists()) {

file.delete();

}

fos = new FileOutputStream(file);// 建立一个可存取字节的文件

} catch (IllegalStateException e) {

Log.e("AudioRecorder", e.getMessage());

throw new IllegalStateException(e.getMessage());

} catch (FileNotFoundException e) {

Log.e("AudioRecorder", e.getMessage());

}

//将录音状态设置成正在录音状态

status = Status.STATUS_START;

while (status == Status.STATUS_START) {

readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);

if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {

try {

fos.write(audiodata);

if (listener != null) {

//用于拓展业务

listener.recordOfByte(audiodata, 0, audiodata.length);

}

} catch (IOException e) {

Log.e("AudioRecorder", e.getMessage());

}

}

}

try {

if (fos != null) {

fos.close();// 关闭写入流

}

} catch (IOException e) {

Log.e("AudioRecorder", e.getMessage());

}

}

/**

* 将pcm合并成wav

*

* @param filePaths

*/

private void mergePCMFilesToWAVFile(final List filePaths) {

new Thread(new Runnable() {

@Override

public void run() {

if (PcmToWav.mergePCMFilesToWAVFile(filePaths, FileUtil.getWavFileAbsolutePath(fileName))) {

//操作成功

} else {

//操作失败

Log.e("AudioRecorder", "mergePCMFilesToWAVFile fail");

throw new IllegalStateException("mergePCMFilesToWAVFile fail");

}

fileName = null;

}

}).start();

}

/**

* 将单个pcm文件转化为wav文件

*/

private void makePCMFileToWAVFile() {

new Thread(new Runnable() {

@Override

public void run() {

if (PcmToWav.makePCMFileToWAVFile(FileUtil.getPcmFileAbsolutePath(fileName), FileUtil.getWavFileAbsolutePath(fileName), true)) {

//操作成功

} else {

//操作失败

Log.e("AudioRecorder", "makePCMFileToWAVFile fail");

throw new IllegalStateException("makePCMFileToWAVFile fail");

}

fileName = null;

}

}).start();

}

/**

* 获取录音对象的状态

*

* @return

*/

public Status getStatus() {

return status;

}

/**

* 获取本次录音文件的个数

*

* @return

*/

public int getPcmFilesCount() {

return filesName.size();

}

/**

* 录音对象的状态

*/

public enum Status {

//未开始

STATUS_NO_READY,

//预备

STATUS_READY,

//录音

STATUS_START,

//暂停

STATUS_PAUSE,

//停止

STATUS_STOP

}

}

AudioRecorder 录音声音数据从音频硬件中被读出,编码格式为 PCM格式,但 PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩。下面实现 PCM 语音数据转为 WAV 文件。

/**

* 将一个pcm文件转化为wav文件

* @param pcmPath pcm文件路径

* @param destinationPath 目标文件路径(wav)

* @param deletePcmFile 是否删除源文件

* @return

*/

public static boolean makePCMFileToWAVFile(String pcmPath, String destinationPath, boolean deletePcmFile) {

byte buffer[] = null;

int TOTAL_SIZE = 0;

File file = new File(pcmPath);

if (!file.exists()) {

return false;

}

TOTAL_SIZE = (int) file.length();

// 填入参数,比特率等等。这里用的是16位单声道 8000 hz

WaveHeader header = new WaveHeader();

// 长度字段 = 内容的大小(TOTAL_SIZE) +

// 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)

header.fileLength = TOTAL_SIZE + (44 - 8);

header.FmtHdrLeth = 16;

header.BitsPerSample = 16;

header.Channels = 2;

header.FormatTag = 0x0001;

header.SamplesPerSec = 8000;

header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);

header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;

header.DataHdrLeth = TOTAL_SIZE;

byte[] h = null;

try {

h = header.getHeader();

} catch (IOException e1) {

Log.e("PcmToWav", e1.getMessage());

return false;

}

if (h.length != 44) // WAV标准,头部应该是44字节,如果不是44个字节则不进行转换文件

return false;

// 先删除目标文件

File destfile = new File(destinationPath);

if (destfile.exists())

destfile.delete();

// 合成的pcm文件的数据,写到目标文件

try {

buffer = new byte[1024 * 4]; // Length of All Files, Total Size

InputStream inStream = null;

OutputStream ouStream = null;

ouStream = new BufferedOutputStream(new FileOutputStream(

destinationPath));

ouStream.write(h, 0, h.length);

inStream = new BufferedInputStream(new FileInputStream(file));

int size = inStream.read(buffer);

while (size != -1) {

ouStream.write(buffer);

size = inStream.read(buffer);

}

inStream.close();

ouStream.close();

} catch (FileNotFoundException e) {

Log.e("PcmToWav", e.getMessage());

return false;

} catch (IOException ioe) {

Log.e("PcmToWav", ioe.getMessage());

return false;

}

if (deletePcmFile) {

file.delete();

}

Log.i("PcmToWav", "makePCMFileToWAVFile success!" + new SimpleDateFormat("yyyy-MM-dd hh:mm").format(new Date()));

return true;

}

总结:AudioRecorder 录音相比较 MediaRecorder 使用起来会麻烦一些,但优点也是显而易见的,AudioRecorder 录音时直接操纵硬件获取音频流数据,该过程是实时处理,可以用代码实现各种音频的封装,同时也可实现暂停功能,关于实现暂停录音功能今天在这里就不赘述了,推荐大家阅读 imhxl 博主的分享 http://blog.csdn.net/imhxl/article/details/52190451 。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《架构探险——从零开始写Java Web框架》这书不错,能看懂的入门书

这书适合我。 哈哈&#xff0c;结合 以前的知识点&#xff0c;勉强能看懂。 讲得细&#xff0c;还可以参照着弄出来。 希望能坚持 完成啦。。。 原来&#xff0c;JSTL就类似于DJANGO中的模板。 而servlet类中的res,req&#xff0c;玩了DJANGO就觉得好熟悉啦。。。&#xff1a;&…

java 生成 tar.gz_一文教您如何通过 Java 压缩文件,打包一个 tar.gz Filebeat 采集器包...

一、背景最近&#xff0c;小哈主要在负责日志中台的开发工作, 等等&#xff0c;啥是日志中台&#xff1f;俺只知道中台概念&#xff0c;这段时间的确很火&#xff0c;但是日志中台又是用来干啥的&#xff1f;这里小哈尽量地通俗的说下日志中台的职责&#xff0c;再说日志中台之…

poj1741 Tree 点分治

入门题&#xff0c;算是对树分治有了初步的理解吧。 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<vector> #define REP(i,a,b) for(int ia;i<b;i) #define MS0(a) memset(…

深入理解 ajax_xhr 对象

2019独角兽企业重金招聘Python工程师标准>>> ajax技术的核心是XMLHttpRequest对象(简称XHR)&#xff0c;这是由微软首先引入的一个特性&#xff0c;其他浏览器提供商后来都提供了相同的实现。 IE5是第一款引入XHR对象的浏览器。在IE5中&#xff0c;XHR对象是通过MSX…

组函数及分组统计

分组函数 SQL中经常使用的分组函数 Count(): 计数 Max()&#xff1a;求最大值 Min()&#xff1a;求最小值 Avg()&#xff1a;求平均值 Sum()&#xff1a;求和 -- 统计emp表中的人数 select count(*) from emp; -- 统计获得奖金的人数 select count(comm) from emp;-- 求全部雇…

两张神图介绍python3和 2.x与 3.x 的区别

有感与第一张图, 做了第二张图.转载于:https://www.cnblogs.com/Vito2008/p/5280393.html

Factorial Trailing Zeroes

https://leetcode.com/problems/factorial-trailing-zeroes/ Given an integer n, return the number of trailing zeroes in n!. Note: Your solution should be in logarithmic time complexity. 解题思路&#xff1a; 再次遇见最讨厌的Math题。 开始的思路&#xff0c;结尾的…

百年难得一见!阿里园区惊现双月争辉奇观!

9月3日晚杭州阿里园区上空突然惊现“双月争辉”奇观&#xff0c;引发路人、员工争相拍照留念狂潮。记者随后深入园区探访&#xff0c;近距离观察“双月奇观”。当晚&#xff0c;热心观众王先生提供线索。王先生路过杭州阿里巴巴园区时&#xff0c;听到有人呼喊&#xff1a;“快…

Spring 事务配置5种方式

Spring配置文件中关于事务配置总是由三个组成部分&#xff0c;分别是DataSource、TransactionManager和代理机制这三部分&#xff0c;无论哪种配置方式&#xff0c;一般变化的只是代理机制这部分。 DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化&…

黑客宣称掌握了600多万个Instagram账号的信息

据外媒报道&#xff0c;上周早些时候&#xff0c;歌手兼演员赛琳娜戈麦斯因Instagram账号被盗而发出大量来自前男友贾斯汀比伯的裸照。不过当时很快赛琳娜就拿回了对账号的控制权并删掉了这些裸照。就在大家以为这件事情已经平息的时候&#xff0c;Instagram却被曝光了一个极为…

奇怪吸引子---Aizawa

奇怪吸引子是混沌学的重要组成理论&#xff0c;用于演化过程的终极状态&#xff0c;具有如下特征&#xff1a;终极性、稳定性、吸引性。吸引子是一个数学概念&#xff0c;描写运动的收敛类型。它是指这样的一个集合&#xff0c;当时间趋于无穷大时&#xff0c;在任何一个有界集…

安装SQL2012出现[HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD)设置为 1

本人安装SQL2012出现这个错误&#xff0c;找了三天三夜&#xff0c;终于把问题找出来&#xff0c;共享给有需要的人们&#xff0c;不用重新换系统 错误如下: 1&#xff0c;此问题是系统.net Framework版本冲突&#xff0c;首先下载.net Framework清理工具&#xff08;如:cleanu…

java限制发送短信次数_使用java发送短信验证码码,出现流量限制怎么办?急急急...

注册登录后需要企业认证,直接在某度上找一张清晰有红章的企业营业执照,注意要细心点,要看看有没有水印。我第一次就没注意上传了一张有水印的营业执照&#xff0c;从此这个账号再也没有审核通过了&#xff0c;后面只能换个账号。都是后台人工审核的&#xff0c;比较严格。如果时…

GDKOI2015 Day2

P1 题目描述&#xff1a; 给出一个二分图&#xff0c;选择互不相交的边&#xff0c;使得边覆盖的点权和最大。 solution&#xff1a; 简单DP&#xff0c;用树状数组维护最大值。 时间复杂度&#xff1a;$O(n \log n) $ P2 题目描述&#xff1a; 给出N个或黑或白的元素&#xff…

XMind入门教程

最近在总结一些框架知识的时候&#xff0c;总找不到一款好的软件来画流程图&#xff0c;后来在网上查找这方面的东西&#xff0c;找到了 XMind,发现用来画思维导图还挺好的&#xff0c;看起来思路清晰&#xff0c;美观。那么便将使用的一些经验分享给大家。 1、什么是思维导图&…

java word转图片tiff_不怕复制内容 Word转存TIFF文件这么玩

辛辛苦苦把Word文件敲好&#xff0c;为了不让别人复制走内容&#xff0c;只能看文稿&#xff0c;有些人就选择转存成PDF文件——但是PDF文件依然可以被编辑&#xff0c;还有什么方法能防范呢&#xff1f;其实在Word 2003之前&#xff0c;用户可以通过Microsoft Office Document…

IDC:聚焦6+6,抓住数字化转型商机

今天&#xff0c;IDC中国2015年中国ICT市场趋势论坛巡回系列的第二站在北京举行。论坛的主题为“加速创新实现数字化转型”。 这是最坏的时代&#xff1a;经济增长乏力、实体经济不振、传统行业在被颠覆与重构、IT市场总体增长进入个位数区间、IT第二平台的领导厂商仍在困境中。…

IBM收购以色列应用发现公司EZSource

6月1日晚消息&#xff0c;IBM宣布对以色列公司EZSource进行收购&#xff0c;交易的具体条款没有被披露。 EZSource成立于2003年&#xff0c;以自有视觉面板产品闻名&#xff0c;该公司的产品能够帮助开发人员将重要的大型机应用程序现代化。该公司在以色列、英国、美国、瑞士、…

hadoop之 Hadoop2.2.0中HDFS的高可用性实现原理

在Hadoop2.0.0之前&#xff0c;NameNode(NN)在HDFS集群中存在单点故障&#xff08;single point of failure&#xff09;&#xff0c;每一个集群中存在一个NameNode&#xff0c;如果NN所在的机器出现了故障&#xff0c;那么将导致整个集群无法利用&#xff0c;直到NN重启或者在…

3D坦克大战游戏源码

3D坦克大战游戏源码&#xff0c;该游戏是基于xcode 4.3&#xff0c;ios sdk 5.1开发。在xcode4.3.3上完美无报错。兼容ios4.3-ios6.0 &#xff0c;一款ios平台上难得的3D坦克大战游戏源码&#xff0c;有20张不同的作战地图。通过左下角方向键和重力感应来控制坦克运行&#xff…