Небольшая пауза между переходами циклов с движком R3 при использовании с ExoPlayer (Android)Android

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Небольшая пауза между переходами циклов с движком R3 при использовании с ExoPlayer (Android)

Сообщение Anonymous »

Я интегрировал библиотеку Rubberband с ExoPlayer для регулировки высоты тона и темпа в проекте Android. При использовании движка R2 зацикливание звука работает плавно, как и ожидалось. Однако при переключении на движок R3 (OptionEngineFiner) с включенным циклическим воспроизведением в exoplayer возникает небольшая, но заметная пауза между концом и началом аудиофайла во время циклических переходов. Такое поведение не наблюдается в движке R2 (OptionEngineFaster) и влияет только на движок R3, что приводит к нестабильному циклическому функционированию.
Что-то мне не хватает в движке R3? Я опробовал демонстрационное приложение Rubberband, и циклирование там работает нормально.
Мой код аудиопроцессора для exoplayer:

Код: Выделить всё

rb = RubberBandStretcher(
inputAudioFormat.sampleRate,
inputAudioFormat.channelCount,
RubberBandStretcher.OptionProcessRealTime.or(RubberBandStretcher.OptionEngineFiner).or(RubberBandStretcher.OptionWindowShort).or(RubberBandStretcher.OptionFormantPreserved).or(RubberBandStretcher.OptionPitchHighConsistency),
speed.toDouble(),
pitch.toDouble()
)
Весь код аудиопроцессора

Код: Выделить всё

class RubberBandAudioProcessor : AudioProcessor {

/** Indicates that the output sample rate should be the same as the input.  */
val SAMPLE_RATE_NO_CHANGE = -1

/** The threshold below which the difference between two pitch/speed factors is negligible.  */
private val CLOSE_THRESHOLD = 0.0001f

/**
* The minimum number of output bytes required for duration scaling to be calculated using the
* input and output byte counts, rather than using the current playback speed.
*/
private val MIN_BYTES_FOR_DURATION_SCALING_CALCULATION = 1024

private var pendingOutputSampleRate = 0
private var speed = 1f
private var pitch = 1f

private var pendingInputAudioFormat: AudioProcessor.AudioFormat
private var pendingOutputAudioFormat: AudioProcessor.AudioFormat
private var inputAudioFormat: AudioProcessor.AudioFormat
private var outputAudioFormat: AudioProcessor.AudioFormat

private var pendingSonicRecreation = false
private var rb: RubberBandStretcher? = null
private var buffer: ByteBuffer
private var shortBuffer: ShortBuffer
private var outputBuffer: ByteBuffer
private var inputBytes: Long = 0
private var outputBytes: Long = 0
private var inputEnded = false

/** Creates a new Sonic audio processor.  */
init {
speed = 1f
pitch = 1f
pendingInputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
pendingOutputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
inputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
outputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
buffer = AudioProcessor.EMPTY_BUFFER
shortBuffer = buffer.asShortBuffer()
outputBuffer = AudioProcessor.EMPTY_BUFFER
pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE
}

/**
* Sets the target playback speed. This method may only be called after draining data through the
* processor. The value returned by [.isActive] may change, and the processor must be
* [flushed][.flush] before queueing more data.
*
* @param speed The target factor by which playback should be sped up.
*/
fun setSpeed(speed: Float) {
if (this.speed != speed) {
this.speed = 1/speed
rb?.timeRatio = 1/speed.toDouble()
pendingSonicRecreation = true
}
}

/**
* Sets the target playback pitch. This method may only be called after draining data through the
* processor. The value returned by [.isActive] may change, and the processor must be
* [flushed][.flush] before queueing more data.
*
* @param pitch The target pitch.
*/
fun setPitch(pitch: Float) {
if (this.pitch != pitch) {
this.pitch = pitch
rb?.pitchScale=pitch.toDouble()
pendingSonicRecreation = true
}
}

/**
* Sets the sample rate for output audio, in Hertz. Pass [.SAMPLE_RATE_NO_CHANGE] to output
* audio at the same sample rate as the input.  After calling this method, call [ ][.configure] to configure the processor with the new sample rate.
*
* @param sampleRateHz The sample rate for output audio, in Hertz.
* @see .configure
*/
fun setOutputSampleRateHz(sampleRateHz: Int) {
pendingOutputSampleRate = sampleRateHz
}

/**
* Returns the media duration corresponding to the specified playout duration, taking speed
* adjustment into account.
*
*
* The scaling performed by this method will use the actual playback speed achieved by the
* audio processor, on average, since it was last flushed.  This may differ very slightly from the
* target playback speed.
*
* @param playoutDuration The playout duration to scale.
* @return The corresponding media duration, in the same units as `duration`.
*/
fun getMediaDuration(playoutDuration: Long): Long {
return if (outputBytes >= MIN_BYTES_FOR_DURATION_SCALING_CALCULATION) {
val processedInputBytes = inputBytes - Assertions.checkNotNull(rb).samplesRequired
if (outputAudioFormat.sampleRate == inputAudioFormat.sampleRate) Util.scaleLargeTimestamp(
playoutDuration,
processedInputBytes,
outputBytes
) else Util.scaleLargeTimestamp(
playoutDuration,
processedInputBytes * outputAudioFormat.sampleRate,
outputBytes * inputAudioFormat.sampleRate
)
} else {
(speed.toDouble() * playoutDuration).toLong()
}
}

@Throws(UnhandledAudioFormatException::class)
override fun configure(inputAudioFormat: AudioProcessor.AudioFormat): AudioProcessor.AudioFormat {
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
throw UnhandledAudioFormatException(inputAudioFormat)
}
val outputSampleRateHz =
if (pendingOutputSampleRate == SAMPLE_RATE_NO_CHANGE) inputAudioFormat.sampleRate else pendingOutputSampleRate
pendingInputAudioFormat = inputAudioFormat
pendingOutputAudioFormat = AudioProcessor.AudioFormat(
outputSampleRateHz,
inputAudioFormat.channelCount,
C.ENCODING_PCM_16BIT
)

pendingSonicRecreation = true
return pendingOutputAudioFormat
}

