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">

	<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文件轉其他格式音頻文件的操作等


歡飲拍磚

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值