Android音頻處理之通過(guò)AudioRecord去保存PCM文件進(jìn)行錄制,播放,停止,刪除功能
音頻這方面很博大精深,我這里肯定講不了什么高級(jí)的東西,最多也只是一些基礎(chǔ)類(lèi)知識(shí),首先,我們要介紹一下Android他提供的錄音類(lèi),實(shí)際上他有兩個(gè),一個(gè)是MediaRecorder,還有一個(gè)就是我們今天要用到的AudioRecord,那他們有什么區(qū)別呢?
一.區(qū)別
MediaRecorder和AudioRecord都可以錄制音頻,區(qū)別是MediaRecorder錄制的音頻文件是經(jīng)過(guò)壓縮后的,需要設(shè)置編碼器。并且錄制的音頻文件可以用系統(tǒng)自帶的Music播放器播放。
而AudioRecord錄制的是PCM格式的音頻文件,需要用AudioTrack來(lái)播放,AudioTrack更接近底層。
PCM可能更加可以理解為音頻的源文件
二.優(yōu)缺點(diǎn)
AudioRecord
主要是實(shí)現(xiàn)邊錄邊播以及對(duì)音頻的實(shí)時(shí)處理,這個(gè)特性讓他更適合在語(yǔ)音方面有優(yōu)勢(shì)
優(yōu)點(diǎn):語(yǔ)音的實(shí)時(shí)處理,可以用代碼實(shí)現(xiàn)各種音頻的封裝
缺點(diǎn):輸出是PCM格式文件,如果保存成音頻文件,是不能夠被播放器播放的,所以必須先寫(xiě)代碼實(shí)現(xiàn)數(shù)據(jù)編碼以及壓縮
MediaRecorder
已經(jīng)集成了錄音、編碼、壓縮等,支持少量的錄音音頻格式,大概有,aac,amr,3gp等
優(yōu)點(diǎn):集成,直接調(diào)用相關(guān)接口即可,代碼量小
缺點(diǎn):無(wú)法實(shí)時(shí)處理音頻;輸出的音頻格式不是很多,例如沒(méi)有輸出mp3格式文件
三.準(zhǔn)備工作
我們要實(shí)現(xiàn)的是一個(gè)實(shí)時(shí)的去錄音,播放,停止等功能的測(cè)試案例,那我們肯定要準(zhǔn)備點(diǎn)什么,比如說(shuō),我這里先創(chuàng)建一個(gè)項(xiàng)目——PCMSample
然后寫(xiě)個(gè)布局
layout_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp"> <Button android:id="@+id/startAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/button_bg" android:text="開(kāi)始錄音" android:textColor="@android:color/white"/> <Button android:id="@+id/stopAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginTop="5dp" android:background="@drawable/button_bg" android:enabled="false" android:text="停止錄音" android:textColor="@android:color/white"/> <Button android:id="@+id/playAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/button_bg" android:enabled="false" android:text="播放音頻" android:textColor="@android:color/white"/> <Button android:id="@+id/deleteAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:background="@drawable/button_bg" android:text="刪除PCM" android:textColor="@android:color/white"/> <ScrollView android:id="@+id/mScrollView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="5dp" android:layout_weight="1"> <TextView android:id="@+id/tv_audio_succeess" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="初始化完成...." android:textColor="@color/colorAccent"/> </ScrollView> </LinearLayout>
可以預(yù)覽一下

