本文主要是介绍Android之音頻錄製-實例篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
使用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"><LinearLayoutandroid: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"><Buttonandroid: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" /><Buttonandroid: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" /><Buttonandroid:id="@+id/audio_stop"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:background="@drawable/btn_stop" /><Buttonandroid:id="@+id/play_audio"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:background="@drawable/btn_play" /><Buttonandroid:id="@+id/pause_audio"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:background="@drawable/btn_pause" /><Buttonandroid:id="@+id/audio_delete"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:background="@drawable/btn_delete" /></LinearLayout><TextViewandroid: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" /><SeekBarandroid: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;@Overrideprotected 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);}@Overridepublic 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() {@Overridepublic void run() {if (isRecord) {second++;if (second >= 60) {second = 0;minute++;if (minute >= 60) {hour++;}}recordTimerHandler.sendEmptyMessage(what);}}}, 1000, 1000);}@Overrideprotected void onStop() {if (isRecord) {audioPause();} else if (isPlay) {pauseAudio();}super.onStop();}@Overrideprotected 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 {@Overridepublic void run() {recordPath = recordFile(recordPath);}}/*** 將PCM格式音頻文件轉換成WAV格式音頻文件線程*/public class RecordConvertRunnable implements Runnable {@Overridepublic 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;}@Overridepublic 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資源的HandleraudioPlayOverHandler.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文件轉其他格式音頻文件的操作等
歡飲拍磚
这篇关于Android之音頻錄製-實例篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!