用于播放分段的pcm流,适用于长连接形式多次分段返回pcm流,边返回边播放场景

class PcmPlayer @JvmOverloads constructor(
    private val sampleRateInHz: Int = 16000
) {
    /**
     * AudioTrack
     */
    private var mAudioTrack: AudioTrack? = null

    /**
     * 缓存大小
     */
    private val bufferSize = 2 * AudioTrack.getMinBufferSize(
        sampleRateInHz,
        AudioFormat.CHANNEL_OUT_MONO,
        AudioFormat.ENCODING_PCM_16BIT
    )

    /**
     * mAudioTrackLock
     */
    private val mAudioTrackLock = Any()

    // Lock
    private val mListLock = ReentrantLock()

    // AudioTrack 获取数据 的 Condition
    private val mTakeData = mListLock.newCondition()

    // 接收的数据缓存 LinkedList
    private val mDataBufferList = LinkedList<ByteArray>()

    // 添加数据完毕标识
    @Volatile
    private var mFinishPut = false

    private val mScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    /**
     * 音频焦点处理
     */
    private var audioFocusManager = AudioFocusManager(ApplicationUtil.application)

    /**
     * 播放完毕回调
     */
    private var mListener: PlayAudioListener? = null

    /**
     * 播放器是否在播放
     */
    val isPlaying: Boolean
        get() {
            synchronized(mAudioTrackLock) {
                return if (mAudioTrack == null) {
                    false
                } else {
                    mAudioTrack?.playState == AudioTrack.PLAYSTATE_PLAYING
                }
            }
        }
    val isStop: Boolean
        get() {
            synchronized(mAudioTrackLock) {
                return if (mAudioTrack == null) false else mAudioTrack?.playState == AudioTrack.PLAYSTATE_STOPPED
            }
        }

    /**
     * 开始播放
     */
    @JvmOverloads
    fun startPlay(streamType: Int = AudioManager.STREAM_MUSIC, isAutoPlay: Boolean = true) {
        Logger.i(TAG, "startPlay,isAutoPlay=$isAutoPlay")
        mFinishPut = false
        mAudioTrack = AudioTrack(
            streamType,
            sampleRateInHz,
            AudioFormat.CHANNEL_OUT_MONO,
            AudioFormat.ENCODING_PCM_16BIT,
            bufferSize,
            AudioTrack.MODE_STREAM
        )
        if (isAutoPlay) {
            play()
        }
    }

    /**
     * 清除数据
     */
    fun clearData() {
        mDataBufferList.clear()
    }

    /**
     * 添加数据
     */
    fun put(buffer: ByteArray) {
        try {
            mListLock.lock()
            if (mFinishPut) {
                return
            }
            if (buffer.isEmpty()) return
            val byteArrayList = splitByteArray(buffer, bufferSize)
            mDataBufferList.addAll(byteArrayList)
            mTakeData.signal()
        } finally {
            mListLock.unlock()
        }
    }

    /**
     * 停止播放
     */
    fun stop() {
        Logger.w(TAG, "stop")
        try {
            if (isStop) {
                Logger.d(TAG, "音频播放器-已经停止,不需要停止")
                return
            }
            finishPut()
            synchronized(mAudioTrackLock) {
                mAudioTrack?.stop()
                mListener?.onStop()
                mAudioTrack = null
            }
        } catch (e: Exception) {
            Logger.e(
                TAG,
                "停止播报异常${e.message}",
                PlayerException(e, "播放器停止异常${e.message}")
            )
            mListener?.onPlayError(e.message, e)
        }
    }

    /**
     * 暂停
     */
    fun pause(): Boolean {
        Logger.i(TAG, "pause")
        try {
            synchronized(mAudioTrackLock) {
                if (isPlaying) {
                    mAudioTrack?.pause()
                    mListener?.onPause()
                    return true
                }
            }
        } catch (e: Exception) {
            Logger.e(
                TAG,
                "暂停播报异常${e.message}",
                PlayerException(e, "播放器暂停异常${e.message}")
            )
            mListener?.onPlayError(e.message, e)
        }
        return false
    }

    /**
     * 播放器是否暂停
     */
    val isPause: Boolean
        get() {
            synchronized(mAudioTrackLock) {
                return if (mAudioTrack == null) {
                    false
                } else {
                    mAudioTrack?.playState == AudioTrack.PLAYSTATE_PAUSED
                }
            }
        }

    /**
     * 播放器继续
     */
    fun resume(): Boolean {
        Logger.i(TAG, "resume ")
        synchronized(mAudioTrackLock) {
            if (!isPause) {
                // 回调给外面,不然外面状态会乱,导致一直在onPause和onResume之间来回切换
                mListener?.onStop()
                return false
            }
            play()
            mListener?.onResume()
            return true
        }
    }

    /**
     * 播放器 release
     */
    fun release() {
        try {
            synchronized(mAudioTrackLock) {
                mScope.cancel()
                mAudioTrack?.flush()
                if (isPlaying) {
                    mAudioTrack?.stop()
                    mListener?.onStop()
                }
                mAudioTrack?.release()
                mAudioTrack = null
                mListener = null
            }
        } catch (e: Exception) {
            mListener?.onPlayError("release 播放器异常!", e)
            Logger.e(
                TAG,
                "释放资源异常${e.message}",
                PlayerException(e, "播放器释放资源异常${e.message}")
            )
        }
    }

    /**
     * 设置播放器音量
     */
    fun setVolume(volume: Float) {
        if (mAudioTrack != null) {
            mAudioTrack?.setVolume(volume)
        }
    }

    /**
     * 给播放器添加数据完毕
     */
    fun finishPut() {
        mFinishPut = true
        try {
            mListLock.lock()
            if (mDataBufferList.isEmpty()) {
                // 如果当前已经没有待播放的数据了 发个信号,避免因为take线程没有数据卡死在那里
                mTakeData.signal()
            }
        } finally {
            mListLock.unlock()
        }
    }

    /**
     * resetFinishPut
     */
    fun resetFinishPut() {
        mFinishPut = false
    }

    /**
     * 设置播放回调
     */
    fun setPlayListener(listener: PlayAudioListener?) {
        mListener = listener
    }

    /**
     * 获取数据给播放器
     */
    private fun take(): ByteArray? {
        return try {
            // 上锁
            mListLock.lock()
            val empty = mDataBufferList.isEmpty()
            if (empty && mFinishPut) {
                return null
            }
            if (empty) {
                // 合成数据已经播放完,等待引擎返回数据时 线程等待
                try {
                    mTakeData.await()
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                    mListener?.onPlayError("获取数据出错", e)
                }
            }
            mDataBufferList.poll() ?: null
        } finally {
            mListLock.unlock()
        }
    }

    /**
     * 播放音频
     */
    private fun play() {
        Logger.i(TAG, "play")
        mScope.launch {
            try {
                if (!isPlaying) {
                    audioFocusManager.requestFocus()
                    mAudioTrack?.play()
                    mListener?.onPlayStart()
                }
                while (isPlaying && isActive) {
                    val buffer = take()
                    if (isPlaying && isActive) {
                        if (buffer != null) {
                            writeToAudioTrack(mAudioTrack, buffer)
                        } else {
                            break
                        }
                    } else {
                        // 当前是暂停,把这一帧扔回去
                        if (buffer != null && isPause) {
                            mDataBufferList.offerFirst(buffer)
                        } else {
                            break
                        }
                    }
                }
                Logger.i(TAG, "播放完毕${hashCode()}:finishPut=$mFinishPut,isPause=$isPause")
                // 播放结束回调
                if (mFinishPut && !isPause) {
                    mListener?.onPlayCompletion()
                }
            } catch (e: Exception) {
                Logger.i(TAG, "PCM播放错误:${e.message}", e)
                mListener?.onPlayError(e.message, e)
            } finally {
                audioFocusManager.releaseAudioFocus()
            }
        }
    }

    /**
     * 写入数据到播放器
     */
    private fun writeToAudioTrack(audioTrack: AudioTrack?, bytes: ByteArray) {
        var count = 0
        while (count < bytes.size) {
            val written = audioTrack?.write(bytes, count, bytes.size)
            if ((written ?: 0) < 0) {
                return
            }
            count += (written ?: 0)
        }
    }

    companion object {
        /**
         * TAG
         */
        private const val TAG = "TtsPcmPlayer"
        /**
         * splitByteArray
         */
        fun splitByteArray(src: ByteArray, frameSize: Int): List<ByteArray> {
            val result: MutableList<ByteArray> = ArrayList()
            val length = src.size
            var i = 0
            while (i < length) {
                val endIndex = (i + frameSize).coerceAtMost(length)
                val size = endIndex - i
                val dst = ByteArray(size)
                System.arraycopy(src, i, dst, 0, size)
                result.add(dst)
                i += size
            }
            return result
        }
    }
}

