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()
}
相关推荐
关键帧Keyframe2 天前
音视频面试题集锦第 7 期
音视频开发·视频编码·客户端
关键帧Keyframe2 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端
蚝油菜花7 天前
MimicTalk:字节跳动和浙江大学联合推出 15 分钟生成 3D 说话人脸视频的生成模型
人工智能·开源·音视频开发
音视频牛哥9 天前
Android平台RTSP|RTMP播放器高效率如何回调YUV或RGB数据?
音视频开发·视频编码·直播
<Sunny>11 天前
MPP音视频总结
音视频开发·1024程序员节·海思mpp
GoFly开发者11 天前
GoFly快速开发框架已集成了RTSP流媒体服务器(直播、录播)代码插件-开发者集成流媒体服务器更加方便
go·音视频开发
音视频牛哥1 个月前
如何设计开发RTSP直播播放器?
音视频开发·视频编码·直播
dvlinker1 个月前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
音视频牛哥1 个月前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播