前言
之前我们使用了MediaRecorder录制了音频和视频,虽然API使用简便,但是欠缺灵活,例如直播中的混音,变声等等,有些我们需要边录制边处理,MediaRecorder已经满足不了这些更高的需求,这个时候就需要使用AudioRecord。
正文
使用AudioRecord录制的是pcm原始音频,具体的概念这里就不多说了,如果你需要MP3,3gp可以自行转换。也就是说录制之后的文件基本上不能使用市面上的播放器直接播放,在安全性有一些小小的提升。
创建AudioRecord
首先了解一下AudioRecord的常用构造方法:
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes)
audioSource :录音的来源
sampleRateInHz:采样率
channelConfig:配置的录音的频道,常用的有立体声,左声道,右声道
audioFormat:录音的编码格式,今天我们使用ENCODING_PCM_8BIT和ENCODING_PCM_16BIT
bufferSizeInBytes:每个数据的最小的大小,用于保存和读取
其中有两个参数:audioFormat和bufferSizeInBytes需要再说几句:
audioFormat:
ENCODING_PCM_8BIT:每一个采样使用8位保存,也就是Byte
ENCODING_PCM_16BIT:每一个采样使用16位保存,也就是short
所以这两种编码的格式,保存和读取会有区别。之后的案例会有说明。
·
bufferSizeInBytes:每一个采样的大小,AudioRecord提供了方法来计算
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
AudioRecord录制
已经创建好了AudioRecord,接下来可以开始录制了,千万不要忘记权限的申请。
inner class RecordTask : AsyncTask<Unit, Unit, Unit>() {
...
override fun doInBackground(vararg params: Unit) {
// 开始录音
audioRecord.startRecording()
// 通过io流,把录制的音频内容保存到文件中
val dataInputStream = DataOutputStream(BufferedOutputStream(FileOutputStream(filePath)))
// 16位需要使用short来保存
if (ENCODER == AudioFormat.ENCODING_PCM_16BIT) {
saveBy16Bit(dataInputStream)
}
// 8位使用byte
else if (ENCODER == AudioFormat.ENCODING_PCM_8BIT) {
saveBy8Bit(dataInputStream)
}
dataInputStream.flush()
dataInputStream.close()
}
/**
* 保存16位的数据需要使用short
*/
private fun saveBy16Bit(dataInputStream: DataOutputStream) {
val byteArray = ShortArray(getMinBufferSize())
while (isRecording) {
val result = audioRecord.read(byteArray, 0, byteArray.size)
for (i in 0 until result) {
dataInputStream.writeShort(byteArray[i].toInt())
}
}
}
/**
* 保存8位的数据需要使用short
*/
private fun saveBy8Bit(dataInputStream: DataOutputStream) {
val byteArray = ByteArray(getMinBufferSize())
while (isRecording) {
audioRecord.read(byteArray, 0, byteArray.size)
dataInputStream.write(
byteArray,
0,
byteArray.size
)
}
}
fun stop() {
audioRecord.stop()
audioRecord.release()
}
}
上面是非常简单的IO读写操作,之前也提到保存16位和8位是有区别的,如果强行用8位保存16位的数据,肯定是要出问题的。
播放PCM
再刚才的录制中,我们没有做格式转换,使用播放器肯定播放不了,所以需要我们自己完成播放PCM的功能,核心肯定是通过IO流再读出来,不过我们需要使用AudioTrack。
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)
AudioAttributes:音轨相关的属性
AudioFormat:录制的音频的格式,与录制时相同
bufferSizeInBytes:每一个采样的最小大小,与录制时相同
mode:播放的模式。我们使用的时流MODE_STREAM
sessionId:此次播放的id,可以使用 AudioManager.AUDIO_SESSION_ID_GENERATE和 AudioManager.generateAudioSessionId()
inner class PlayTaks : AsyncTask<Unit, Unit, Unit>() {
override fun doInBackground(vararg params: Unit?) {
// 创建音轨
val audioTrack = AudioTrack(
AudioAttributes.Builder()
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
.build(),
AudioFormat.Builder()
// 这里会和录制的时候不一样,录制时时CHANNEL_IN_MONO,这里时CHANNEL_OUT_MONO
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.setEncoding(ENCODER)
.setSampleRate(11025)
.build(),
getMinBufferSize(),
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE
)
audioTrack.play()
val dataInputStream = DataInputStream(BufferedInputStream(FileInputStream(filePath)))
// 读取数据也是一样,16位要读取short
if (ENCODER == AudioFormat.ENCODING_PCM_16BIT) {
readBy16Bit(dataInputStream, audioTrack)
}
// 8位可以直接读取byte
else if (ENCODER == AudioFormat.ENCODING_PCM_8BIT) {
readBy8Bit(dataInputStream, audioTrack)
}
// 播放结束,释放资源
dataInputStream.close()
audioTrack.stop()
audioTrack.release()
}
/**
* 读取8位数据
*/
private fun readBy8Bit(dataInputStream: DataInputStream, audioTrack: AudioTrack) {
val byteArray = ByteArray(getMinBufferSize())
while (dataInputStream.available() > 0) {
dataInputStream.read(byteArray, 0, byteArray.size)
audioTrack.write(byteArray, 0, byteArray.size)
}
}
/**
* 读取16位数据
*/
private fun readBy16Bit(dataInputStream: DataInputStream, audioTrack: AudioTrack) {
val byteArray = ShortArray(getMinBufferSize())
while (dataInputStream.available() > 0) {
var i = 0
while (i < byteArray.size) {
byteArray[i] = dataInputStream.readShort()
i++
}
audioTrack.write(byteArray, 0, byteArray.size)
}
}
}
总结
今天我们了解使用AudioRecord录制音频和使用AudioTrack播放音频,下一篇我们了解一下MediaExtractor。