电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Android用AudioRecord進行錄音

瀏覽:108日期:2022-09-21 11:21:55

在音視頻開發中,錄音當然是必不可少的。首先我們要學會單獨的錄音功能,當然這里說的錄音是指用AudioRecord來錄音,讀取錄音原始數據,讀到的就是所謂的PCM數據。對于錄音來說,最重要的幾個參數要搞明白:

1、simpleRate采樣率,采樣率就是采樣頻率,每秒鐘記錄多少個樣本。

2、channelConfig通道配置,其實就是所謂的單通道,雙通道之類的,AudioFormat.CHANNEL_IN_MONO單通道,AudioFormat.CHANNEL_IN_STEREO雙通道,這里只列了這兩種,還有其它的,可自行查閱。

3、audioFormat音頻格式,其實就是采樣的精度,每個樣本的位數,AudioFormat.ENCODING_PCM_8BIT每個樣本占8位,AudioFormat.ENCODING_PCM_16BIT每個樣本占16位,這里也只用了這兩個,別的沒研究。

在學習過程中會用到的一些參數,我這里封裝了一個類,如下

public class AudioParams { enum Format { SINGLE_8_BIT, DOUBLE_8_BIT, SINGLE_16_BIT, DOUBLE_16_BIT } private Format format; int simpleRate; AudioParams(int simpleRate, Format f) { this.simpleRate = simpleRate; this.format = f; } AudioParams(int simpleRate, int channelCount, int bits) { this.simpleRate = simpleRate; set(channelCount, bits); } int getBits() { return (format == Format.SINGLE_8_BIT || format == Format.DOUBLE_8_BIT) ? 8 : 16; } int getEncodingFormat() { return (format == Format.SINGLE_8_BIT || format == Format.DOUBLE_8_BIT) ? AudioFormat.ENCODING_PCM_8BIT : AudioFormat.ENCODING_PCM_16BIT; } int getChannelCount() {return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ? 1 : 2;} int getChannelConfig() { return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO; } int getOutChannelConfig() { return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO; } void set(int channelCount, int bits) { if ((channelCount != 1 && channelCount != 2) || (bits != 8 && bits != 16)) { throw new IllegalArgumentException('不支持其它格式 channelCount=$channelCount bits=$bits'); } if (channelCount == 1) { if (bits == 8) {format = Format.SINGLE_8_BIT; } else {format = Format.SINGLE_16_BIT; } } else { if (bits == 8) {format = Format.DOUBLE_8_BIT; } else {format = Format.DOUBLE_16_BIT; } } }}

這里固定使用了單通道8位,雙通道8位,單通道16位,雙通道16位,所以用了枚舉來限制。

為了方便把錄音數據拿出來顯示、存儲,這里寫了一個回調方法如下

public interface RecordCallback { /** * 數據回調 * * @param bytes 數據 * @param len 數據有效長度,-1時表示數據結束 */ void onRecord(byte[] bytes, int len); }

有了這些參數,現在就可以錄音了,先看一下樣例