override fun isActive(): Boolean {
val outputSampleRate = pendingOutputAudioFormat.sampleRate
val inputSampleRate = pendingInputAudioFormat.sampleRate
val speedDifference = abs(speed - 1f)
val pitchDifference = abs(pitch - 1f)

val isOutputSampleRateValid = outputSampleRate != Format.NO_VALUE
val isSpeedChanged = speedDifference >= CLOSE_THRESHOLD
val isPitchChanged = pitchDifference >= CLOSE_THRESHOLD
val isSampleRateDifferent = outputSampleRate != inputSampleRate
return (isOutputSampleRateValid
&& (isSpeedChanged || isPitchChanged || isSampleRateDifferent))
}

override fun queueInput(inputBuffer: ByteBuffer) {
if (!inputBuffer.hasRemaining()) {
return
}
val rb = Assertions.checkNotNull(rb)
val shortBuffer = inputBuffer.asShortBuffer()
val inputSize = inputBuffer.remaining()
inputBytes += inputSize.toLong()
rb.process(shortBuffer, inputAudioFormat.channelCount, false)
inputBuffer.position(inputBuffer.position() + inputSize)
}

override fun queueEndOfStream() {
Log.e("RUBBERBANDAUDIOPROCESSOR","QueueEndOfStream");
if (rb != null) {
rb!!.queueEndOfStream()
}
inputEnded = true
}

override fun getOutput(): ByteBuffer {

val rb = rb
val channelCount = inputAudioFormat.channelCount
val samplesRequired = rb?.samplesRequired
if (rb != null) {
val outputSize = rb.available()
if (outputSize > 0) {
if (buffer.capacity() <  outputSize * channelCount * 2) {
buffer = ByteBuffer.allocateDirect(outputSize * channelCount * 2)
.order(ByteOrder.nativeOrder())
shortBuffer = buffer.asShortBuffer()
} else {
buffer.clear()
shortBuffer.clear()
}
val retrieved = rb.retrieve(shortBuffer, channelCount)
outputBytes += retrieved.toLong() * channelCount * 2
buffer.limit(retrieved * channelCount * 2)
outputBuffer = buffer
}
}
val outputBuffer = outputBuffer
this.outputBuffer = AudioProcessor.EMPTY_BUFFER
return outputBuffer
}

override fun isEnded(): Boolean {
Log.e("inputeEnded", inputEnded.toString())
Log.e("rb", rb.toString())
val samplesRequired= rb?.samplesRequired

Log.e("rb sample required", samplesRequired.toString())

// return inputEnded && (rb == null || samplesRequired == 0)
return inputEnded
}

override fun flush() {
val isAct=isActive();
Log.e("IsActive", isAct.toString())

if (isAct) {
Log.e("pendingSonicRecreation", pendingSonicRecreation.toString()  )
inputAudioFormat = pendingInputAudioFormat
outputAudioFormat = pendingOutputAudioFormat
if (pendingSonicRecreation) {

rb = RubberBandStretcher(
inputAudioFormat.sampleRate,
inputAudioFormat.channelCount,
RubberBandStretcher.OptionProcessRealTime.or(RubberBandStretcher.OptionEngineFiner).or(RubberBandStretcher.OptionWindowShort).or(RubberBandStretcher.OptionFormantPreserved).or(RubberBandStretcher.OptionPitchHighConsistency),
speed.toDouble(),
pitch.toDouble()
)

} else if (rb != null) {

rb!!.reset()
}
}
outputBuffer = AudioProcessor.EMPTY_BUFFER
inputBytes = 0
outputBytes = 0
inputEnded = false
}

override fun reset() {
speed = 1f
pitch = 1f
pendingInputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
pendingOutputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
inputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
outputAudioFormat = AudioProcessor.AudioFormat.NOT_SET
buffer = AudioProcessor.EMPTY_BUFFER
shortBuffer = buffer.asShortBuffer()
outputBuffer = AudioProcessor.EMPTY_BUFFER
pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE
pendingSonicRecreation = false
rb = null
inputBytes = 0
outputBytes = 0
inputEnded = false
}
}
Я пробовал изменить размер буфера.

Подробнее здесь: https://stackoverflow.com/questions/790 ... oplayerand
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»