使用AudioRecord&AudioTrack進行錄製音頻文件與播放操作
代碼稍微多了點,儘量一個章節寫完
首先看看介面圖
然後看看項目目錄
之後就是代碼環節了,首先看佈局文件
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_audio">
<LinearLayout
android:id="@+id/audio_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:layout_alignParentBottom="true"
android:background="@drawable/background_key">
<Button
android:id="@+id/audio_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_weight="1"
android:background="@drawable/btn_record" />
<Button
android:id="@+id/audio_pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_weight="1"
android:background="@drawable/btn_pause" />
<Button
android:id="@+id/audio_stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_stop" />
<Button
android:id="@+id/play_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_play" />
<Button
android:id="@+id/pause_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_pause" />
<Button
android:id="@+id/audio_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_delete" />
</LinearLayout>
<TextView
android:id="@+id/audio_timer"
android:layout_above="@id/audio_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:gravity="center"
android:text="00:00:00"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="30sp" />
<SeekBar
android:id="@+id/seekbar_audio"
android:layout_above="@id/audio_timer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:progressDrawable="@drawable/style_seekbar"
android:thumb="@drawable/seekbar_play" />
</RelativeLayout>
然後看看AndroidManifest.xml文件中的權限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
下面,將會給出MainActivity、AudioRecordDecorated、AudioTrackDecorated中的代碼,代碼中都已經給出了註釋信息,在文章中,就不多說了
/**
* 使用AudioRecord錄製音頻文件
* 使用AudioTrack播放PCM、WAV格式音頻文件
* @author Francis-ChinaFeng
* @version 1.0 2013-08-31
*/
public class MainActivity extends Activity implements OnClickListener {
// 聲明控件對象
private Button audio_record, audio_pause, audio_stop, play_audio, pause_audio, audio_delete;
private TextView audio_timer;
private SeekBar seekbar_audio;
// 聲明AudioRecord參數
// 設置錄製音頻的硬件設備
private int audioSource = AudioSource.MIC;
// 設置音頻採樣率,不同的設備採樣率也不同,如果錄製的音頻無聲、雜音可以修改採樣率的值
private int sampleRateInHz = 44100;
// 設置音頻錄製聲道CHANNEL_IN_STEREO表示雙聲道
private int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
// 設置錄製音頻的編碼ENCODING_PCM_16BIT差不多可以被全部設備接受
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
// 設置錄製音頻緩衝區
private int bufferSizeInBytes;
// 聲明AudioRecord對象,用於錄製音頻文件
private AudioRecord audioRecord;
// 聲明AudioTrack對象,用於播放音頻文件
private AudioTrack audioTrack;
// 聲明一堆程序變量
private boolean isCard, isRecord, isPlay;
private String sdCard, recordPath, audioPath, recordFormat, format;
private int offset;
private Timer timer;
private int hour, minute, second;
private int what = 0x111;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
initView();
setListener();
}
/**
* 初始化操作
*/
private void init() {
audio_record = (Button) findViewById(R.id.audio_record);
audio_pause = (Button) findViewById(R.id.audio_pause);
audio_stop = (Button) findViewById(R.id.audio_stop);
play_audio = (Button) findViewById(R.id.play_audio);
pause_audio = (Button) findViewById(R.id.pause_audio);
audio_delete = (Button) findViewById(R.id.audio_delete);
audio_timer = (TextView) findViewById(R.id.audio_timer);
seekbar_audio = (SeekBar) findViewById(R.id.seekbar_audio);
isCard = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
isRecord = false;
isPlay = false;
if (isCard) {
sdCard = SDCardUtils.getExternalPath();
}
recordFormat = ".pcm";
format = ".wav";
}
/**
* 初始化Activity頁面控件
*/
private void initView() {
audio_pause.setVisibility(View.GONE);
pause_audio.setVisibility(View.GONE);
seekbar_audio.setVisibility(View.GONE);
audio_stop.setEnabled(false);
play_audio.setEnabled(false);
audio_delete.setEnabled(false);
}
/**
* 綁定按鈕點擊事件
*/
private void setListener() {
audio_record.setOnClickListener(this);
audio_pause.setOnClickListener(this);
audio_stop.setOnClickListener(this);
play_audio.setOnClickListener(this);
pause_audio.setOnClickListener(this);
audio_delete.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (!isCard) {
toast("SDCard is Invalid");
return;
}
switch (v.getId()) {
case R.id.audio_record:
audioRecord();
toast("Audio Record is Start");
break;
case R.id.audio_pause:
audioPause();
toast("Audio Record is Pause");
break;
case R.id.audio_stop:
audioStop();
toast("Audio Record is Stop");
break;
case R.id.play_audio:
playAudio();
toast("Audio Play is Start");
break;
case R.id.pause_audio:
pauseAudio();
toast("Audio Play is Pause");
break;
case R.id.audio_delete:
audioDelete();
toast("Audio File is Deleted");
break;
}
}
// 開始錄製音頻文件
private void audioRecord() {
if (recordPath == null) {
recordPath = sdCard + File.separator + System.currentTimeMillis() + recordFormat;
audio_timer.setText("00:00:00");
}
isRecord = true;
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
// 使用裝飾者模式,簡潔Activity類中的代碼
audioRecord = AudioRecordDecorated.play(audioRecord, audioSource,
sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
// 開啟一條線程,用於將音頻數據寫入SDCard中
new Thread(new AudioRecordRunnable()).start();
// 開啟計時器
setRecordTimer();
// 用於控制按鈕顯示、點擊操作
referenceRecordView();
}
// 暫停錄製音頻文件
private void audioPause() {
isRecord = false;
// 釋放AudioRecord對象資源,其實不需要接收返回值,此處為保險起見
audioRecord = AudioRecordDecorated.release(audioRecord);
// 刷新Activity控件
referenceRecordView();
}
// 停止錄製音頻文件
private void audioStop() {
audioPause();
// 刷新Activity控件,重置一些數據
resetRecordView();
// 開啟一條線程,用於將PCM格式音頻文件轉換成WAV音頻文件,用於在PC端上進行播放
new Thread(new RecordConvertRunnable()).start();
}
// 開始播放
private void playAudio() {
isPlay = true;
bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
// 封裝AudioTrack播放音頻文件的初始操作
audioTrack = AudioTrackDecorated.play(audioTrack, sampleRateInHz,
channelConfig, audioFormat, bufferSizeInBytes);
// 刷新Activity頁面
referenceTrackView();
File file = new File(audioPath);
if (file.exists() && file.length() > 0) {
// 開啟一條線程,用於播放音頻文件
new Thread(new AudioTrackRunnable(file)).start();
} else {
toast(audioPath + " Is Not Found");
}
}
// 暫停播放音頻文件
private void pauseAudio() {
isPlay = false;
// 釋放AudioTrack對象資源
audioTrack = AudioTrackDecorated.release(audioTrack);
referenceTrackView();
}
// 刪除剛剛錄製的音頻文件
private void audioDelete() {
if (isPlay) {
// 如果在播放時,刪除文件,需要釋AudioTrack資源,直接調用暫停方法,省事
pauseAudio();
}
File file = new File(audioPath);
if (file.exists()) {
file.delete();
}
// 刷新頁面
resetTrackView();
}
// 刷新頁面,控制控件點擊事件
private void referenceRecordView() {
if (isRecord) {
audio_record.setVisibility(View.GONE);
seekbar_audio.setVisibility(View.GONE);
audio_pause.setVisibility(View.VISIBLE);
audio_timer.setVisibility(View.VISIBLE);
audio_stop.setEnabled(isRecord);
play_audio.setEnabled(!isRecord);
} else {
audio_record.setVisibility(View.VISIBLE);
audio_pause.setVisibility(View.GONE);
if (timer != null) {
timer.cancel();
timer = null;
}
}
}
// 刷新頁面,控制控件點擊事件
private void referenceTrackView() {
if (isPlay) {
play_audio.setVisibility(View.GONE);
audio_timer.setVisibility(View.GONE);
pause_audio.setVisibility(View.VISIBLE);
seekbar_audio.setVisibility(View.VISIBLE);
} else {
play_audio.setVisibility(View.VISIBLE);
pause_audio.setVisibility(View.GONE);
}
}
// 重置頁面,控制頁面控制點擊事件
private void resetRecordView() {
hour = 0;
minute = 0;
second = 0;
audio_stop.setEnabled(false);
play_audio.setEnabled(true);
audio_delete.setEnabled(true);
audio_record.setVisibility(View.VISIBLE);
audio_pause.setVisibility(View.GONE);
audioPath = recordPath;
recordPath = null;
}
// 重置頁面,控制頁面控制點擊事件
private void resetTrackView() {
audio_timer.setText("00:00:00");
seekbar_audio.setVisibility(View.GONE);
audio_timer.setVisibility(View.VISIBLE);
play_audio.setEnabled(false);
audio_delete.setEnabled(false);
audioPath = null;
}
// 計時器
private void setRecordTimer() {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (isRecord) {
second++;
if (second >= 60) {
second = 0;
minute++;
if (minute >= 60) {
hour++;
}
}
recordTimerHandler.sendEmptyMessage(what);
}
}
}, 1000, 1000);
}
@Override
protected void onStop() {
if (isRecord) {
audioPause();
} else if (isPlay) {
pauseAudio();
}
super.onStop();
}
@Override
protected void onDestroy() {
if (isRecord) {
audioStop();
} else if (isPlay) {
pauseAudio();
}
super.onDestroy();
}
private void toast(String text) {
Log.e("custom", text);
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
}
// 計時器Handler,用於顯示錄製音頻時間
private Handler recordTimerHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == what ) {
StringBuilder builder = new StringBuilder();
builder.append(hour < 10 ? "0"+ hour : hour);
builder.append(":");
builder.append(minute < 10 ? "0"+ minute : minute);
builder.append(":");
builder.append(second < 10 ? "0"+ second : second);
audio_timer.setText(builder.toString());
}
};
};
// 播放Handler,用於控制播放進度條
private Handler audioPlayingHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == what) {
seekbar_audio.setProgress(offset);
}
};
};
// 播放完畢Handler,用於調用暫停播放,釋放資源操作
private Handler audioPlayOverHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == what) {
seekbar_audio.setProgress(0);
pauseAudio();
}
};
};
/**
* 錄製音頻文件線程
*/
public class AudioRecordRunnable implements Runnable {
@Override
public void run() {
recordPath = recordFile(recordPath);
}
}
/**
* 將PCM格式音頻文件轉換成WAV格式音頻文件線程
*/
public class RecordConvertRunnable implements Runnable {
@Override
public void run() {
String targetPath = audioPath.replace(recordFormat, format);
audioPath = AudioRecordDecorated.convertWAVFile(audioPath,
targetPath, sampleRateInHz, bufferSizeInBytes, true);
}
}
/**
* 播放WAV格式音頻文件,同樣可以播放PCM格式音頻文件,不過PCM作為臨時文件在播放之前會被刪除
*/
public class AudioTrackRunnable implements Runnable {
private File file;
public AudioTrackRunnable(File file) {
this.file = file;
}
@Override
public void run() {
// RandomAccessFile可以很好的獲取文件的某個具體位置,用於多線程下載、音頻文件播放很適用
RandomAccessFile in = null;
try {
// 聲明只讀RandomAccessFile對象
in = new RandomAccessFile(file, "r");
// 設置SeekBar的最大值
seekbar_audio.setMax((int) in.getChannel().size());
// 設置文件的讀取位置
in.seek(offset);
byte[] audioData = new byte[1024];
int i;
while ((i = in.read(audioData)) != -1 && isPlay) {
if (audioTrack != null && isPlay) {
// 記錄音頻文件的播放位置
offset += i;
audioTrack.write(audioData, 0, i);
// 使用Handler改變SeekBar的位置
audioPlayingHandler.sendEmptyMessage(what);
}
// 判斷是否播放完畢
if (offset >= in.getChannel().size()) {
offset = 0;
// 釋放AudioTrack資源的Handler
audioPlayOverHandler.sendEmptyMessage(what);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 將錄製的音頻文件持久化
* @param path
* @return
*/
public String recordFile(String path) {
File file = new File(path);
RandomAccessFile out = null;
try {
if (!file.exists()) {
file.createNewFile();
}
out = new RandomAccessFile(file, "rw");
out.seek(out.length());
byte[] audioData = new byte[bufferSizeInBytes];
while (isRecord) {
int i = audioRecord.read(audioData, 0, bufferSizeInBytes);
if (AudioRecord.ERROR_INVALID_OPERATION != i) {
out.write(audioData, 0, i);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.freeSource(out);
}
if (file.exists() && file.length() > 0) {
return file.getAbsolutePath();
}
return null;
}
}
/**
* AudioRecord的裝飾類
* @author Francis-ChinaFeng
* @version 1.0 2013-8-30
*/
public class AudioRecordDecorated {
/**
* 使用AudioRecord開始錄製PCM格式音頻文件
* @param audioRecord AudioRecord對象
* @param audioSource 錄製音頻文件的硬件設備源
* @param sampleRateInHz 錄製音頻文件的採樣率
* @param channelConfig 錄製音頻文件的聲道
* @param audioFormat 錄製音頻文件的編碼格式
* @param bufferSizeInBytes 錄製音頻文件的緩存
* @return
*/
public static AudioRecord play(AudioRecord audioRecord, int audioSource,
int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes) {
release(audioRecord);
audioRecord = new AudioRecord(audioSource, sampleRateInHz,
channelConfig, audioFormat, bufferSizeInBytes);
audioRecord.startRecording();
return audioRecord;
}
/**
* 釋放AudioRecord資源
* @param audioRecord
* @return
*/
public static AudioRecord release(AudioRecord audioRecord) {
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
return audioRecord;
}
/**
* 將PCM格式音頻文件轉換成WAV格式文件
* @param source PCM文件源路徑
* @param target WAV文件目標路徑
* @param sampleRateInHz
* @param bufferSizeInBytes
* @param bool 是否刪除PCM文件
* @return
*/
public static String convertWAVFile(String source, String target,
long sampleRateInHz, int bufferSizeInBytes, boolean bool) {
File file = new File(source);
if (file != null && file.exists() && file.isFile() && file.length() > 0) {
File targetFile = convertWAVFile(source, target, sampleRateInHz, bufferSizeInBytes);
if (targetFile != null && targetFile.exists() && targetFile.isFile()) {
if (bool) {
file.delete();
}
return targetFile.getAbsolutePath();
}
}
return null;
}
/**
*
* @param source
* @param target
* @param sampleRateInHz
* @param bufferSizeInBytes
* @return
*/
private static File convertWAVFile(String source, String target,
long sampleRateInHz, int bufferSizeInBytes) {
BufferedInputStream in = null;
BufferedOutputStream out = null;
long longSampleRate = sampleRateInHz;
int channels = 2;
long byteRate = 16 * sampleRateInHz * channels / 8;
byte[] buffer = new byte[bufferSizeInBytes];
File targetFile = new File(target);
try {
if (!targetFile.exists()) {
targetFile.createNewFile();
}
in = new BufferedInputStream(new FileInputStream(source));
out = new BufferedOutputStream(new FileOutputStream(targetFile));
long totalAudioLen = in.available();
long totalDataLen = totalAudioLen + 36;
convertWAVFile(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
int i;
while ((i = in.read(buffer)) != -1) {
out.write(buffer, 0, i);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.freeSource(out, in);
}
if (targetFile != null && targetFile.exists() && targetFile.length() > 0) {
System.out.println(targetFile.getAbsolutePath() +"---"+ targetFile.length());
return targetFile;
}
return null;
}
/**
* 在PCM文件之前添加44個字節,轉換成WAV格式文件
* @param out
* @param totalAudioLen
* @param totalDataLen
* @param longSampleRate
* @param channels
* @param byteRate
* @throws IOException
*/
private static void convertWAVFile(BufferedOutputStream out,
long totalAudioLen, long totalDataLen, long longSampleRate,
int channels, long byteRate) throws IOException {
byte[] header = new byte[44];
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);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
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);
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
header[34] = 16;
header[35] = 0;
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);
}
}
/**
*
* @author Francis-ChinaFeng
* @version 1.0 2013-8-31
*/
public class AudioTrackDecorated {
/**
* 使用AudioTrack播放PCM、WAV格式文件
* @param audioTrack
* @param sampleRateInHz
* @param channelConfig
* @param audioFormat
* @param bufferSizeInBytes
* @return
*/
public static AudioTrack play(AudioTrack audioTrack, int sampleRateInHz,
int channelConfig, int audioFormat, int bufferSizeInBytes) {
release(audioTrack);
int streamType = AudioManager.STREAM_MUSIC;
int mode = AudioTrack.MODE_STREAM;
audioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig,
audioFormat, bufferSizeInBytes, mode);
audioTrack.play();
return audioTrack;
}
/**
* 釋放AudioTrack對象資源
* @param audioTrack
* @return
*/
public static AudioTrack release(AudioTrack audioTrack) {
if (audioTrack != null) {
audioTrack.stop();
audioTrack.release();
audioTrack = null;
}
return audioTrack;
}
}
整個錄製音頻操作也就差不多了,不過還有些內容是可以完善的:用戶拖動SeekBar操作、動態改變採樣率、PCM文件轉其他格式音頻文件的操作等
歡飲拍磚