kotlin音视频系列之AudioRecord

1,388 阅读4分钟
AudioRecord类管理Java应用程序的音频资源,以记录来自平台音频输入硬件的音频。这是通过“拉”(读取)来自AudioRecord对象的数据来实现的。该应用程序负责轮询使用以下三种方法之一AudioRecord对象在时间: read(byte[], int, int)read(short[], int, int)read(java.nio.ByteBuffer, int)。使用哪种方法的选择将基于对AudioRecord用户最方便的音频数据存储格式。
创建后,AudioRecord对象将初始化其关联的音频缓冲区,它将用新的音频数据填充。在构造期间指定的此缓冲区的大小确定AudioRecord在尚未读取的“超速运行”数据之前可以记录多长时间。应从音频硬件读取数据,其大小应小于总记录缓冲区的大小。
录音的完整过程分为以下部分:
1、调用AudioRecord类中的getMinBufferSize获取到最小的音频缓冲区。
2、初始化AudioRecord类,配置一些基本参数
3、初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小。
4、开始录音
5、将录音写入数据流
6、关闭录音。
// 返回成功创建音频记录所需的最小缓冲区大小,以字节为单位。
//请注意,此大小不能保证在负载和更高的值下平稳录制
//应根据录音实例的预期频率选择
---getMinBufferSize
//@param sampleRateInHz以赫兹表示的采样率。(采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。)
//@param channelConfig描述音频通道的配置。(声道数。CHANNEL_IN_MONO 、 CHANNEL_IN_STEREO等等. AudioFormat里面可以查看到所有支持的。其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。)
//@param audio format表示音频数据的格式。音频数据的采样精度,这里设置为AudioFormat.ENCODING_16BIT;也是通过AudioFormat去查看,还是有很多种,这里不一一介绍了。
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
recordBufSize=AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)

---AudioRecord
//audioSource 定义音频源。麦克风音频源,除了这个还有其他的。
public static final int MIC = 1;
//bufferSizeInBytes 写入音频数据的缓冲区的总大小(字节),需大于等于getMinBufferSize,否则将导致初始化失败。
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)
audioRecord= AudioRecord(MediaRecorder.AudioSource.MIC,44100,AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,recordBufSize)
---整个过程代码

private fun createAudioRecord() {
    //audioRecord能接受的最小的buffer大小,
    //sampleRateInHz:默认采样率,单位Hz,这里设置为44100,44100Hz是当前唯一能保证在所有设备上工作的采样率;
    //channelConfig: 描述音频声道设置,这里设置为AudioFormat.CHANNEL_CONFIGURATION_MONO,CHANNEL_CONFIGURATION_MONO保证能在所有设备上工作;
    //audioFormat:音频数据的采样精度,这里设置为AudioFormat.ENCODING_16BIT;
    recordBufSize=AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)
    print("recordBufSize-->$recordBufSize")
    audioRecord= AudioRecord(MediaRecorder.AudioSource.MIC,44100,AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,recordBufSize)
    audioRecord?.startRecording()
    isRecording = true
    var os:FileOutputStream?=null
    val file = File(baseContext.externalCacheDir?.absolutePath, "AudioRecord/test.pcm")
    Log.e("---", "filename"+file.absolutePath)

    if (!file.mkdirs()) {
        Log.e("---", "Directory not created")
    }
    if (file.exists()) {
        file.delete()
    }
    var read: Int?=null
    var data=ByteArray(recordBufSize)
    try {
        os = FileOutputStream(file)
    } catch (e:Exception) {
        e.printStackTrace()
    }
    if (null != os) {
        while (isRecording) {
            read = audioRecord?.read(data, 0, recordBufSize)
            // 如果读取音频数据没有出现错误,就将数据写入到文件
            if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                try {
                    os.write(data)
                } catch (e:IOException) {
                    e.printStackTrace()
                }
            }
        }
        try {
            os.close()
        } catch (e:IOException) {
            e.printStackTrace()
        }
    }

}

而以上生成的文件,我们是播放不了的,还只是原始的音频数据,需要加文件头,否则播放器不晓得是什么内容。

/**
 * pcm文件转wav文件
 *
 * @param inFilename 源文件路径
 * @param outFilename 目标文件路径
 */
fun pcmToWav(inFilename: String, outFilename: String) {
    val `in`: FileInputStream
    val out: FileOutputStream
    val totalAudioLen: Long
    val totalDataLen: Long
    val longSampleRate = mSampleRate.toLong()
    val channels = if (mChannel == AudioFormat.CHANNEL_IN_MONO) 1 else 2
    val byteRate = (16 * mSampleRate * channels / 8).toLong()
    val data = ByteArray(mBufferSize)
    try {
        `in` = FileInputStream(inFilename)
        out = FileOutputStream(outFilename)
        totalAudioLen = `in`.channel.size()
        totalDataLen = totalAudioLen + 36

        writeWaveFileHeader(
            out, totalAudioLen, totalDataLen,
            longSampleRate, channels, byteRate
        )
        while (`in`.read(data) !== -1) {
            out.write(data)
        }
        `in`.close()
        out.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }

}


/**
 * 加入wav文件头
 */
@Throws(IOException::class)
private fun writeWaveFileHeader(
    out: FileOutputStream, totalAudioLen: Long,
    totalDataLen: Long, longSampleRate: Long, channels: Int, byteRate: Long
) {
    val header = 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()
    out.write(header, 0, 44)
}

以上就是一个简单的录音过程,更多的技术细节,需要自己去实践操作。