美文网首页
Android录音实时处理以及lameMP3的使用

Android录音实时处理以及lameMP3的使用

作者: 小强开学前 | 来源:发表于2020-05-18 17:37 被阅读0次

准备==>初始化==>要点==>具体代码
原理:
AudioRecord开启后==>
开启线程 A: 监听其按照AudioFormatSampleRate定时返回的数据(一个数组),拿到后存入缓冲队列;
开启线程B:监听缓冲队列,有数据就取出并调用用lame处理为mp3格式然后写入文件。
AudioRecord关闭==>
处理缓冲队列剩余数据;
lame写入mp3头文件(给播放器识别用,比如按什么码率、声道播放)

准备

MediaRecorder是封装好的播放器,一些录音、编码、压缩工作都已经实现,虽然使用方便但是支持格式少,使用受限。

AudioRecord输出纯字节,可以手动设置audioFormatsampleRate等参数,可定制性强。

毫无悬念,选择AudioRecord

初始化变量

// 声音来源
private val audioSource: Int = MediaRecorder.AudioSource.MIC
// 采样率 1s采集多少次声音
private val sampleRate: Int = 44100
// 单声道
private val channel: Int = AudioFormat.CHANNEL_IN_MONO
// 上面是单声道所以是1
private val channelCount = 1
// 编码格式,每次采样的值的大小
private val audioFormat: Int = ENCODING_PCM_16BIT
// 每台机器硬件不同,Android提供方法确保buffer安全
val minBufferSize: Int = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat)
// 待lame转mp3的数据缓冲区
val linkedBufferQueue = LinkedBlockingQueue<ShortArray>()
// lame一次处理的数据
lateinit var mp3Buffer: ByteArray
// 录音机
var recorder: AudioRecord? = null

初始化

// 初始化recorder
recorder = AudioRecord(audioSource, sampleRate, channel, audioFormat, minBufferSize)
// 初始化lame,《==》中音质
init(sampleRate, channelCount, audioFormat, 5)
// lame头文件有这个大小的相关定义,按他说的做
mp3Buffer = ByteArray((7200 + 1.25 * minBufferSize * 2).toInt())

AudioRecord初始化是Android内置的方法
lame的初始化需要调用JNI的方法


image.png

要点

1. LameMp3的JNI导入

这方面不太熟,照理讲应该编译成so再导入的,后面再看看。

  • 下载lame
  • 复制 lame-x.xx.x 包下的libmp3lame 目录下的所有 .c和.h文件和 include目录下的lame.h到include文件夹下,其余删了。
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# 设定所要求能运行的最低版本
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
      native-lib
      # Sets the library as a shared library.
      SHARED
      # Provides a relative path to your source file(s).
      native-lib.cpp

      include/libmp3lame/bitstream.c
      include/libmp3lame/encoder.c
      include/libmp3lame/fft.c
      include/libmp3lame/gain_analysis.c
      include/libmp3lame/id3tag.c
      include/libmp3lame/lame.c
      include/libmp3lame/mpglib_interface.c
      include/libmp3lame/newmdct.c
      include/libmp3lame/presets.c
      include/libmp3lame/psymodel.c
      include/libmp3lame/quantize.c
      include/libmp3lame/quantize_pvt.c
      include/libmp3lame/reservoir.c
      include/libmp3lame/set_get.c
      include/libmp3lame/tables.c
      include/libmp3lame/takehiro.c
      include/libmp3lame/util.c
      include/libmp3lame/vbrquantize.c
      include/libmp3lame/VbrTag.c
      include/libmp3lame/version.c
      )
target_link_libraries( 
      native-lib
)
  • native-lib.cpp
static lame_global_flags *lame = NULL;
lame初始化 mp3编码 写入mp3头文件及释放

2. 权限获取

烂安卓,各个厂商不同版本返回值不一,5.0以下就更麻烦,所以:

  • 初始化前checkSelfPermission判断是否有权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
    // TODO
}
  • 没有权限暴力请求

为什么这么做参考这篇文章

try {
  ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), Constants.requestAudioCode)
} catch (e: Exception) {
   showRequestSettingAlertDialog()
}
  • 录音开始后检测是否有异常,排除部分机型
// 开始录音校验(返回值是我做的封装,不必理会)
try {
    recorder?.startRecording()
} catch (e: Exception) {
    e.printStackTrace()
    // 没有权限
    recorder?.release()
    recorder == null
    return true
}
  • 上一步后再判断录音状态(返回值是我做的封装,不必理会)
// 记得是recordingState不是state
if (recorder?.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
    return false
}

3. 文件写入

        /**
         * 确认操作《==》PCM转mp3,重命名
         */
        fun doSaveMp3() {
            // 歌曲录制完毕,标记下次需要从头开始
//            startFromTheHead = true
            val fileOutputStream = FileOutputStream(tempFile!!,true)
            // 没有退出activity
            while (recorder != null && linkedBufferQueue.isNotEmpty()) {
                // 还有数据继续写入
                linkedBufferQueue.poll()?.apply {
                    val mp3Read = encoder(this, mp3Buffer, size)
                    if (mp3Read >= 0) fileOutputStream.write(mp3Buffer, 0, mp3Read)
                }
            }
            // 写入剩余数据
            if (recorder != null) {
                val flushRead = flush(mp3Buffer)
                if (flushRead > 0) {
                    fileOutputStream.write(mp3Buffer, 0, flushRead)
                }
            }
            fileOutputStream.close()


            if (recorder != null) {
                // 写入tag
                writeTag(tempFile!!.absolutePath)

                // 重命名
                tempFile?.apply {
                    // rename
                    val finalName = name.substring(0, name.length - 5).plus(".mp3")
                    val temp = File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), finalName)
                    renameTo(temp)
                }
                MuseUtil.toast(R.string.upload_succeed)
            } else {
                tempFile?.delete()
            }
            tempFile = null
        }
 fun cancelUpload() {
    // 删除文件
   tempFile?.delete()
   linkedBufferQueue.clear()
}
 private fun saveMp3() {
            Thread {
                val pcmBuffer = ShortArray(minBufferSize)
                while (recorder?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
                    val pcmRead = recorder?.read(pcmBuffer, 0, minBufferSize)
                    if (pcmRead != AudioRecord.ERROR_INVALID_OPERATION) {
                        linkedBufferQueue.offer(pcmBuffer.copyOfRange(0, pcmRead!!))
                    }
                }
            }.start()
            Thread {
                val fileOutputStream = FileOutputStream(tempFile!!,!startFromTheHead)
                // 退出activity
                while (recorder != null && recorder!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
                    // 还有数据继续写入
                    if (linkedBufferQueue.isNotEmpty()) {
                        linkedBufferQueue.poll()?.apply {
                            val mp3Read = encoder(this, mp3Buffer, size)
                            if (mp3Read >= 0)  fileOutputStream.write(mp3Buffer, 0, mp3Read)
                        }
                        // 没有数据但是还在录音,不退出
                    }
                }
                fileOutputStream.close()
            }.start()
        }

具体代码

寻找合适的地方中...

相关文章

网友评论

      本文标题:Android录音实时处理以及lameMP3的使用

      本文链接:https://www.haomeiwen.com/subject/ljnuohtx.html