使用AudioRecord和AudioTrack来录制和播放音频

1.使用AudioRecord录制原始音频

除了通过意图启动录音机和使用MediaRecorder之外,Android还提供了第三种方法来捕获音频:使用成为AudioRecord的类。AudioRecord是三种方法里最灵活的(因为允许访问原始音频流),但是它拥有的内置功能也是最少的,如不会自动压缩音频。

使用AudioRecord的基础知识非常简单。我们只需要构造一个AudioRecord类型的对象,并传入各种不同的配置参数。

需要指定的第一个值是音频源。下面使用的值与用于MediaRecorder的值相同,其在MediaRecorder.AudioSource中定义。实际上,这意味着可以使用MediaRecorder.AudioSource.MIC。

int audioSource=MediaRecorder.AudioSource.MIC;
需要指定的第二个值是 录制的采样率。以赫兹(Hz)为单位指定它。MediaRecorder的采样音频是8kHz,而CD质量的音频通常是44.1Hz。不同的安卓手机硬件能够以不同的采样率进行采样。而本示例则以11025Hz的采样率进行采样,这是另外一个常用的采样率。

int frequency = 11025;

接下来,需要指定的第三个值是 捕获的音频通道的数量。在AudioFormat类中指定了用于此参数的常量,而且可根据名称理解它们。
 AudioFormat.CHANNEL_CONFIGURATION_MONO;//channelConfiguration
 AudioFormat.CHANNEL_CONFIGURATION_STEREO;
 AudioFormat.CHANNEL_CONFIGURATION_INVALID;
 AudioFormat.CHANNEL_CONFIGURATION_DEFAULT;
随后,需要指定的第四个值是 音频的格式。在 AudioFormat类中也指定了以下各种可能的常量。

 AudioFormat.ENCODING_DEFAULT;//audioEncoding
 AudioFormat.ENCODING_INVALID;
 AudioFormat.ENCODING_PCM_16BIT;
 AudioFormat.ENCODING_PCM_8BIT;
PCM代表脉冲编码调制,它实际上是原始的音频样本。16位将占用更多的空间和处理能力,但是表示的音频将更接近真实。

最后,需要指定的是第五个值是缓冲区大小。实际上可以查询AudioRecord类以获得最小缓冲区大小,查询方式是调用getMinBufferSize静态方法,同时传入采样率、通道配置以及音频格式。

int bufferSize = AudioTrack.getMinBufferSize(frequency,channelConfiguration, audioEncoding);
完成了以上步骤,我们就可以构造实际的AudioRecord对象。
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency,
						channelConfiguration, audioEncoding, bufferSize);
但值得注意的是,AudioRecord类实际上 并不保存捕获的音频,因此需要手动保存捕获的音频。可以首先利用以下方法来创建一个文件。

File path = new File(
	Environment.getExternalStorageDirectory().getAbsolutePath()
		+ "/AudioRecorder/files/");
path.mkdirs();
try
{
	recordingFile = File.createTempFile("recording", ".pcm", path);
} catch (IOException e)
{
	throw new RuntimeException("Couldn't create file on SD card", e);
}
接下来再创建该文件对应的OutputStream输出流,尤其是出于 性能和便利的原因,可以将它包装在BufferedOutputStream和DataOutputStream中。

DataOutputStream dos = new DataOutputStream(
	new BufferedOutputStream(new FileOutputStream(
		recordingFile)));
现在就可以启动捕获,同时将音频样本写入到文件中。可以使用short数组来保存从AudioRecord对象读取而来的音频。同时, 将采用比AudioRecord对象的缓冲区更小的数组,从而在确保将音频读出来之前缓冲区没有被填满。
为了确保此数组小于缓冲区大小,需要将缓冲区大小除以4.因为缓冲区的大小以字节为单位,而每个short类型的数据占用2个字节,所以除以2并不足够。所以除以4就刚好使得该数组是AudioRecord对象内部缓冲区大小的一半了。

short[] audiodata = new short[bufferSize / 4];
至此,只需要调用AudioRecord对象上的startRecording方法即可。
录制开始之后,可以构造一个循环,不断从AudioRecorder对象读取音频并放入short数组中,同时写入对应文件的DataOutputStream
while (isRecording)
{
	int bufferReadResult = audioRecord.read(buffer, 0,bufferSize);
	for (int i = 0; i < bufferReadResult; i++)
	{
		dos.writeShort(buffer[i]);
	}
}
当想结束录音的时候,调用AudioRecord对象上的stop方法和DataOutputStream上的close方法。

