Skip to content

Commit

Permalink
Add end of stream flag to AudioDecoder and increase min api to 23
Browse files Browse the repository at this point in the history
  • Loading branch information
mobad committed Apr 18, 2024
1 parent e5a818b commit d0bc1e3
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 92 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ android {
namespace 'com.github.jing332.tts_server_android'
defaultConfig {
applicationId 'com.github.jing332.tts_server_android'
minSdk 21
minSdk 23
targetSdk 34
versionCode gitCommits
versionName version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import android.media.MediaCodec
import android.media.MediaCodec.BufferInfo
import android.media.MediaExtractor
import android.media.MediaFormat
import android.os.Build
import android.os.SystemClock
import android.text.TextUtils
import android.util.Log
import com.github.jing332.tts_server_android.help.audio.AudioDecoderException.Companion.ERROR_CODE_NO_AUDIO_TRACK
import com.github.jing332.tts_server_android.utils.GcManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import okio.ByteString.Companion.toByteString
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
Expand Down Expand Up @@ -62,12 +60,7 @@ class AudioDecoder {
private fun getFormats(srcData: ByteArray): List<MediaFormat> {
kotlin.runCatching {
val mediaExtractor = MediaExtractor()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
mediaExtractor.setDataSource(ByteArrayMediaDataSource(srcData))
else
mediaExtractor.setDataSource(
"data:" + "" + ";base64," + srcData.toByteString().base64()
)
mediaExtractor.setDataSource(ByteArrayMediaDataSource(srcData))

val formats = mutableListOf<MediaFormat>()
for (i in 0 until mediaExtractor.trackCount) {
Expand Down Expand Up @@ -116,6 +109,7 @@ class AudioDecoder {
}
}
mediaCodec!!.reset()
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0)
mediaCodec!!.configure(mediaFormat, null, null, 0)
return mediaCodec as MediaCodec
}
Expand All @@ -135,12 +129,7 @@ class AudioDecoder {
onRead.invoke(data)
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
mediaExtractor.setDataSource(ByteArrayMediaDataSource(srcData))
else
mediaExtractor.setDataSource(
"data:" + currentMime + ";base64," + srcData.toByteString().base64()
)
mediaExtractor.setDataSource(ByteArrayMediaDataSource(srcData))

decodeInternal(mediaExtractor, sampleRate) {
onRead.invoke(it)
Expand All @@ -165,13 +154,7 @@ class AudioDecoder {
) {
val mediaExtractor = MediaExtractor()
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
mediaExtractor.setDataSource(InputStreamMediaDataSource(ins))
else
throw AudioDecoderException(
AudioDecoderException.ERROR_CODE_NOT_SUPPORT_A5,
"音频流解码器不支持 Android 5"
)
mediaExtractor.setDataSource(InputStreamMediaDataSource(ins))

decodeInternal(mediaExtractor, sampleRate, timeoutUs) {
onRead.invoke(it)
Expand Down Expand Up @@ -208,31 +191,38 @@ class AudioDecoder {
bufferInfo.presentationTimeUs = mediaExtractor.sampleTime

inputBuffer = mediaCodec.getInputBuffer(inputIndex)
if (inputBuffer != null) {
inputBuffer.clear()
} else
if (inputBuffer == null) {
continue
}

//从流中读取的采样数量
val sampleSize = mediaExtractor.readSampleData(inputBuffer, 0)
if (sampleSize > 0) {
bufferInfo.size = sampleSize
//入队解码
mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0)
mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, mediaExtractor.sampleTime, 0)
//移动到下一个采样点
if (!mediaExtractor.nextSample(startNanos)) {
Log.d(TAG, "nextSample(): 已到达流末尾EOF")
}
} else
break
} else {
mediaCodec.queueInputBuffer(
inputIndex,
0,
0,
0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM
)
}

//取解码后的数据/
val outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeoutUs)

//取解码后的数据
var outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeoutUs)
//不一定能一次取完,所以要循环取
var outputBuffer: ByteBuffer?
val pcmData = ByteArray(bufferInfo.size)

while (coroutineContext.isActive && outputIndex >= 0) {
if (outputIndex >= 0) {
outputBuffer = mediaCodec.getOutputBuffer(outputIndex)
if (outputBuffer != null) {
outputBuffer.get(pcmData)
Expand All @@ -241,7 +231,10 @@ class AudioDecoder {

onRead.invoke(pcmData)
mediaCodec.releaseOutputBuffer(/* index = */ outputIndex, /* render = */ false)
outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeoutUs)
}

if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break
}
}
}
Expand All @@ -262,7 +255,7 @@ class AudioDecoder {
for (i in 0 until trackCount) {
trackFormat = getTrackFormat(i)
mime = trackFormat.getString(MediaFormat.KEY_MIME)
if (!TextUtils.isEmpty(mime) && mime!!.startsWith("audio")) {
if (!TextUtils.isEmpty(mime) && mime!!.startsWith("audio/")) {
audioTrackIndex = i
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import java.io.InputStream

@SuppressLint("UnsafeOptInUsageError")
class ExoAudioPlayer(val context: Context) {
companion object {
const val TAG = "AudioPlayer"
Expand All @@ -34,7 +33,7 @@ class ExoAudioPlayer(val context: Context) {

// APP内音频播放器 必须在主线程调用
private val exoPlayer by lazy {
ExoPlayer.Builder(context).setSkipSilenceEnabled(true).build().apply {
ExoPlayer.Builder(context).build().apply {
playWhenReady = true
addListener(object : Player.Listener {
@SuppressLint("SwitchIntDef")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package com.github.jing332.tts_server_android.help.audio

import android.media.MediaDataSource
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import okio.buffer
import okio.source
import java.io.InputStream
import kotlin.math.max

@RequiresApi(Build.VERSION_CODES.M)
class InputStreamMediaDataSource(private val inputStream: InputStream) : MediaDataSource() {
companion object {
const val TAG = "InputStreamDataSource"
}

private val bufferedInputStream = inputStream.source().buffer()
private var cache = ByteArray(1024)
private var limit = 0L
private var finishedRead = false


override fun close() {
Log.d(TAG, "close")
Expand All @@ -24,9 +26,27 @@ class InputStreamMediaDataSource(private val inputStream: InputStream) : MediaDa
override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
Log.d(TAG, "readAt: pos=$position, offset=$offset, size=$size")
kotlin.runCatching {
return bufferedInputStream.read(buffer, offset, size).apply {
Log.d(TAG, "readAt: readLen=$this")
val newSize = position.toInt() + size
if (!finishedRead && newSize > cache.size) {
cache = cache.copyOf(32 - Integer.numberOfLeadingZeros((newSize * 2) - 1))
}
while (!finishedRead && newSize > limit) {
val readSize = bufferedInputStream.read(cache, limit.toInt(), size).apply {
Log.d(TAG, "readAt: readLen=$this")
}
if (readSize == -1) {
finishedRead = true
} else {
limit += readSize
}
}
val bytesToRead = max(limit - position, -1)
if (bytesToRead > 0) {
cache.copyInto(buffer, offset, position.toInt(), limit.toInt())
}

return bytesToRead.toInt()

}.onFailure {
Log.d(TAG, it.stackTraceToString())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import androidx.media3.common.AudioAttributes
import androidx.media3.common.AuxEffectInfo
import androidx.media3.common.Format
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.audio.AudioProcessor
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.audio.AudioSink
import androidx.media3.exoplayer.audio.AudioSink.SINK_FORMAT_SUPPORTED_WITH_TRANSCODING
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor
import java.nio.ByteBuffer

@UnstableApi
Expand All @@ -17,14 +15,6 @@ import java.nio.ByteBuffer
*/
class DecoderAudioSink(private val onPcmBuffer: (ByteBuffer) -> Unit) : AudioSink {
private var timeUs: Long = 0L
private var skippingAudioProcessor =
SilenceSkippingAudioProcessor(
SilenceSkippingAudioProcessor.DEFAULT_MINIMUM_SILENCE_DURATION_US,
SilenceSkippingAudioProcessor.DEFAULT_SILENCE_RETENTION_RATIO,
SilenceSkippingAudioProcessor.DEFAULT_MAX_SILENCE_TO_KEEP_DURATION_US,
SilenceSkippingAudioProcessor.DEFAULT_MIN_VOLUME_TO_KEEP_PERCENTAGE,
SilenceSkippingAudioProcessor.DEFAULT_SILENCE_THRESHOLD_LEVEL
)

companion object {
const val TAG = "DecoderAudioSink"
Expand All @@ -46,9 +36,6 @@ class DecoderAudioSink(private val onPcmBuffer: (ByteBuffer) -> Unit) : AudioSin
specifiedBufferSize: Int,
outputChannels: IntArray?
) {
skippingAudioProcessor.setEnabled(true)
skippingAudioProcessor.configure(AudioProcessor.AudioFormat(inputFormat))
skippingAudioProcessor.flush()
}

override fun play() {
Expand All @@ -63,39 +50,18 @@ class DecoderAudioSink(private val onPcmBuffer: (ByteBuffer) -> Unit) : AudioSin
presentationTimeUs: Long,
encodedAccessUnitCount: Int
): Boolean {
while (!skippingAudioProcessor.isEnded) {
val outBuf = skippingAudioProcessor.output
if (outBuf.hasRemaining()) {
onPcmBuffer.invoke(outBuf)
}
if (!buffer.hasRemaining()) {
break
}
skippingAudioProcessor.queueInput(buffer)
}
onPcmBuffer.invoke(buffer)
timeUs += presentationTimeUs
return true
}

override fun playToEndOfStream() {
skippingAudioProcessor.queueEndOfStream()
while (!skippingAudioProcessor.isEnded) {
val outBuf = skippingAudioProcessor.output
if (outBuf.hasRemaining()) {
onPcmBuffer.invoke(outBuf)
}
skippingAudioProcessor.queueEndOfStream()
}
}

override fun isEnded(): Boolean {
return skippingAudioProcessor.isEnded
}

override fun hasPendingData(): Boolean
{
return !skippingAudioProcessor.isEnded
}
override fun isEnded(): Boolean = true

override fun hasPendingData(): Boolean = true

override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {

Expand All @@ -108,7 +74,7 @@ class DecoderAudioSink(private val onPcmBuffer: (ByteBuffer) -> Unit) : AudioSin

}

override fun getSkipSilenceEnabled(): Boolean = true
override fun getSkipSilenceEnabled(): Boolean = false

override fun setAudioAttributes(audioAttributes: AudioAttributes) {

Expand Down Expand Up @@ -137,15 +103,16 @@ class DecoderAudioSink(private val onPcmBuffer: (ByteBuffer) -> Unit) : AudioSin
}

override fun pause() {

}

override fun flush() {
timeUs = 0
skippingAudioProcessor.flush()

}


override fun reset() {
flush()
skippingAudioProcessor.reset()


}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@ package com.github.jing332.tts_server_android.help.audio.exo

import android.annotation.SuppressLint
import android.content.Context
import androidx.media3.common.C
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.audio.AudioSink
import androidx.media3.exoplayer.audio.DefaultAudioSink
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor.DEFAULT_PADDING_SILENCE_US
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor.DEFAULT_SILENCE_THRESHOLD_LEVEL
import androidx.media3.exoplayer.audio.TeeAudioProcessor
import androidx.media3.exoplayer.source.MediaSource
import com.drake.net.utils.withMain
import com.github.jing332.tts_server_android.help.audio.AudioDecoderException
Expand All @@ -37,10 +31,10 @@ class ExoAudioDecoder(val context: Context) {
context: Context,
enableFloatOutput: Boolean,
enableAudioTrackPlaybackParams: Boolean
): AudioSink? {
): AudioSink {
return DecoderAudioSink { callback?.onReadPcmAudio(it) }
}
}.apply { forceEnableMediaCodecAsynchronousQueueing() }
}

ExoPlayer.Builder(context, rendererFactory).build().apply {
addListener(object : Player.Listener {
Expand Down

0 comments on commit d0bc1e3

Please sign in to comment.