這里我給按鈕加了一個(gè)扁平的效果,實(shí)際上寫(xiě)了一個(gè)xml,很簡(jiǎn)單
button_bg.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape> <corners android:radius="30dp"/> <solid android:color="@color/colorPrimary"/> </shape> </item> <item android:state_pressed="false"> <shape> <corners android:radius="30dp"/> <solid android:color="@color/colorPrimaryDark"/> </shape> </item> </selector>
好的,回到正題,我們這里有四個(gè)按鈕,分別是開(kāi)始。停止,播放,和刪除,我們就是要實(shí)現(xiàn)這四個(gè)功能,在此之前,我們還需要做的事情就是添加權(quán)限,因?yàn)槲覀円浺艉蛯?xiě)內(nèi)存卡文件,所有需要這兩個(gè)權(quán)限即可
<!--錄音--> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!--讀取SD卡--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
這里初始化什么的就不說(shuō)了,我們直接進(jìn)入正題
四.開(kāi)始錄音
開(kāi)始錄音的話,這里,我們定義一個(gè)變量isRecording去控制,這樣就比較好結(jié)束了,而且要注意的是,錄音是不能放在UI線程的,你懂的,所以我們可以寫(xiě)一個(gè)開(kāi)始錄音的方法
//開(kāi)始錄音
public void StartRecord() {
Log.i(TAG,"開(kāi)始錄音");
//16K采集率
int frequency = 16000;
//格式
int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
//16Bit
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
//生成PCM文件
file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm");
Log.i(TAG,"生成文件");
//如果存在,就先刪除再創(chuàng)建
if (file.exists())
file.delete();
Log.i(TAG,"刪除文件");
try {
file.createNewFile();
Log.i(TAG,"創(chuàng)建文件");
} catch (IOException e) {
Log.i(TAG,"未能創(chuàng)建");
throw new IllegalStateException("未能創(chuàng)建" + file.toString());
}
try {
//輸出流
OutputStream os = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream dos = new DataOutputStream(bos);
int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);
short[] buffer = new short[bufferSize];
audioRecord.startRecording();
Log.i(TAG, "開(kāi)始錄音");
isRecording = true;
while (isRecording) {
int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
for (int i = 0; i < bufferReadResult; i++) {
dos.writeShort(buffer[i]);
}
}
audioRecord.stop();
dos.close();
} catch (Throwable t) {
Log.e(TAG, "錄音失敗");
}
}
首先,這里我們了解一下采樣率,編碼,音頻流等基本的概念,剩下的大多是讀寫(xiě)流的操作了,我們通過(guò)創(chuàng)建一個(gè)AudioRecord去寫(xiě)pcm文件,定義一個(gè)while循環(huán),用我們剛才定義的isRecording控制,所以,我們的點(diǎn)擊事件就
case R.id.startAudio:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
StartRecord();
Log.e(TAG,"start");
}
});
thread.start();
printLog("開(kāi)始錄音");
ButtonEnabled(false, true, false);
break;
這里要注意一下thread.start();開(kāi)啟線程,同時(shí)打印出log,具體代碼如下
//打印log
private void printLog(final String resultString) {
tv_audio_succeess.post(new Runnable() {
@Override
public void run() {
tv_audio_succeess.append(resultString + "\n");
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
這里,我為了防止ANR,所以控制了一下按鈕的焦點(diǎn)
//獲取/失去焦點(diǎn)
private void ButtonEnabled(boolean start, boolean stop, boolean play) {
startAudio.setEnabled(start);
stopAudio.setEnabled(stop);
playAudio.setEnabled(play);
}
好的,我們運(yùn)行一下

看起來(lái)沒(méi)什么變化,但是你去內(nèi)存卡中就會(huì)發(fā)現(xiàn)多了一個(gè)pcm文件

當(dāng)然,你只是點(diǎn)擊啟動(dòng)錄音是不會(huì)生成這個(gè)pcm文件的,你需要點(diǎn)擊停止停止錄音的按鈕
五.停止錄音
停止錄音很簡(jiǎn)單,我們控制通過(guò)改變寫(xiě)入流就好了
case R.id.stopAudio:
isRecording = false;
ButtonEnabled(true, false, true);
printLog("停止錄音");
break;
這樣才會(huì)生成PCM
六播放音頻
現(xiàn)在有了PCM我們可以試著去播放了,寫(xiě)一個(gè)播放的方法
//播放文件
public void PlayRecord() {
if(file == null){
return;
}
//讀取文件
int musicLength = (int) (file.length() / 2);
short[] music = new short[musicLength];
try {
InputStream is = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
DataInputStream dis = new DataInputStream(bis);
int i = 0;
while (dis.available() > 0) {
music[i] = dis.readShort();
i++;
}
dis.close();
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
musicLength * 2,
AudioTrack.MODE_STREAM);
audioTrack.play();
audioTrack.write(music, 0, musicLength);
audioTrack.stop();
} catch (Throwable t) {
Log.e(TAG, "播放失敗");
}
}
正如上面所說(shuō),我們播放需要用到AudioTrack,調(diào)用他的play方法以及設(shè)置一些參數(shù)即可
七.刪除音頻
刪除音頻只需要?jiǎng)h除這個(gè)pcm文件就行
//刪除文件
private void deleFile() {
if(file == null){
return;
}
file.delete();
printLog("文件刪除成功");
}
這就是大致的錄音邏輯,雖然看起來(lái)很簡(jiǎn)單,但是這正是現(xiàn)在很多語(yǔ)音和音頻的最基礎(chǔ)部分,特別是語(yǔ)音,如果你從事語(yǔ)音的工作,我相信你會(huì)感謝我的!
好了,最后放上完整的代碼:
MainActivity
package com.liuguilin.pcmsample;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final String TAG = "PCMSample";
//是否在錄制
private boolean isRecording = false;
//開(kāi)始錄音
private Button startAudio;
//結(jié)束錄音
private Button stopAudio;
//播放錄音
private Button playAudio;
//刪除文件
private Button deleteAudio;
private ScrollView mScrollView;
private TextView tv_audio_succeess;
//pcm文件
private File file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
//初始化View
private void initView() {
mScrollView = (ScrollView) findViewById(R.id.mScrollView);
tv_audio_succeess = (TextView) findViewById(R.id.tv_audio_succeess);
printLog("初始化成功");
startAudio = (Button) findViewById(R.id.startAudio);
startAudio.setOnClickListener(this);
stopAudio = (Button) findViewById(R.id.stopAudio);
stopAudio.setOnClickListener(this);
playAudio = (Button) findViewById(R.id.playAudio);
playAudio.setOnClickListener(this);
deleteAudio = (Button) findViewById(R.id.deleteAudio);
deleteAudio.setOnClickListener(this);
}
//點(diǎn)擊事件
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.startAudio:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
StartRecord();
Log.e(TAG,"start");
}
});
thread.start();
printLog("開(kāi)始錄音");
ButtonEnabled(false, true, false);
break;
case R.id.stopAudio:
isRecording = false;
ButtonEnabled(true, false, true);
printLog("停止錄音");
break;
case R.id.playAudio:
PlayRecord();
ButtonEnabled(true, false, false);
printLog("播放錄音");
break;
case R.id.deleteAudio:
deleFile();
break;
}
}
//打印log
private void printLog(final String resultString) {
tv_audio_succeess.post(new Runnable() {
@Override
public void run() {
tv_audio_succeess.append(resultString + "\n");
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
//獲取/失去焦點(diǎn)
private void ButtonEnabled(boolean start, boolean stop, boolean play) {
startAudio.setEnabled(start);
stopAudio.setEnabled(stop);
playAudio.setEnabled(play);
}
//開(kāi)始錄音
public void StartRecord() {
Log.i(TAG,"開(kāi)始錄音");
//16K采集率
int frequency = 16000;
//格式
int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
//16Bit
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
//生成PCM文件
file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm");
Log.i(TAG,"生成文件");
//如果存在,就先刪除再創(chuàng)建
if (file.exists())
file.delete();
Log.i(TAG,"刪除文件");
try {
file.createNewFile();
Log.i(TAG,"創(chuàng)建文件");
} catch (IOException e) {
Log.i(TAG,"未能創(chuàng)建");
throw new IllegalStateException("未能創(chuàng)建" + file.toString());
}
try {
//輸出流
OutputStream os = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream dos = new DataOutputStream(bos);
int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);
short[] buffer = new short[bufferSize];
audioRecord.startRecording();
Log.i(TAG, "開(kāi)始錄音");
isRecording = true;
while (isRecording) {
int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
for (int i = 0; i < bufferReadResult; i++) {
dos.writeShort(buffer[i]);
}
}
audioRecord.stop();
dos.close();
} catch (Throwable t) {
Log.e(TAG, "錄音失敗");
}
}
//播放文件
public void PlayRecord() {
if(file == null){
return;
}
//讀取文件
int musicLength = (int) (file.length() / 2);
short[] music = new short[musicLength];
try {
InputStream is = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
DataInputStream dis = new DataInputStream(bis);
int i = 0;
while (dis.available() > 0) {
music[i] = dis.readShort();
i++;
}
dis.close();
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
musicLength * 2,
AudioTrack.MODE_STREAM);
audioTrack.play();
audioTrack.write(music, 0, musicLength);
audioTrack.stop();
} catch (Throwable t) {
Log.e(TAG, "播放失敗");
}
}
//刪除文件
private void deleFile() {
if(file == null){
return;
}
file.delete();
printLog("文件刪除成功");
}
}
如果你想去調(diào)試這些pcm文件做音頻測(cè)試的話,我推薦使用Audacity這個(gè)軟件,可以看到,我直接點(diǎn)擊左上角的file-導(dǎo)入-源文件,然后設(shè)置16K

這樣就可以調(diào)試了

最后,放一張完整的截圖

以上所述是小編給大家介紹的Android音頻處理之通過(guò)AudioRecord去保存PCM文件進(jìn)行錄制,播放,停止,刪除功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Android用AudioRecord進(jìn)行錄音
- Android利用AudioRecord類(lèi)實(shí)現(xiàn)音頻錄制程序
- Android使用AudioRecord實(shí)現(xiàn)暫停錄音功能實(shí)例代碼
- Android錄音--AudioRecord、MediaRecorder的使用
- Android使用AudioRecord判斷是否有音頻輸入
- Android提高之AudioRecord實(shí)現(xiàn)助聽(tīng)器的方法
- android AudioRecorder簡(jiǎn)單心得分享
- Android?WebRTC?對(duì)?AudioRecord?的使用技術(shù)分享
相關(guān)文章
Android GridView添加頭部問(wèn)題的解決
這篇文章主要介紹了Android GridView添加頭部問(wèn)題的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
kotlin中的數(shù)據(jù)轉(zhuǎn)換方法(示例詳解)
這篇文章介紹了Kotlin中將數(shù)字轉(zhuǎn)換為字符串和字符串轉(zhuǎn)換為數(shù)字的多種方法,包括使用`toString()`、字符串模板、格式化字符串、處理可空類(lèi)型等,同時(shí),也詳細(xì)講解了如何安全地進(jìn)行字符串到數(shù)字的轉(zhuǎn)換,并處理了不同進(jìn)制和本地化格式的字符串轉(zhuǎn)換,感興趣的朋友一起看看吧2025-03-03
Android仿微信進(jìn)度彈出框的實(shí)現(xiàn)方法
最近公司項(xiàng)目需要實(shí)現(xiàn)類(lèi)似微信進(jìn)度條彈出框效果,其實(shí)現(xiàn)方法并不難,下面給大家介紹下Android仿微信進(jìn)度彈出框的實(shí)現(xiàn)方法,需要的朋友參考下吧2017-01-01
Android觸摸及手勢(shì)操作GestureDetector
這篇文章主要a為大家詳細(xì)介紹了Android觸摸及手勢(shì)操作GestureDetector的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-07-07
詳解Android Automotive車(chē)載應(yīng)用對(duì)駕駛模式Safe Drive Mode的適配
這篇文章主要介紹了詳解Android Automotive車(chē)載應(yīng)用對(duì)駕駛模式(Safe Drive Mode)的適配,對(duì)車(chē)載應(yīng)用感興趣的同學(xué)可以參考下2021-04-04
Android Studio 3.6 調(diào)試 smali的全過(guò)程
這篇文章主要介紹了Android Studio 3.6 調(diào)試 smali, 目前最新版的 Android Studio 利用附加功能調(diào)試 smali 非常方便,具體操作步驟跟隨小編一起看看吧2020-02-02
Android開(kāi)發(fā)實(shí)現(xiàn)按鈕點(diǎn)擊切換背景并修改文字顏色的方法
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)按鈕點(diǎn)擊切換背景并修改文字顏色的方法,涉及Android界面布局與相關(guān)屬性設(shè)置技巧,需要的朋友可以參考下2018-01-01

