音频-mp3 -> pcm 解码

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
            )
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值