准备==>初始化==>要点==>具体代码
原理:
AudioRecord开启后==>
开启线程A
: 监听其按照AudioFormat
和SampleRate
定时返回的数据(一个数组),拿到后存入缓冲队列;
开启线程B
:监听缓冲队列,有数据就取出并调用用lame处理为mp3格式然后写入文件。
AudioRecord关闭==>
处理缓冲队列剩余数据;
lame写入mp3头文件(给播放器识别用,比如按什么码率、声道播放)
准备
MediaRecorder
是封装好的播放器,一些录音、编码、压缩工作都已经实现,虽然使用方便但是支持格式少,使用受限。
AudioRecord
输出纯字节,可以手动设置audioFormat
、sampleRate
等参数,可定制性强。
毫无悬念,选择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的方法
![](https://img.haomeiwen.com/i14730476/c6f2ad10c907074b.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;
![](https://img.haomeiwen.com/i14730476/d02159624ee5780b.png)
![](https://img.haomeiwen.com/i14730476/cf526b911ff69d7d.png)
![](https://img.haomeiwen.com/i14730476/ba25fd17c7b29733.png)
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()
}
具体代码
寻找合适的地方中...
网友评论