Android 音频采集可以使用 AudioRecord 或 MediaRecorder,MediaRecorder 适合简单地录制音频并将其保存为文件或流媒体的场景,它提供了更高层次的封装和便捷的录制功能,而 AudioRecord 提供了更底层的音频录制控制,它录制的是原始 pcm 数据,更适合对音频数据进行实时处理和分析的场景,因此,这里采用 AudioRecord 进行音频采集。
因为需要录音,录音权限是属于危险权限,所以使用前需要先动态申请一下权限,这里不再赘述。
xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
配置参数
我们先来看下 AudioRecord 的构造
java
public AudioRecord(int audioSource,
int sampleRateInHz,
int channelConfig,
int audioFormat,
int bufferSizeInBytes)
- audioSource:音频数据的来源,常见的有 MediaRecorder.AudioSource.MIC(麦克风),MediaRecorder.AudioSource.VOICE_CALL(语音通话)等。
- sampleRateInHz:采样率,采样率以赫兹(Hz)为单位,常见的有 16000,44100 等。
- channelConfig:声道配置,决定了音频数据的通道数,常见的有 AudioFormat.CHANNEL_IN_MONO(单声道),AudioFormat.CHANNEL_IN_STEREO(立体声)。
- audioFormat:音频数据的编码格式,用于描述每个样本的位数和编码方式。常见的有 AudioFormat.ENCODING_PCM_8BIT,AudioFormat.ENCODING_PCM_16BIT 等。
- bufferSizeInBytes:缓冲区的大小,以字节为单位,用于接收音频数据的缓冲区最小尺寸。可以使用 getMinBufferSize 方法来获取适当的缓冲区大小。
计算最小缓冲区大小,需要使用正确的缓冲区大小来初始化 AudioRecord 对象。
kotlin
val bufferSizeInBytes = AudioRecord.getMinBufferSize(
44100,
AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT
)
kotlin
val audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
44100,
AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSizeInBytes
)
音频录制
AudioRecord 构造好之后就可以进行录制了,简单地说,就是通过 AudioRecord 的 read 方法,将数据写到 ByteArray 里面去,然后将其写入文件中保存起来就是了。
kotlin
// 文件保存路径
private val audioFile: File by lazy { File(getExternalFilesDir(null), "audio_record.pcm") }
// 是否正在录制,用于控制开始/停止录制
private var isRecording = false
录制方法如下
kotlin
@Throws(IOException::class)
private fun startRecord() {
// 使用前先检查一下录音权限
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 1)
return
}
// 初始化
val bufferSizeInBytes = AudioRecord.getMinBufferSize(
44100,
AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT
)
val audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
44100,
AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSizeInBytes
)
if (audioFile.exists()) {
audioFile.delete()
}
audioFile.createNewFile()
val outputStream = FileOutputStream(audioFile)
//开始录制
audioRecord.startRecording()
val buffer = ByteArray(bufferSizeInBytes)
isRecording = true
while (isRecording) {
AudioRecord.ERROR_INVALID_OPERATION
val bytesRead = audioRecord.read(buffer, 0, buffer.size)
outputStream.write(buffer, 0, bytesRead)
}
//结束录制
audioRecord.stop()
audioRecord.release()
outputStream.close()
}
由上可知,我们开启了一个循环,不断将数据读取到一个 buffer 里面,然后再将其写入文件中。所以我们就可以根据这个 isRecording 变量去控制是否停止录制,如果要停止录制的话,就直接将 isRecording 置为 false 即可。
kotlin
// 停止录制
isRecording = false
需要注意的是,不要把该录制操作放在主线程,为了避免主线程阻塞,应该将音频录制操作放在独立的线程上进行。例如,我们可以开个协程去执行这个方法。
kotlin
lifecycleScope.launch(Dispatchers.IO) {
try {
startRecord()
} catch (ex: IOException) {
ex.printStackTrace()
}
}
音频播放
上面所录制的文件,是 pcm 文件,是不能被播放器直接播放的,如果想播放 pcm 文件,可以使用 AudioTrack。
AudioTrack 提供了两种模式:流模式和静态模式,它们在音频数据传输和播放方式上有所不同。
流模式
在流模式下,音频数据会按照一定的缓冲区大小以流的形式传输,可以通过 write 方法将音频数据写入 AudioTrack 的缓冲区中,AudioTrack 会自动从缓冲区中读取音频数据并播放。这种模式适用于实时生成或接收音频数据的场景,例如音乐播放器,语音通话等,也可以使用 setBufferSizeInFrames 方法设置缓冲区大小,并使用 play 方法开始播放音频数据。
kotlin
@Throws(Exception::class)
private fun playWithFlow() {
// 设置音频信息属性
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
val format = AudioFormat.Builder().setSampleRate(44100)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_STEREO)
.build()
val bufferSize = AudioTrack.getMinBufferSize(
44100,
AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT
);
val audioTrack = AudioTrack(
attributes,
format,
bufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE
)
// 等待播放
audioTrack.play()
if (audioFile.exists()) {
val inputStream = FileInputStream(audioFile)
val buffer = ByteArray(bufferSize)
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
audioTrack.write(buffer, 0, length)
}
audioTrack.stop()
audioTrack.release()
inputStream.close()
}
}
静态模式
在静态模式下,需要将完整的音频数据一次性写入到 AudioTrack 中,并在播放之前先调用 play 方法。这种模式适用于已经提前准备好的音频文件或音频片段的播放,通常是较小的音频数据,使用 write 方法将所有音频数据一次性写入 AudioTrack,然后使用 play 方法开始播放音频。
kotlin
@Throws(Exception::class)
private fun playWithStatic() {
val inputStream = FileInputStream(audioFile)
val byteArrayOutputStream = ByteArrayOutputStream()
var length: Int
val buffer = ByteArray(1024)
while (inputStream.read(buffer).also { length = it } > 0) {
// 将数据存入 ByteArrayOutputStream 中
byteArrayOutputStream.write(buffer, 0, length)
}
// 获取音频数据
val audioByteData = byteArrayOutputStream.toByteArray()
// 设置音频信息属性
val attributes = AudioAttributes.Builder()
// 音频的用途
.setUsage(AudioAttributes.USAGE_MEDIA)
// 音频内容类型
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
val format = AudioFormat.Builder().setSampleRate(44100)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_STEREO)
.build()
// 静态模式
val audioTrack = AudioTrack(
attributes, format, audioByteData.size, AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE
)
audioTrack.write(audioByteData, 0, audioByteData.size)
audioTrack.play()
}