播放器音频焦点处理器

class AudioFocusManager(context: Context) : AudioManager.OnAudioFocusChangeListener {
    private val mAudioManager: AudioManager =
        context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
    private var mFocusRequest: AudioFocusRequest? = null
    private var mAudioAttributes: AudioAttributes? = null
    private var mOnRequestFocusResultListener: OnRequestFocusResultListener? = null
    private var mAudioFocusChangeListener: OnAudioFocusChangeListener? = null

    /**
     * Request audio focus.
     */
    fun requestFocus() {
        var result: Int = AudioManager.AUDIOFOCUS_NONE
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (mFocusRequest == null) {
                if (mAudioAttributes == null) {
                    mAudioAttributes = AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build()
                }
                mAudioAttributes?.let {
                    mFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                        .setAudioAttributes(it)
                        .setWillPauseWhenDucked(true)
                        .setOnAudioFocusChangeListener(this)
                        .build()
                }
            }
            mFocusRequest?.let {
                result = mAudioManager.requestAudioFocus(it)
            }
        } else {
            result = mAudioManager.requestAudioFocus(
                this,
                AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN
            )
        }
        mOnRequestFocusResultListener?.onHandleResult(result)
    }

    override fun onAudioFocusChange(focusChange: Int) {
        mAudioFocusChangeListener?.onAudioFocusChange(focusChange)
    }

    /**
     * Release audio focus.
     */
    fun releaseAudioFocus() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mFocusRequest?.let {
                mAudioManager.abandonAudioFocusRequest(it)
            }
        } else {
            mAudioManager.abandonAudioFocus(this)
        }
    }

    /**
     * Handle the result of audio focus.
     */
    interface OnRequestFocusResultListener {
        /**
         * onHandleResult
         */
        fun onHandleResult(result: Int)
    }

    /**
     * setOnHandleResultListener 获得焦点时播放(暂不使用)
     */
    fun setOnHandleResultListener(listener: OnRequestFocusResultListener?) {
        mOnRequestFocusResultListener = listener
    }

    /**
     * Same as AudioManager.OnAudioFocusChangeListener.
     */
    interface OnAudioFocusChangeListener {
        /**
         * onAudioFocusChange
         */
        fun onAudioFocusChange(focusChange: Int)
    }

    /**
     * setOnAudioFocusChangeListener
     */
    fun setOnAudioFocusChangeListener(listener: OnAudioFocusChangeListener?) {
        mAudioFocusChangeListener = listener
    }
}

播放器回调

interface PlayAudioListener {
    /**
     * 播放开始回调
     */
    fun onPlayStart()

    /**暂停*/
    fun onPause()

    /**停止*/
    fun onStop()

    /**
     * 播放结束回调
     */
    fun onPlayCompletion()

    /**
     * 播放出错
     */
    fun onPlayError(errorMsg: String?, throwable: Throwable?)

    /**resume*/
    fun onResume()
}

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