diff --git a/encoder/src/main/java/com/pedro/encoder/input/decoder/AndroidExtractor.kt b/encoder/src/main/java/com/pedro/encoder/input/decoder/AndroidExtractor.kt index 02ecd4ca6..1f3af2b67 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/decoder/AndroidExtractor.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/decoder/AndroidExtractor.kt @@ -95,6 +95,12 @@ class AndroidExtractor: Extractor { return sleepTime } + override fun resetTs() { + sleepTime = 0 + accumulativeTs = 0 + lastExtractorTs = 0 + } + override fun seekTo(time: Long) { mediaExtractor.seekTo(time, MediaExtractor.SEEK_TO_PREVIOUS_SYNC) lastExtractorTs = getTimeStamp() diff --git a/encoder/src/main/java/com/pedro/encoder/input/decoder/BaseDecoder.java b/encoder/src/main/java/com/pedro/encoder/input/decoder/BaseDecoder.java index dd6c405b1..9c4bedfbc 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/decoder/BaseDecoder.java +++ b/encoder/src/main/java/com/pedro/encoder/input/decoder/BaseDecoder.java @@ -114,10 +114,19 @@ public void start() { } public void stop() { + stop(true); + } + + public void stop(boolean release) { Log.i(TAG, "stop decoder"); running = false; stopDecoder(); startTs = 0; + extractor.resetTs(); + if (release) releaseExtractor(); + } + + public void releaseExtractor() { extractor.release(); } diff --git a/encoder/src/main/java/com/pedro/encoder/input/decoder/Extractor.kt b/encoder/src/main/java/com/pedro/encoder/input/decoder/Extractor.kt index fe7f95ef1..9065a9c87 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/decoder/Extractor.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/decoder/Extractor.kt @@ -46,6 +46,8 @@ interface Extractor { fun getSleepTime(ts: Long): Long + fun resetTs() {} + fun seekTo(time: Long) fun release() diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/audio/AudioFileSource.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/audio/AudioFileSource.kt index 4959a1172..8271dc1c5 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/audio/AudioFileSource.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/audio/AudioFileSource.kt @@ -92,13 +92,13 @@ class AudioFileSource( override fun stop() { this.getMicrophoneData = null running = false - audioDecoder.stop() + audioDecoder.stop(false) } override fun isRunning(): Boolean = running override fun release() { - if (running) stop() + audioDecoder.releaseExtractor() } fun mute() { diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/audio/InternalAudioSource.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/audio/InternalAudioSource.kt index 8e77d8550..b40e64001 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/audio/InternalAudioSource.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/audio/InternalAudioSource.kt @@ -85,13 +85,12 @@ class InternalAudioSource( this.getMicrophoneData = null microphone.stop() handlerThread.quitSafely() + MediaProjectionHandler.mediaProjection?.unregisterCallback(mediaProjectionCallback) } override fun isRunning(): Boolean = microphone.isRunning - override fun release() { - MediaProjectionHandler.mediaProjection?.unregisterCallback(mediaProjectionCallback) - } + override fun release() {} override fun inputPCMData(frame: Frame) { getMicrophoneData?.inputPCMData(frame) diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/audio/MixAudioSource.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/audio/MixAudioSource.kt index 85a30abe2..aa3a8fa60 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/audio/MixAudioSource.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/audio/MixAudioSource.kt @@ -105,6 +105,7 @@ class MixAudioSource( getMicrophoneData = null microphone.stop() handlerThread.quitSafely() + MediaProjectionHandler.mediaProjection?.unregisterCallback(mediaProjectionCallback) } override fun isRunning(): Boolean = microphone.isRunning diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/video/BufferVideoSource.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/video/BufferVideoSource.kt index e01c66e20..a9457debf 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/video/BufferVideoSource.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/video/BufferVideoSource.kt @@ -121,7 +121,7 @@ class BufferVideoSource( surface = null } - override fun release() { } + override fun release() {} override fun isRunning(): Boolean = running } \ No newline at end of file diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/video/ScreenSource.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/video/ScreenSource.kt index 2a067b210..9f7f4c630 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/video/ScreenSource.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/video/ScreenSource.kt @@ -94,12 +94,11 @@ class ScreenSource @JvmOverloads constructor( surface?.release() surface = null handlerThread.quitSafely() - } - - override fun release() { MediaProjectionHandler.mediaProjection?.unregisterCallback(mediaProjectionCallback) } + override fun release() {} + override fun isRunning(): Boolean = virtualDisplay != null override fun getOrientationConfig(): OrientationConfig { diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt index f84937eef..ee83bf4f3 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt @@ -74,13 +74,13 @@ class VideoFileSource( override fun stop() { running = false - videoDecoder.stop() + videoDecoder.stop(false) this.surface?.release() this.surface = null } override fun release() { - if (running) stop() + videoDecoder.releaseExtractor() } override fun isRunning(): Boolean = running diff --git a/extra-sources/src/main/java/com/pedro/extrasources/AudioBufferProcessor.kt b/extra-sources/src/main/java/com/pedro/extrasources/AudioBufferProcessor.kt index 50815b791..877b64839 100644 --- a/extra-sources/src/main/java/com/pedro/extrasources/AudioBufferProcessor.kt +++ b/extra-sources/src/main/java/com/pedro/extrasources/AudioBufferProcessor.kt @@ -2,44 +2,35 @@ package com.pedro.extrasources import android.media.AudioFormat import androidx.media3.common.audio.AudioProcessor -import androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER +import androidx.media3.common.audio.BaseAudioProcessor import androidx.media3.common.util.UnstableApi -import com.pedro.common.toByteArray import java.nio.ByteBuffer +/** + * Capture the decoded PCM and forward it to the callback. + * The data is also written to the output buffer to keep the AudioSink writing to the AudioTrack. + * That write is what paces the player to real time, so it must not be skipped even if the + * player is muted. + */ @UnstableApi class AudioBufferProcessor( private val callback: (ByteArray) -> Unit -) : AudioProcessor { +) : BaseAudioProcessor() { - private var inputEnded = false - - override fun configure(inputAudioFormat: AudioProcessor.AudioFormat): AudioProcessor.AudioFormat { - return if (inputAudioFormat.encoding == AudioFormat.ENCODING_PCM_16BIT) inputAudioFormat - else AudioProcessor.AudioFormat.NOT_SET + override fun onConfigure(inputAudioFormat: AudioProcessor.AudioFormat): AudioProcessor.AudioFormat { + if (inputAudioFormat.encoding != AudioFormat.ENCODING_PCM_16BIT) { + throw AudioProcessor.UnhandledAudioFormatException(inputAudioFormat) + } + return inputAudioFormat } - override fun isActive(): Boolean = true - override fun queueInput(inputBuffer: ByteBuffer) { - callback(inputBuffer.toByteArray()) - } - - override fun queueEndOfStream() { - inputEnded = true - } - - override fun getOutput(): ByteBuffer { - return EMPTY_BUFFER - } - - override fun isEnded(): Boolean = inputEnded - - override fun flush() { - inputEnded = false - } - - override fun reset() { - inputEnded = false + val size = inputBuffer.remaining() + if (size <= 0) return + val bytes = ByteArray(size) + //get consume the input buffer, required to indicate that the data was processed + inputBuffer.get(bytes) + callback(bytes) + replaceOutputBuffer(size).put(bytes).flip() } } diff --git a/extra-sources/src/main/java/com/pedro/extrasources/CameraUvcSource.kt b/extra-sources/src/main/java/com/pedro/extrasources/CameraUvcSource.kt index b259a6c65..8ff034179 100644 --- a/extra-sources/src/main/java/com/pedro/extrasources/CameraUvcSource.kt +++ b/extra-sources/src/main/java/com/pedro/extrasources/CameraUvcSource.kt @@ -59,8 +59,7 @@ class CameraUvcSource: VideoSource() { running = false } - override fun release() { - } + override fun release() {} override fun isRunning(): Boolean = running diff --git a/extra-sources/src/main/java/com/pedro/extrasources/Media3AudioSource.kt b/extra-sources/src/main/java/com/pedro/extrasources/Media3AudioSource.kt index 08f41cd04..0523d8265 100644 --- a/extra-sources/src/main/java/com/pedro/extrasources/Media3AudioSource.kt +++ b/extra-sources/src/main/java/com/pedro/extrasources/Media3AudioSource.kt @@ -55,6 +55,7 @@ class Media3AudioSource( this.getMicrophoneData = getMicrophoneData if (isRunning()) return player = ExoPlayer.Builder(context, TracksRenderersFactory(context, MediaFrame.Type.AUDIO, processor)).build().also { exoPlayer -> + exoPlayer.volume = 0f val mediaItem = MediaItem.fromUri(path) exoPlayer.setMediaItem(mediaItem) exoPlayer.playbackParameters = PlaybackParameters(speed) @@ -77,9 +78,7 @@ class Media3AudioSource( player = null } - override fun release() { - - } + override fun release() {} override fun isRunning(): Boolean = running diff --git a/extra-sources/src/main/java/com/pedro/extrasources/Media3VideoSource.kt b/extra-sources/src/main/java/com/pedro/extrasources/Media3VideoSource.kt index 4b991d526..59c947a61 100644 --- a/extra-sources/src/main/java/com/pedro/extrasources/Media3VideoSource.kt +++ b/extra-sources/src/main/java/com/pedro/extrasources/Media3VideoSource.kt @@ -70,9 +70,7 @@ class Media3VideoSource( surface = null } - override fun release() { - - } + override fun release() {} override fun isRunning(): Boolean = running diff --git a/extra-sources/src/main/java/com/pedro/extrasources/extractor/Media3Extractor.kt b/extra-sources/src/main/java/com/pedro/extrasources/extractor/Media3Extractor.kt index d50e68be6..c89d23e9b 100644 --- a/extra-sources/src/main/java/com/pedro/extrasources/extractor/Media3Extractor.kt +++ b/extra-sources/src/main/java/com/pedro/extrasources/extractor/Media3Extractor.kt @@ -107,6 +107,12 @@ class Media3Extractor(private val context: Context): Extractor { return sleepTime } + override fun resetTs() { + sleepTime = 0 + accumulativeTs = 0 + lastExtractorTs = 0 + } + override fun seekTo(time: Long) { mediaExtractor.seekTo(time, MediaExtractorCompat.SEEK_TO_PREVIOUS_SYNC) lastExtractorTs = getTimeStamp() diff --git a/library/src/main/java/com/pedro/library/base/StreamBase.kt b/library/src/main/java/com/pedro/library/base/StreamBase.kt index 5f0aabb3b..71761df3e 100644 --- a/library/src/main/java/com/pedro/library/base/StreamBase.kt +++ b/library/src/main/java/com/pedro/library/base/StreamBase.kt @@ -410,9 +410,14 @@ abstract class StreamBase( source.init(width, height, videoEncoder.fps, videoEncoder.rotation) } videoSource.stop() - videoSource.release() if (glInterface.isRunning) glInterface.surfaceTexture.tryClear() - if (wasRunning) source.start(glInterface.surfaceTexture) + if (wasRunning) { + runCatching { source.start(glInterface.surfaceTexture) }.getOrElse { + runCatching { videoSource.start(glInterface.surfaceTexture) } + throw it + } + } + videoSource.release() glInterface.setOrientationConfig(source.getOrientationConfig()) videoSource = source } @@ -429,8 +434,13 @@ abstract class StreamBase( val wasCreated = audioSource.created if (wasCreated) source.init(audioSource.sampleRate, audioSource.isStereo, audioSource.echoCanceler, audioSource.noiseSuppressor) audioSource.stop() + if (wasRunning) { + runCatching { source.start(getMicrophoneData) }.getOrElse { + runCatching { audioSource.start(getMicrophoneData) } + throw it + } + } audioSource.release() - if (wasRunning) source.start(getMicrophoneData) audioSource = source }