2.使用AudioTrack来播放原始音频

AudioTrack允许播放AudioRecord捕获的原始音频类,而它们并不能使用MediaPlayer对象播放。

为了构造一个AudioTrack对象,需要传入以下一系列配置变量来描述待播放的音频

第一个参数是流类型。可能的值定义为AudioManager类的常量。例如

AudioManager.STREAM_MUSIC//正常播放音乐的音频流
第二个参数是播放音频数据的采样率,这里的参数需要和AudioRecord指定的参数一致。

第三个参数是通道配置。可能的值与构造AudioRecord的值相同。

第四个参数是音频格式。可能的值与构造AudioRecord的值相同。

第五个参数是将在对象中用于存储音频的缓冲区大小。为了确定使用最小的缓冲区大小,可以调用getMinBufferSize方法,同时传入采样率、通道配置和音频格式。

最后一个参数是模式。可能的值定义为AudioTrack类中的常量。

AudioTrack.MODE_STATIC://在播放发生之前将所有的音频数据转移到AudioTrack对象
AudioTrack.MODE_STREAM://在播放的同时将音频数据持续地转移到AudioTrack对象。
AudioTrack audioTrack = new AudioTrack(
	AudioManager.STREAM_MUSIC, frequency,
	channelConfiguration, audioEncoding, bufferSize,
	AudioTrack.MODE_STREAM);

构造了AudioTrack对象之后,就需要打开音频源,将音频数据读取到缓冲区中,并将它传递给AudioTrack对象。我们可以根据一个包含了正确格式的原始PCM数据的文件,构造DataInputStream。

DataInputStream dis = new DataInputStream(
	new BufferedInputStream(new FileInputStream(
		recordingFile)));
然后调用AudioTrack对象上的play方法,并开始从DataInputStream读取音频。

audioTrack.play();

while (isPlaying && dis.available() > 0)
{
	int i = 0;
	while (dis.available() > 0 && i < audiodata.length)
	{
		audiodata[i] = dis.readShort();
		i++;
	}
	audioTrack.write(audiodata, 0, audiodata.length);
}

dis.close();
下面是一个完整的示例,通过使用异步任务,每个操作都在它们各自的线程中完成,所以并不会阻碍UI线程。


public class AltAudioRecorder extends Activity implements OnClickListener
{

	RecordAudio recordTask;
	PlayAudio playTask;

	Button startRecordingButton, stopRecordingButton, startPlaybackButton,
			stopPlaybackButton;
	TextView statusText;

	File recordingFile;

	boolean isRecording = false;
	boolean isPlaying = false;

	int frequency = 11025;
	int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
	int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		statusText = (TextView) this.findViewById(R.id.StatusTextView);

		startRecordingButton = (Button) this
				.findViewById(R.id.StartRecordingButton);
		stopRecordingButton = (Button) this
				.findViewById(R.id.StopRecordingButton);
		startPlaybackButton = (Button) this
				.findViewById(R.id.StartPlaybackButton);
		stopPlaybackButton = (Button) this
				.findViewById(R.id.StopPlaybackButton);

		startRecordingButton.setOnClickListener(this);
		stopRecordingButton.setOnClickListener(this);
		startPlaybackButton.setOnClickListener(this);
		stopPlaybackButton.setOnClickListener(this);

		stopRecordingButton.setEnabled(false);
		startPlaybackButton.setEnabled(false);
		stopPlaybackButton.setEnabled(false);

