【Android音视频学习之路(一)】如何在 Android 平台绘制一张图片
AudioRecord API
AudioRecord是Android系统提供的用于实现录音的功能类,主要功能就是让各种java应用来管理音视频资源,通过此类可以录制相关硬件所收集的声音。
通过事前设定好的声音存储格式,然后读取AudioRecord对象的声音数据来完成操作。
开始录音的时候,AudioRecord需要初始化一个相关联的声音buffer, 这个buffer 主要是用来保存新的声音数据。这个buffer的大小,我们可以在对象构造期间去指定。它表明一个 AudioRecord对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数 据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化 buffer容量的数据。
graph TD
构造AudioRecord对象,最小录音缓存存buffer大小通过getMinBufferSize方法得到 --> 初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小 --> 开始录音 --> 创建数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中数据导入数据流 --> 关闭数据流 --> 停止录音
使用 AudioRecord 实现录音,并生成wav
创建一个AudioRecord对象
1.首先要声明一些全局的变量参数:
private var audioRecord: AudioRecord? = null
private var recordBufSize: Int = 0
private val frequency = 44100
private val channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO
private val encodingBitRate = AudioFormat.ENCODING_PCM_16BIT
private var isRecording = false
private var audioFilePath = ""
private var recordingThread: Job? = null //录制音频的协程
函数原型:
public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)作用:
返回成功创建AudioRecord对象所需要的最小缓冲区大小
参数:
sampleRateInHz: 默认采样率,单位Hz,这里设置为44100,44100Hz是当前唯一能保证在所有设备上工作的采样率;
channelConfig: 描述音频声道设置,这里设置为AudioFormat.CHANNEL_CONFIGURATION_MONO,CHANNEL_CONFIGURATION_MONO保证能在所有设备上工作;
audioFormat: 音频数据的采样精度,这里设置为AudioFormat.ENCODING_16BIT;
返回值:
返回成功创建AudioRecord对象所需要的最小缓冲区大小。 注意:这个大小并不保证在负荷下的流畅录制,应根据预期的频率来选择更高的值,AudioRecord实例在推送新数据时使用此值
如果硬件不支持录制参数,或输入了一个无效的参数,则返回ERROR_BAD_VALUE(-2),如果硬件查询到输出属性没有实现,或最小缓冲区用byte表示,则返回ERROR(-1)
函数原型:
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes) throws IllegalArgumentException参数:
audioSource: 录制源,这里设置MediaRecorder.AudioSource.MIC,其他请见MediaRecorder.AudioSource录制源定义,比如MediaRecorder.AudioSource.FM_TUNER等;
sampleRateInHz: 默认采样率,单位Hz,这里设置为44100,44100Hz是当前唯一能保证在所有设备上工作的采样率;
channelConfig: 描述音频声道设置,这里设置为AudioFormat.CHANNEL_CONFIGURATION_MONO,CHANNEL_CONFIGURATION_MONO保证能在所有设备上工作;
audioFormat: 音频数据保证支持此格式,这里设置为AudioFormat.ENCODING_16BIT;
bufferSizeInBytes: 在录制过程中,音频数据写入缓冲区的总数(字节),即getMinVufferSize()获取到的值。
异常:
当参数设置不正确或不支持的参数时,将会抛出IllegalArgumentException
2.获取buffer的大小并创建AudioRecord:
recordBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, encodingBitRate)
audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration,
encodingBitRate,recordBufSize)
3.初始化一个buffer
val buffer = ByteArray(recordBufSize)
4.开始录音
audioRecord.startRecording()
isRecording = true
5.创建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中数据导入数据流
audioFilePath = Environment.getExternalStorageDirectory().absolutePath + File.separator + "test.wav"
val audioFile = File(audioFilePath)
if (audioFile.exists()) audioFile.delete()
val fileOutputStream = FileOutputStream(audioFile)
fileOutputStream.use {
while (isRecording) {
audioRecord.read(buffer, 0, recordBufSize)
it.write(buffer)
}
}
6.关闭数据流
修改标志位:isRecording 为false,上面的while循环就自动停止了,数据流也就停止流动了,Stream 也就被关闭了。
isRecording = false;
7.停止录音
停止录音之后,注意要释放资源。
isRecording = false
audioRecord?.stop()
audioRecord?.release()
audioRecord = null
recordingThread?.cancel()
注:权限需求:WRITE_EXTERNAL_STORAGE、RECORD_AUDIO
到现在基本的录音的流程就介绍完了。但是这时候,有人就提出问题来了:
1. 我按照流程,把音频数据都输出到文件里面了,停止录音后,打开此文件,发现不能播放,到底是为什么呢?
答:按照流程走完了,数据是进去了,但是现在的文件里面的内容仅仅是最原始的音频数据,术语称为 raw(中文解释是“原材料”或“未经处理的东西”),这时候,你让播放器去打开,它既不知道保存的格 式是什么,又不知道如何进行解码操作。当然播放不了。 2. 那如何才能在播放器中播放我录制的内容呢?
答: 在文件的数据开头加入WAVE HEAD数据即可,也就是文件头。只有加上文件头部的数据,播放器 才能正确的知道里面的内容到底是什么,进而能够正常的解析并播放里面的内容。具体的头文件的描 述,在Play a WAV file on an AudioTrack里面可以进行了解。
添加WAVE文件头的代码如下:
/**
* @param bufferSize 缓存的音频大小
* @param sampleRate 采样率
* @param channel 声道数
*/
class PcmToWavUtil(
private val bufferSize: Int,
private val sampleRate: Long,
private val channel: Int
) {
/**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
fun pcmToWav(inFilename: String,outFilename: String) {
val inputStream = FileInputStream(inFilename)
val outputStream = FileOutputStream(outFilename)
val totalAudioLen = inputStream.channel.size()
val totalDataLen = totalAudioLen + 36
val longSampleRate: Long = sampleRate
val channels = if (channel == AudioFormat.CHANNEL_IN_MONO) 1 else 2
val byteRate: Long = 16 * sampleRate * channels / 8
val data: ByteArray = ByteArray(bufferSize)
writeWaveFileHeader(outputStream, totalAudioLen, totalDataLen ,longSampleRate, channels, byteRate)
inputStream.use { input ->
outputStream.use { output ->
while (input.read(data) != -1) {
output.write(data)
}
}
}
}
/**
* 加入wav文件头
*/
@Throws(IOException::class)
private fun writeWaveFileHeader(
outputStream: FileOutputStream,
totalAudioLen: Long,
totalDataLen: Long,
longSampleRate: Long,
channels: Int,
byteRate: Long
) {
val header: ByteArray = ByteArray(44)
// RIFF/WAVE header
header[0] = 'R'.toByte()
header[1] = 'I'.toByte()
header[2] = 'F'.toByte()
header[3] = 'F'.toByte()
header[4] = ((totalDataLen and 0xff).toByte())
header[5] = (totalDataLen shr 8 and 0xff).toByte()
header[6] = (totalDataLen shr 16 and 0xff).toByte()
header[7] = (totalDataLen shr 24 and 0xff).toByte()
//WAVE
header[8] = 'W'.toByte()
header[9] = 'A'.toByte()
header[10] = 'V'.toByte()
header[11] = 'E'.toByte()
// 'fmt ' chunk
header[12] = 'f'.toByte()
header[13] = 'm'.toByte()
header[14] = 't'.toByte()
header[15] = ' '.toByte()
// 4 bytes: size of 'fmt ' chunk
header[16] = 16
header[17] = 0
header[18] = 0
header[19] = 0
// format = 1
header[20] = 1
header[21] = 0
header[22] = channels.toByte()
header[23] = 0
header[24] = ((longSampleRate and 0xff).toByte())
header[25] = ((longSampleRate shr 8 and 0xff).toByte())
header[26] = ((longSampleRate shr 16 and 0xff).toByte())
header[27] = ((longSampleRate shr 24 and 0xff).toByte())
header[28] = (byteRate and 0xff).toByte()
header[29] = (byteRate shr 8 and 0xff).toByte()
header[30] = (byteRate shr 16 and 0xff).toByte()
header[31] = (byteRate shr 24 and 0xff).toByte()
// block align
header[32] = (2 * 16 / 8).toByte()
header[33] = 0
// bits per sample
header[34] = 16
header[35] = 0
//data
header[36] = 'd'.toByte()
header[37] = 'a'.toByte()
header[38] = 't'.toByte()
header[39] = 'a'.toByte()
header[40] = (totalAudioLen and 0xff).toByte()
header[41] = (totalAudioLen shr 8 and 0xff).toByte()
header[42] = (totalAudioLen shr 16 and 0xff).toByte()
header[43] = (totalAudioLen shr 24 and 0xff).toByte()
outputStream.write(header, 0, 44)
}
}
后言
Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加 上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文 件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。如果想简 单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder,而如果需要对音频做进一步的算法 处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord,其实 MediaRecorder 底层也是调用了 AudioRecord 与 Android Framework 层的 AudioFlinger 进行交互 的。直播中实时采集音频自然是要用 AudioRecord 了。