public void startRecord(AudioParams params, RecordCallback callback) { int simpleRate = params.simpleRate; int channelConfig = params.getChannelConfig(); int audioFormat = params.getEncodingFormat(); // 根據AudioRecord提供的api拿到最小緩存大小 int bufferSize = AudioRecord.getMinBufferSize(simpleRate, channelConfig, audioFormat); //創建Record對象 record = new AudioRecord(MediaRecorder.AudioSource.MIC, simpleRate, channelConfig, audioFormat, bufferSize); recordThread = new Thread(() -> { byte[] buffer = new byte[bufferSize]; record.startRecording(); recording = true; while (recording) {int read = record.read(buffer, 0, bufferSize);// 將數據回調到外部if (read > 0 && callback != null) { callback.onRecord(buffer, read);} } if (callback != null) {// len 為-1時表示結束callback.onRecord(buffer, -1);recording = false; } //釋放資源 release(); }); recordThread.start(); }

這個方法就是簡單的采集音頻數據,這個數據就是最原始的pcm數據。

拿到pcm數據以后,如果直接保存到文件是無法直接播放的,因為這只是一堆數據,沒有任何格式說明,如果想讓普通播放器可以播放,需要在文件中加入文件頭,來告訴播放器這個數據的格式,這里是直接保存成wav格式的數據。下面就是加入wav格式文件頭的方法

private static byte[] getWaveFileHeader(int totalDataLen, int sampleRate, int channelCount, int bits) { byte[] header = new byte[44]; // RIFF/WAVE header header[0] = ’R’; header[1] = ’I’; header[2] = ’F’; header[3] = ’F’; int fileLength = totalDataLen + 36; header[4] = (byte) (fileLength & 0xff); header[5] = (byte) (fileLength >> 8 & 0xff); header[6] = (byte) (fileLength >> 16 & 0xff); header[7] = (byte) (fileLength >> 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; // pcm format = 1 header[20] = 1; header[21] = 0; header[22] = (byte) channelCount; header[23] = 0; header[24] = (byte) (sampleRate & 0xff); header[25] = (byte) (sampleRate >> 8 & 0xff); header[26] = (byte) (sampleRate >> 16 & 0xff); header[27] = (byte) (sampleRate >> 24 & 0xff); int byteRate = sampleRate * bits * channelCount / 8; 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) (channelCount * bits / 8); header[33] = 0; // bits per sample header[34] = (byte) bits; header[35] = 0; //data header[36] = ’d’; header[37] = ’a’; header[38] = ’t’; header[39] = ’a’; header[40] = (byte) (totalDataLen & 0xff); header[41] = (byte) (totalDataLen >> 8 & 0xff); header[42] = (byte) (totalDataLen >> 16 & 0xff); header[43] = (byte) (totalDataLen >> 24 & 0xff); return header; }

根據幾個參數設置一下文件頭,然后直接寫入錄音采集到的pcm數據,就可被正常播放了。wav文件頭格式定義,可點擊這里查看或自行百度。

如果想要通過AudioRecord錄音直接保存到文件,可參考下面方法

public void startRecord(String filePath, AudioParams params, RecordCallback callback) { int channelCount = params.getChannelCount(); int bits = params.getBits(); final boolean storeFile = filePath != null && !filePath.isEmpty(); startRecord(params, (bytes, len) -> { if (storeFile) {if (file == null) { File f = new File(filePath); if (f.exists()) { f.delete(); } try { file = new RandomAccessFile(f, 'rw'); file.write(getWaveFileHeader(0, params.simpleRate, channelCount, bits)); } catch (IOException e) { e.printStackTrace(); }}if (len > 0) { try { file.write(bytes, 0, len); } catch (IOException e) { e.printStackTrace(); }} else { try { // 因為在前面已經寫入頭信息,所以這里要減去頭信息才是數據的長度 int length = (int) file.length() - 44; file.seek(0); file.write(getWaveFileHeader(length, params.simpleRate, channelCount, bits)); file.close(); } catch (IOException e) { e.printStackTrace(); }} } if (callback != null) {callback.onRecord(bytes, len); } }); }

先通過RandomAccessFile創建文件,先寫入文件頭,由于暫時我們不知道會錄多長,有多少pcm數據,長度先用0表示,等錄音結束后,通過seek(int)方法重新寫入文件頭信息,也可以先把pcm數據保存到臨時文件,然后再寫入到一個新的文件中,這里就不舉例說明了。

最后放入完整類的代碼

package cn.sskbskdrin.record.audio;import android.media.AudioRecord;import android.media.MediaRecorder;import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;/** * @author sskbskdrin * @date 2019/April/3 */public class AudioRecordManager { private AudioParams DEFAULT_FORMAT = new AudioParams(8000, 1, 16); private AudioRecord record; private Thread recordThread; private boolean recording = false; private RandomAccessFile file; public void startRecord(String filePath, RecordCallback callback) { startRecord(filePath, DEFAULT_FORMAT, callback); } public void startRecord(String filePath, AudioParams params, RecordCallback callback) { int channelCount = params.getChannelCount(); int bits = params.getBits(); final boolean storeFile = filePath != null && !filePath.isEmpty(); startRecord(params, (bytes, len) -> { if (storeFile) {if (file == null) { File f = new File(filePath); if (f.exists()) { f.delete(); } try { file = new RandomAccessFile(f, 'rw'); file.write(getWaveFileHeader(0, params.simpleRate, channelCount, bits)); } catch (IOException e) { e.printStackTrace(); }}if (len > 0) { try { file.write(bytes, 0, len); } catch (IOException e) { e.printStackTrace(); }} else { try { // 因為在前面已經寫入頭信息,所以這里要減去頭信息才是數據的長度 int length = (int) file.length() - 44; file.seek(0); file.write(getWaveFileHeader(length, params.simpleRate, channelCount, bits)); file.close(); } catch (IOException e) { e.printStackTrace(); }} } if (callback != null) {callback.onRecord(bytes, len); } }); } public void startRecord(AudioParams params, RecordCallback callback) { int simpleRate = params.simpleRate; int channelConfig = params.getChannelConfig(); int audioFormat = params.getEncodingFormat(); // 根據AudioRecord提供的api拿到最小緩存大小 int bufferSize = AudioRecord.getMinBufferSize(simpleRate, channelConfig, audioFormat); //創建Record對象 record = new AudioRecord(MediaRecorder.AudioSource.MIC, simpleRate, channelConfig, audioFormat, bufferSize); recordThread = new Thread(() -> { byte[] buffer = new byte[bufferSize]; record.startRecording(); recording = true; while (recording) {int read = record.read(buffer, 0, bufferSize);// 將數據回調到外部if (read > 0 && callback != null) { callback.onRecord(buffer, read);} } if (callback != null) {// len 為-1時表示結束callback.onRecord(buffer, -1);recording = false; } //釋放資源 release(); }); recordThread.start(); } public void stop() { recording = false; } public void release() { recording = false; if (record != null) { record.stop(); record.release(); } record = null; file = null; recordThread = null; } private static byte[] getWaveFileHeader(int totalDataLen, int sampleRate, int channelCount, int bits) { byte[] header = new byte[44]; // RIFF/WAVE header header[0] = ’R’; header[1] = ’I’; header[2] = ’F’; header[3] = ’F’; int fileLength = totalDataLen + 36; header[4] = (byte) (fileLength & 0xff); header[5] = (byte) (fileLength >> 8 & 0xff); header[6] = (byte) (fileLength >> 16 & 0xff); header[7] = (byte) (fileLength >> 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; // pcm format = 1 header[20] = 1; header[21] = 0; header[22] = (byte) channelCount; header[23] = 0; header[24] = (byte) (sampleRate & 0xff); header[25] = (byte) (sampleRate >> 8 & 0xff); header[26] = (byte) (sampleRate >> 16 & 0xff); header[27] = (byte) (sampleRate >> 24 & 0xff); int byteRate = sampleRate * bits * channelCount / 8; 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) (channelCount * bits / 8); header[33] = 0; // bits per sample header[34] = (byte) bits; header[35] = 0; //data header[36] = ’d’; header[37] = ’a’; header[38] = ’t’; header[39] = ’a’; header[40] = (byte) (totalDataLen & 0xff); header[41] = (byte) (totalDataLen >> 8 & 0xff); header[42] = (byte) (totalDataLen >> 16 & 0xff); header[43] = (byte) (totalDataLen >> 24 & 0xff); return header; } public interface RecordCallback { /** * 數據回調 * * @param bytes 數據 * @param len 數據有效長度,-1時表示數據結束 */ void onRecord(byte[] bytes, int len); }}

如有不對之處還請評論指正

以上就是Android用AudioRecord進行錄音的詳細內容,更多關于Android AudioRecord的資料請關注好吧啦網其它相關文章!

標簽: Android
相關文章:
主站蜘蛛池模板: 艾默生变频器,艾默生ct,变频器,ct驱动器,广州艾默生变频器,供水专用变频器,风机变频器,电梯变频器,艾默生变频器代理-广州市盟雄贸易有限公司官方网站-艾默生变频器应用解决方案服务商 | NM-02立式吸污机_ZHCS-02软轴刷_二合一吸刷软轴刷-厦门地坤科技有限公司 | 丁基胶边来料加工,医用活塞边角料加工,异戊二烯橡胶边来料加工-河北盛唐橡胶制品有限公司 | 周易算网-八字测算网 - 周易算网-宝宝起名取名测名字周易八字测算网 | 实验室装修_实验室设计_实验室规划设计- 上海广建净化工程公司 | 硫酸钡厂家_高光沉淀硫酸钡价格-河南钡丰化工有限公司 | 祝融环境-地源热泵多恒系统高新技术企业,舒适生活环境缔造者! | 四合院设计_四合院装修_四合院会所设计-四合院古建设计与建造中心1 | 成都思迪机电技术研究所-四川成都思迪编码器| 海南在线 海南一家| IP检测-检测您的IP质量| 螺旋绞龙叶片,螺旋输送机厂家,山东螺旋输送机-淄博长江机械制造有限公司 | 海外仓系统|国际货代系统|退货换标系统|WMS仓储系统|海豚云 | 铣床|万能铣床|立式铣床|数控铣床|山东滕州万友机床有限公司 | 消泡剂-水处理消泡剂-涂料消泡剂-切削液消泡剂价格-东莞德丰消泡剂厂家 | 数字展示在线_数字展示行业门户网站 | 乐泰胶水_loctite_乐泰胶_汉高乐泰授权(中国)总代理-鑫华良供应链 | 废水处理-废气处理-工业废水处理-工业废气处理工程-深圳丰绿环保废气处理公司 | 青岛空压机,青岛空压机维修/保养,青岛空压机销售/出租公司,青岛空压机厂家电话 | 不锈钢水箱生产厂家_消防水箱生产厂家-河南联固供水设备有限公司 | 喷漆房_废气处理设备-湖北天地鑫环保设备有限公司 | 防爆大气采样器-防爆粉尘采样器-金属粉尘及其化合物采样器-首页|盐城银河科技有限公司 | 网优资讯-为循环资源、大宗商品、工业服务提供资讯与行情分析的数据服务平台 | 上海皓越真空设备有限公司官网-真空炉-真空热压烧结炉-sps放电等离子烧结炉 | 样品瓶(色谱样品瓶)百科-浙江哈迈科技有限公司 | 科昊仪器超纯水机系统-可成气相液氮罐-美菱超低温冰箱-西安昊兴生物科技有限公司 | 珠海冷却塔降噪维修_冷却塔改造报价_凉水塔风机维修厂家- 广东康明节能空调有限公司 | 立式矫直机_卧式矫直机-无锡金矫机械制造有限公司 | 跨境物流_美国卡派_中大件运输_尾程派送_海外仓一件代发 - 广州环至美供应链平台 | 低温等离子清洗机(双气路进口)-嘉润万丰 | BHK汞灯-百科|上海熙浩实业有限公司 | 雷冲击高压发生器-水内冷直流高压发生器-串联谐振分压器-武汉特高压电力科技有限公司 | 常州减速机_减速机厂家_常州市减速机厂有限公司 | 压力喷雾干燥机,喷雾干燥设备,柱塞隔膜泵-无锡市闻华干燥设备有限公司 | 山东包装,山东印刷厂,济南印刷厂-济南富丽彩印刷有限公司 | 西门子伺服电机维修,西门子电源模块维修,西门子驱动模块维修-上海渠利 | 家用净水器代理批发加盟_净水机招商代理_全屋净水器定制品牌_【劳伦斯官网】 | 香蕉筛|直线|等厚|弧形|振动筛|香蕉筛厂家-洛阳隆中重工 | 耙式干燥机_真空耙式干燥机厂家-无锡鹏茂化工装备有限公司 | 换网器_自动换网器_液压换网器--郑州海科熔体泵有限公司 | 瓶盖扭矩测试仪-瓶盖扭力仪-全自动扭矩仪-济南三泉中石单品站 |