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.pswp.cn/news/257550.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/257550.shtml
英文地址,請注明出處:http://en.pswp.cn/news/257550.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SqlServer中的數據類型UniqueIdentifier

SqlServer中的數據類型UniqueIdentifier究竟是什么東東&#xff1f;該類型一般用來做為主鍵使用&#xff0c;可用SQL語法的newid()來生成一個唯一的值。我想請問的是&#xff0c;這個值是一個長整型的數據值呢&#xff0c;還是個其他的什么值&#xff1f;我在程序中該怎樣去控制…

《架構探險——從零開始寫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;再說日志中臺之…

腳本安裝smokeping

我將提供兩種方法來安裝smokeping&#xff0c;一種是大家常用的普通安裝&#xff0c;另一種是用腳本下自動化安裝的&#xff0c;僅供大家學習&#xff0c;參考!普通安裝&#xff1a;centos 5.4下安裝smokeping需要的軟件:(1)httpd(2)rrdtool(3)smokeping(4)fping(5)libwww-perl…

強烈推薦:Android史上最強大的自定義任務軟件Tasker

強烈推薦&#xff1a;Android史上最強大的自定義任務軟件Taskerhttp://bbs.mumayi.com/thread-28387-1-1.html(出處: 木螞蟻手機樂園) Android上的Tasker絕對稱得上是Android系統的神器之一&#xff0c;與Auto Memory Manager不同&#xff0c;Tasker不是加速型的軟件&#xff0…

配置文件*.xml中 classpath: 與 classpath*: 的區別

首先classpath 指的是WEB-INF下面的classes目錄&#xff0c;所有src目錄下面的java、xml、properties等文件編譯后都會在此,classes在eclipse的項目目錄下是看不到的&#xff0c;它存在于部署在服務器上的項目目錄WEB-INF下 classpath:指的是第一個classpath路徑&#xff0c;也…

原型模式 java 深淺_JAVA設計模式---原型模式--淺客隆和深克隆

JAVA淺克隆和深克隆淺克隆&#xff1a;被復制對象的所有變量和原來相同&#xff0c;而所有的對其他對象的引用仍指向原對象。即如果復制的對象修改復制對象的變量&#xff0c;原對象不會改變。而修改引用的對象&#xff0c;二者均會發生改變。深復制(克隆)&#xff1a;被復制對…

SocketErrorCode:10022

在編寫.net的網絡服務器時&#xff0c;我使用了裸socket來實現。在windows上&#xff0c;或者在linux上通過.net core來跑時都沒有什么問題&#xff0c;但是通過mono運行調用socket.Bind()時卻總是報ErrorCode為10022的SocketException&#xff0c;表示參數無效。通過命令netst…

request.RequestContextListener

由于是使用spring mvc來做項目&#xff0c;因此脫離了HttpServletRequest作為參數&#xff0c;不能夠直接使用request&#xff0c;要想使用request可以使用下面的方法&#xff1a; 在web點xml中配置一個監聽 [html] view plaincopyprint?<listener> <listen…

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…

POJ 1584 A Round Peg in a Ground Hole(點到直線距離,圓與多邊形相交,多邊形是否為凸)...

題意&#xff1a;給出一個多邊形和一個圓&#xff0c;問是否是凸多邊形&#xff0c;若是則再問圓是否在凸多邊形內部。 分3步&#xff1a; 1、判斷是否是凸多邊形 2、判斷點是否在多邊形內部 3、判斷點到各邊的距離是否大于等于半徑 上代碼&#xff1a; #include <iostream&…

組函數及分組統計

分組函數 SQL中經常使用的分組函數 Count(): 計數 Max()&#xff1a;求最大值 Min()&#xff1a;求最小值 Avg()&#xff1a;求平均值 Sum()&#xff1a;求和 -- 統計emp表中的人數 select count(*) from emp; -- 統計獲得獎金的人數 select count(comm) from emp;-- 求全部雇…

java數據生成excel_Java 數據庫數據生成Excel

采用jxl.jar生成Excel項目開發注意事項&#xff1a; 1:導入從網上下載的jar包&#xff1a;mail.jar 和 activation.jar2:刪掉C:\Program Files\MyEclipse\Common\plugins\com.genuitec.eclipse.j2eedt.core_10.0.0.me201110301321\data\libraryset\EE_5 下 javaee.jar中的java…

兩張神圖介紹python3和 2.x與 3.x 的區別

有感與第一張圖, 做了第二張圖.轉載于:https://www.cnblogs.com/Vito2008/p/5280393.html

Java-jdbc連接數據庫

1、Oracle8/8i/9i數據庫&#xff08;thin模式&#xff09; Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); String url"jdbc:oracle:thin:localhost:1521:orcl"; //orcl為數據庫的SID String user"test"; String…

abstract class 和 interface 區別

本文出自與&#xff1a;heipai:tsg666含有 abstract 修飾符的 class 即為抽象類&#xff0c;abstract 類不能創建的實例對象。含有 abstract 方法的類必須定義為 abstract class&#xff0c;abstract class 類中的方法不必是抽象的。abstract class 類中定義抽象方法必須在具體…

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;結尾的…

java設計模式懶漢_java設計模式-懶漢設計模式

一、理論類加載時&#xff0c;不進行實例化&#xff0c;調用時才進行類的實例化。二、代碼實現public class LazyManPattern {//1.構造方法私有化private LazyManPattern(){}//2.類加載時&#xff0c;不進行實例化private static LazyManPattern lazyManPattern;//3.創建實例化…

多視圖參數傳遞

在iOS開發中常用的參數傳遞有以下幾種方法&#xff1a; 采用代理模式 采用iOS消息機制 通過NSDefault存儲&#xff08;或者文件、數據庫存儲等&#xff09; 通過AppDelegate定義全局變量&#xff08;或者使用UIApplication、定義一個單例類等&#xff09; 通過控制器屬性傳遞轉…