object AudioDecodeUtil {
private const val TAG = "AudioDecodeUtil"
private const val KEY_BIT_WIDTH = "bit-width"
/* 编解码超时时间 */
private const val TIME_OUTS = 100L
/*媒体提取器、解码器*/
private var mediaExtractor: MediaExtractor? = null
private var mediaCodec: MediaCodec? = null
/*存放 PCM 文件的路径*/
private var pcmFilePath: String = ""
/*采样率、声道数*/
private var sampleRate: Int = 0
private var channelCount: Int = 0
/*当前解码的缓存信息,里面的有效数据在 offset 和 offset+size 之间*/
private var bufferInfo = MediaCodec.BufferInfo()
/*获取存储输出数据的 ByteBuffer 数组*/
private var outputBuffers = arrayOf<ByteBuffer>()
/*音频文件的采样位数字节数 = 采样位数 / 8*/
private var byteNumber: Int = 0
/*输出音频的媒体格式信息*/
private var outputFormat: MediaFormat? = null
/*当前输入数据的 ByteBuffer 序号,当前输出数据的 ByteBuffer 序号*/
private var inputBufferIndex: Int = 0
private var outputBufferIndex: Int = 0
/*结束输入数据的标志*/
private var decodeInputEnd = false
/**
* 将音乐文件解码
*
* @param musicFileUrl 源文件路径
* @param decodeFileUrl 解码文件路径
*/
fun decodeMusicFile(musicFileUrl: String?, decodeFileUrl: String?): Boolean {
MLog.d(TAG, "decodeMusicFile: musicFileUrl = $musicFileUrl, decodeFileUrl = $decodeFileUrl")
if (musicFileUrl.isNullOrEmpty() || decodeFileUrl.isNullOrEmpty()) {
MLog.d(TAG, "decodeMusicFile: 文件路径为空!!!")
return false
}
mediaExtractor = MediaExtractor()
// 给媒体信息提取器设置源音频文件路径
try {
mediaExtractor?.setDataSource(musicFileUrl)
} catch (ex: Exception) {
ex.printStackTrace()
try {
mediaExtractor?.setDataSource(FileInputStream(musicFileUrl).fd)
} catch (e: Exception) {
MLog.d(TAG, "decodeMusicFile: 设置解码音频文件路径错误!!!")
e.printStackTrace()
return false
}
}
// 获取音频格式轨信息
val mediaFormat = mediaExtractor?.getTrackFormat(0) ?: return false
// 从音频格式轨信息中读取 采样率,声道数,时长,音频文件类型
sampleRate =
if (mediaFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE)) mediaFormat.getInteger(
MediaFormat.KEY_SAMPLE_RATE
) else 44100
channelCount =
if (mediaFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT)) mediaFormat.getInteger(
MediaFormat.KEY_CHANNEL_COUNT
) else 1
val duration =
if (mediaFormat.containsKey(MediaFormat.KEY_DURATION)) mediaFormat.getLong(MediaFormat.KEY_DURATION) else 0
var mime =
if (mediaFormat.containsKey(MediaFormat.KEY_MIME)) mediaFormat.getString(MediaFormat.KEY_MIME) else ""
MLog.d(
TAG,
"decodeMusicFile: mime = $mime, sampleRate = $sampleRate, channelCount = $channelCount, duration = $duration"
)
if (TextUtils.isEmpty(mime) || !mime!!.startsWith("audio/")) {
MLog.d(TAG, "decodeMusicFile: 解码文件不是音频文件 mime = $mime")
return false
}
if ((mime == "audio/ffmpeg")) {
mime = "audio/mpeg"
mediaFormat.setString(MediaFormat.KEY_MIME, mime)
}
if (duration <= 0) {
MLog.d(TAG, "decodeMusicFile: 音频文件 duration = $duration")
return false
}
// 创建一个解码器
try {
mediaCodec = MediaCodec.createDecoderByType((mime))
mediaCodec?.configure(mediaFormat, null, null, 0)
} catch (e: Exception) {
MLog.d(TAG, "decodeMusicFile: 解码器 configure 出错!!!")
return false
}
// 后续解码操作
try {
MLog.d(TAG, "decodeMusicFile: 开始解码...")
// 得到输出 PCM 文件的路径
pcmFilePath = "${
decodeFileUrl.substring(
0,
decodeFileUrl.lastIndexOf(".")
)
}${AudioConstant.SUFFIX_PCM}"
decodeData(mediaCodec, musicFileUrl)
} catch (e: FileNotFoundException) {
MLog.d(TAG, "decodeMusicFile: FileNotFoundException: " + e.message)
}
return true
}
/**
* 解码数据
*
* @param mediaCodec MediaCodec 解码器
*/
private fun decodeData(
mediaCodec: MediaCodec?,
musicFileUrl: String,
) {
if (mediaCodec == null || mediaExtractor == null) {
MLog.d(TAG, "decodeData: mediaExtractor = $mediaExtractor, mediaCodec = $mediaCodec")
return
}
// 开始解码操作
mediaCodec.start()
mediaExtractor?.selectTrack(0)
// 解码操作
coreDecodeData(mediaCodec)
// 释放 mediaCodec 和 mediaExtractor
mediaCodec.stop()
mediaCodec.release()
mediaExtractor?.release()
// 编码结束后删除文件 .temp
MLog.d(TAG, "decodeData: deleteFile = $musicFileUrl")
File(musicFileUrl).delete()
}
/**
* 核心解码操作
*/
private fun coreDecodeData(mediaCodec: MediaCodec) {
// 初始化解码状态,未解析完成
var decodeOutputEnd = false
// 获取输出音频的媒体格式信息
val outputFormat = mediaCodec.outputFormat
byteNumber =
(if (outputFormat.containsKey(KEY_BIT_WIDTH)) outputFormat.getInteger(KEY_BIT_WIDTH) else 0) / 8
MLog.d(TAG, "decodeData: byteNumber = $byteNumber")
// 获取存储输入数据的 ByteBuffer 数组,输出数据的 ByteBuffer 数组
outputBuffers = mediaCodec.outputBuffers
// 获取解码后文件的输出流
val fos = FileOutputStream(File(pcmFilePath))
val bufferedOutputStream = BufferedOutputStream(fos)
// 当前编解码器操作的 输入数据 ByteBuffer 和 输出数据 ByteBuffer,可以从 targetBuffer 中获取解码后的 PCM 数据
var targetBuffer: ByteBuffer
// 开始进入循环解码操作,判断读入源音频数据是否完成,输出解码音频数据是否完成
while (!decodeOutputEnd) {
if (decodeInputEnd) {
break
}
try {
// 操作解码输入数据
decodeInputData()
// 从队列中获取当前解码器处理输出数据的 ByteBuffer 序号
outputBufferIndex =
AudioDecodeUtil.mediaCodec?.dequeueOutputBuffer(bufferInfo, TIME_OUTS) ?: 0
if (outputBufferIndex < 0) {
// 操作解码输出数据
decodeOutputData()
continue
}
// 取得当前解码器处理输出数据的 ByteBuffer
targetBuffer = outputBuffers[outputBufferIndex]
val sourceByteArray = ByteArray(bufferInfo.size)
// 将解码后的 targetBuffer 中的数据复制到 sourceByteArray 中
targetBuffer[sourceByteArray]
targetBuffer.clear()
// 释放当前的输出缓存
mediaCodec.releaseOutputBuffer(outputBufferIndex, false)
// 判断当前是否解码数据全部结束了
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
decodeOutputEnd = true
}
// sourceByteArray 就是最终解码后的采样数据
// 接下来可以对这些数据进行采样位数,声道的转换,但这是可选的,默认是和源音频一样的声道和采样位数
if (sourceByteArray.isNotEmpty() && bufferedOutputStream != null) {
// 将解码后的 PCM 数据写入到 PCM 文件
try {
MLog.d(
TAG,
"decodeData: 将 PCM 数据写入 PCM 文件: size = ${sourceByteArray.size}, decodeInputEnd = $decodeInputEnd"
)
bufferedOutputStream.write(sourceByteArray)
convertPcm2Wav()
} catch (e: Exception) {
MLog.d(TAG, "decodeData: 输出解压音频数据异常 error = $e")
}
}
} catch (e: java.lang.Exception) {
MLog.d(TAG, "decodeData异常$e")
}
}
try {
bufferedOutputStream?.close()
} catch (e: IOException) {
MLog.d(TAG, "decodeData: 关闭 bufferedOutputStream 异常 error = $e")
e.printStackTrace()
}
}
/**
* 操作解码输入数据
*/
private fun decodeInputData() {
mediaCodec ?: return
// 获取存储输入数据的 ByteBuffer 数组
val inputBuffers = mediaCodec?.inputBuffers ?: return
// 当前采样的音频时间,比如在当前音频的第 40 秒的时候
var presentationTimeUs = 0L
// 从队列中获取当前解码器处理输入数据的 ByteBuffer 序号
inputBufferIndex = mediaCodec?.dequeueInputBuffer(TIME_OUTS) ?: 0
// 当前编解码器操作的 输入数据 ByteBuffer
val sourceBuffer: ByteBuffer
// 当前读取采样数据的大小
var sampleDataSize: Int
if (inputBufferIndex >= 0) {
// 取得当前解码器处理输入数据的 ByteBuffer
sourceBuffer = inputBuffers[inputBufferIndex]
// 获取当前 ByteBuffer,编解码器读取了多少采样数据
sampleDataSize = mediaExtractor?.readSampleData(sourceBuffer, 0) ?: 0
MLog.d(TAG, "decodeData: sampleDataSize = $sampleDataSize")
// 如果当前读取的采样数据<0,说明已经完成了读取操作
if (sampleDataSize < 0) {
decodeInputEnd = true
MLog.d(TAG, "decodeData: 解码结束!")
sampleDataSize = 0
} else {
presentationTimeUs = mediaExtractor?.sampleTime ?: 0
}
// 然后将当前 ByteBuffer 重新加入到队列中交给编解码器做下一步读取操作
mediaCodec?.queueInputBuffer(
inputBufferIndex,
0,
sampleDataSize,
presentationTimeUs,
if (decodeInputEnd) MediaCodec.BUFFER_FLAG_END_OF_STREAM else 0
)
// 前进到下一段采样数据
if (!decodeInputEnd) {
mediaExtractor?.advance()
}
}
MLog.d(TAG, "decodeData: inputBufferIndex = $inputBufferIndex")
}
/**
* 操作解码输出数据
*/
private fun decodeOutputData() {
mediaCodec ?: return
// 输出 ByteBuffer 序号 < 0,可能是输出缓存变化了,输出格式信息变化了
when (outputBufferIndex) {
MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> {
outputBuffers = mediaCodec!!.outputBuffers
MLog.d(
TAG,
"decodeData: MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED [AudioDecoder]output buffers have changed."
)
}
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
outputFormat = mediaCodec!!.outputFormat
sampleRate =
if (outputFormat!!.containsKey(MediaFormat.KEY_SAMPLE_RATE)) outputFormat!!.getInteger(
MediaFormat.KEY_SAMPLE_RATE
) else sampleRate
channelCount =
if (outputFormat!!.containsKey(MediaFormat.KEY_CHANNEL_COUNT)) outputFormat!!.getInteger(
MediaFormat.KEY_CHANNEL_COUNT
) else channelCount
byteNumber =
((if (outputFormat!!.containsKey(KEY_BIT_WIDTH)) outputFormat!!.getInteger(
KEY_BIT_WIDTH
) else 0) / 8)
MLog.d(
TAG,
"decodeData: sampleRate = $sampleRate, channelCount = $channelCount, byteNumber = $byteNumber"
)
MLog.d(
TAG,
"decodeData: MediaCodec.INFO_OUTPUT_FORMAT_CHANGED [AudioDecoder]output format has changed to ${mediaCodec?.outputFormat}"
)
}
else -> {
// do nothing
}
}
}
/**
* 转码
*/
private fun convertPcm2Wav() {
if (decodeInputEnd) {
MLog.d(TAG, "decodeData: 解码结束")
val wavFilePath = "${
pcmFilePath.substring(
0,
pcmFilePath.lastIndexOf(".")
)
}${AudioConstant.SUFFIX_WAV}"
MLog.d(
TAG,
"decodeData: 解码结束 pcmFilePath = $pcmFilePath, wavFilePath = $wavFilePath"
)
AudioEncodeUtil.convertPcm2Wav(
pcmFilePath,
wavFilePath,
sampleRate,
channelCount,
byteNumber
)
}
}
}
音频-mp3 -> pcm 解码
于 2023-03-06 21:41:47 首次发布