Android 音视频开发第2弹 - AudioRecord 音频采集

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()
}
相关推荐
x007xyz1 个月前
前端纯手工绘制音频波形图
前端·音视频开发·canvas
音视频牛哥1 个月前
Android摄像头采集选Camera1还是Camera2?
音视频开发·视频编码·直播
九酒2 个月前
【harmonyOS NEXT 下的前端开发者】WAV音频编码实现
前端·harmonyos·音视频开发
音视频牛哥2 个月前
结合GB/T28181规范探讨Android平台设备接入模块心跳实现
音视频开发·视频编码·直播
哔哩哔哩技术2 个月前
自研点直播转码核心
音视频开发
音视频牛哥2 个月前
Android平台轻量级RTSP服务模块二次封装版调用说明
音视频开发·视频编码·直播
音视频牛哥2 个月前
Android平台RTSP|RTMP直播播放器技术接入说明
音视频开发·视频编码·直播
山雨楼2 个月前
ExoPlayer架构详解与源码分析(15)——Renderer
android·架构·音视频开发
音视频牛哥2 个月前
Windows平台如何实现多路RTSP|RTMP流合成后录像或转发RTMP服务
音视频开发·视频编码·直播
音视频牛哥2 个月前
GB28181设备接入模块和轻量级RTSP服务有什么区别?
音视频开发·视频编码·直播