		File path = new File(
				Environment.getExternalStorageDirectory().getAbsolutePath()
						+ "/Android/data/AudioRecorder/files/");
		path.mkdirs();
		try
		{
			recordingFile = File.createTempFile("recording", ".pcm", path);
		} catch (IOException e)
		{
			throw new RuntimeException("Couldn't create file on SD card", e);
		}
	}

	public void onClick(View v)
	{
		if (v == startRecordingButton)
		{
			record();
		} else if (v == stopRecordingButton)
		{
			stopRecording();
		} else if (v == startPlaybackButton)
		{
			play();
		} else if (v == stopPlaybackButton)
		{
			stopPlaying();
		}
	}

	public void play()
	{
		startPlaybackButton.setEnabled(true);

		playTask = new PlayAudio();
		playTask.execute();

		stopPlaybackButton.setEnabled(true);
	}

	public void stopPlaying()
	{
		isPlaying = false;
		stopPlaybackButton.setEnabled(false);
		startPlaybackButton.setEnabled(true);
	}

	public void record()
	{
		startRecordingButton.setEnabled(false);
		stopRecordingButton.setEnabled(true);

		// For Fun
		startPlaybackButton.setEnabled(true);

		recordTask = new RecordAudio();
		recordTask.execute();
	}

	public void stopRecording()
	{
		isRecording = false;
	}

	private class PlayAudio extends AsyncTask<Void, Integer, Void>
	{
		@Override
		protected Void doInBackground(Void... params)
		{
			isPlaying = true;

			int bufferSize = AudioTrack.getMinBufferSize(frequency,
					channelConfiguration, audioEncoding);
			short[] audiodata = new short[bufferSize / 4];

			try
			{
				DataInputStream dis = new DataInputStream(
						new BufferedInputStream(new FileInputStream(
								recordingFile)));

				AudioTrack audioTrack = new AudioTrack(
						AudioManager.STREAM_MUSIC, frequency,
						channelConfiguration, audioEncoding, bufferSize,
						AudioTrack.MODE_STREAM);

				audioTrack.play();

				while (isPlaying && dis.available() > 0)
				{
					int i = 0;
					while (dis.available() > 0 && i < audiodata.length)
					{
						audiodata[i] = dis.readShort();
						i++;
					}
					audioTrack.write(audiodata, 0, audiodata.length);
				}

				dis.close();

				startPlaybackButton.setEnabled(false);
				stopPlaybackButton.setEnabled(true);

			} catch (Throwable t)
			{
				Log.e("AudioTrack", "Playback Failed");
			}

			return null;
		}
	}

	private class RecordAudio extends AsyncTask<Void, Integer, Void>
	{
		@Override
		protected Void doInBackground(Void... params)
		{
			isRecording = true;

			try
			{
				DataOutputStream dos = new DataOutputStream(
						new BufferedOutputStream(new FileOutputStream(
								recordingFile)));

				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();

				int r = 0;
				while (isRecording)
				{
					int bufferReadResult = audioRecord.read(buffer, 0,
							bufferSize);
					for (int i = 0; i < bufferReadResult; i++)
					{
						dos.writeShort(buffer[i]);
					}

					publishProgress(new Integer(r));
					r++;
				}

				audioRecord.stop();
				dos.close();
			} catch (Throwable t)
			{
				Log.e("AudioRecord", "Recording Failed");
			}

			return null;
		}

		protected void onProgressUpdate(Integer... progress)
		{
			statusText.setText(progress[0].toString());
		}

		protected void onPostExecute(Void result)
		{
			startRecordingButton.setEnabled(true);
			stopRecordingButton.setEnabled(false);
			startPlaybackButton.setEnabled(true);
		}
	}
}




来实现一个基于Android平台的录音和播放功能? 要实现基于Android平台的录音和播放功能,可以结合使用AudioRecordAudioTrack,具体步骤如下: 1.创建AudioRecord对象,设置录音参数,如采样率、声道数和采样位深等。可以使用MediaRecorder.AudioSource.MIC设置音频输入源。 2.创建一个字节数组用于存储录制的声音,然后开始录制,不断将音频数据写入字节数组。 3.创建AudioTrack对象,设置播放参数,如采样率、声道数和采样位深等。使用MODE_STREAM模式。 4.将录制的数据写入AudioTrack中播放。 以下是代码示例: //录音 int frequency = 16000;//采样率 int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;//单声道 int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;//采样位深 int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize); byte[] recordBuffer = new byte[bufferSize]; audioRecord.startRecording(); while (isRecording) { int readSize = audioRecord.read(recordBuffer, 0, bufferSize); //处理录制音频数据 } audioRecord.stop(); audioRecord.release(); //播放 AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, bufferSize, AudioTrack.MODE_STREAM); audioTrack.play(); audioTrack.write(recordBuffer, 0, recordBuffer.length); audioTrack.stop(); audioTrack.release();
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShaderJoy

您的打赏是我继续写博客的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值