From 579e4a0d4cd339c41f7a22542abd44bb9a2a0d2a Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Thu, 16 Nov 2017 18:24:37 +0300 Subject: [PATCH] Small tweaks (#1043) * Make Future.consume() inline. * Small player tweaks. --- .../src/main/kotlin/konan/worker/Worker.kt | 11 ++--- .../src/main/kotlin/DecoderWorker.kt | 47 +++++++++++-------- .../src/main/kotlin/VideoPlayer.kt | 2 + 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/runtime/src/main/kotlin/konan/worker/Worker.kt b/runtime/src/main/kotlin/konan/worker/Worker.kt index 62f81ad6b3c..0aab5f03617 100644 --- a/runtime/src/main/kotlin/konan/worker/Worker.kt +++ b/runtime/src/main/kotlin/konan/worker/Worker.kt @@ -89,10 +89,10 @@ class Future internal constructor(val id: FutureId) { /** * Blocks execution until the future is ready. */ - fun consume(code: (T) -> R) = + inline fun consume(code: (T) -> R) = when (state) { FutureState.SCHEDULED, FutureState.COMPUTED -> { - val value = @Suppress("UNCHECKED_CAST") (consumeFuture(id) as T) + val value = @Suppress("UNCHECKED_CAST", "NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") (consumeFuture(id) as T) code(value) } FutureState.INVALID -> @@ -101,9 +101,7 @@ class Future internal constructor(val id: FutureId) { throw IllegalStateException("Future is cancelled") } - fun result(): T { - return consume { it -> it } - } + fun result(): T = consume { it -> it } val state: FutureState get() = FutureState.values()[stateOfFuture(id)] @@ -123,7 +121,7 @@ class Worker(val id: WorkerId) { * until all scheduled jobs processed, or terminate immediately. */ fun requestTermination(processScheduledJobs: Boolean = true) = - Future(requestTerminationInternal(id, processScheduledJobs)) + Future(requestTerminationInternal(id, processScheduledJobs)) /** * Schedule a job for further execution in the worker. Schedule is a two-phase operation, @@ -218,6 +216,7 @@ external internal fun shallowCopyInternal(value: Any?): Any? external internal fun stateOfFuture(id: FutureId): Int @SymbolName("Kotlin_Worker_consumeFuture") +@kotlin.internal.InlineExposed external internal fun consumeFuture(id: FutureId): Any? @SymbolName("Kotlin_Worker_waitForAnyFuture") diff --git a/samples/videoplayer/src/main/kotlin/DecoderWorker.kt b/samples/videoplayer/src/main/kotlin/DecoderWorker.kt index 6a704db6de8..7a91d805aeb 100644 --- a/samples/videoplayer/src/main/kotlin/DecoderWorker.kt +++ b/samples/videoplayer/src/main/kotlin/DecoderWorker.kt @@ -27,14 +27,13 @@ data class OutputInfo(val width: Int, val height: Int, val pixelFormat: AVPixelF data class VideoInfo(val width: Int, val height: Int, val fps: Double, val sampleRate: Int, val channels: Int) -private data class AskNextAudioFrame(val size: Int) -data class VideoFrame(val buffer: CPointer, val lineSize: Int) { +data class VideoFrame(val buffer: CPointer, val lineSize: Int, val timeStamp: Double) { fun unref() { av_buffer_unref2(buffer) } } -data class AudioFrame(val buffer: CPointer, var position: Int, val size: Int) { +data class AudioFrame(val buffer: CPointer, var position: Int, val size: Int, val timeStamp: Double) { fun unref() { av_buffer_unref2(buffer) } @@ -68,15 +67,21 @@ class DecodeWorkerState(val formatContext: CPointer, var windowHeight = 0 var scaledFrameSize = 0 var noMoreFrames = false - val minAudioFrames = 1 - val maxAudioFrames = 3 + val minAudioFrames = 2 + val maxAudioFrames = 5 val minVideoFrames = 5 fun makeVideoFrame(): VideoFrame { // TODO: reuse buffers! + // Convert the frame from its movie format to window pixel format. + sws_scale(softwareScalingContext, videoFrame!!.pointed.data, + videoFrame!!.pointed.linesize, 0, videoHeight, + scaledVideoFrame!!.pointed.data, scaledVideoFrame!!.pointed.linesize) val buffer = av_buffer_alloc(scaledFrameSize)!! + val ts = av_frame_get_best_effort_timestamp(videoFrame)* av_q2d( + videoCodecContext!!.pointed.time_base.readValue()) memcpy(buffer.pointed.data, scaledVideoFrame!!.pointed.data[0], scaledFrameSize.signExtend()) - return VideoFrame(buffer, scaledVideoFrame!!.pointed.linesize[0]) + return VideoFrame(buffer, scaledVideoFrame!!.pointed.linesize[0], ts) } fun makeAudioFrame(): AudioFrame { @@ -87,9 +92,11 @@ class DecodeWorkerState(val formatContext: CPointer, resampledAudioFrame!!.pointed.nb_samples, resampledAudioFrame!!.pointed.format, 1) + val ts = av_frame_get_best_effort_timestamp(audioFrame) * av_q2d( + audioCodecContext!!.pointed.time_base.readValue()) val buffer = av_buffer_alloc(audioFrameSize)!! memcpy(buffer.pointed.data, resampledAudioFrame!!.pointed.data[0], audioFrameSize.signExtend()) - return AudioFrame(buffer, 0, audioFrameSize) + return AudioFrame(buffer, 0, audioFrameSize, ts) } private fun setResampleOpt(name: String, value: Int) = @@ -188,7 +195,8 @@ class DecodeWorkerState(val formatContext: CPointer, } fun decodeIfNeeded() { - if (!needMoreBuffers()) return + if (!needMoreBuffers() || audioQueue.size() > audioQueue.maxSize - 20 || + videoQueue.size() > videoQueue.maxSize - 5) return memScoped { val packet = alloc() @@ -200,10 +208,6 @@ class DecodeWorkerState(val formatContext: CPointer, avcodec_decode_video2(videoCodecContext, videoFrame, frameFinished.ptr, packet.ptr) // Did we get a video frame? if (frameFinished.value != 0) { - // Convert the frame from its movie format to window pixel format. - sws_scale(softwareScalingContext, videoFrame!!.pointed.data, - videoFrame!!.pointed.linesize, 0, videoHeight, - scaledVideoFrame!!.pointed.data, scaledVideoFrame!!.pointed.linesize) videoQueue.push(makeVideoFrame()) } } @@ -233,7 +237,8 @@ class DecodeWorkerState(val formatContext: CPointer, if (videoQueue.isEmpty()) { return null } - return videoQueue.pop() + val frame = videoQueue.pop()!! + return frame } fun nextAudioFrame(size: Int): AudioFrame? { @@ -247,7 +252,7 @@ class DecodeWorkerState(val formatContext: CPointer, if (frame.position + realSize == frame.size) { return audioQueue.pop() } else { - val result = AudioFrame(av_buffer_ref(frame.buffer)!!, frame.position, frame.size) + val result = AudioFrame(av_buffer_ref(frame.buffer)!!, frame.position, frame.size, frame.timeStamp) frame.position += realSize return result } @@ -271,8 +276,6 @@ class DecodeWorker { fun workerId() = decodeWorker.id - fun init() {} - fun renderPixelFormat(pixelFormat: PixelFormat) = when (pixelFormat) { PixelFormat.RGB24 -> AV_PIX_FMT_RGB24 PixelFormat.ARGB32 -> AV_PIX_FMT_RGB32 @@ -351,6 +354,13 @@ class DecodeWorker { } } + fun init() { + } + + fun deinit() { + decodeWorker.requestTermination().result() + } + fun start(width: Int, height: Int, pixelFormat: PixelFormat) { decodeWorker.schedule(TransferMode.CHECKED, { OutputInfo(width, height, renderPixelFormat(pixelFormat)) @@ -361,7 +371,7 @@ class DecodeWorker { decodeWorker.schedule( TransferMode.CHECKED, { null }) { _ -> - state!!.stop() + state?.stop() state = null }.result() } @@ -372,9 +382,6 @@ class DecodeWorker { { null }) { _ -> (state == null || state!!.done()) as Boolean? }.consume { it -> it!! } - fun deinit() { - decodeWorker.requestTermination().result() - } fun requestDecodeChunk() = decodeWorker.schedule( TransferMode.CHECKED, diff --git a/samples/videoplayer/src/main/kotlin/VideoPlayer.kt b/samples/videoplayer/src/main/kotlin/VideoPlayer.kt index 3b5c7930d3f..ff0d56983a8 100644 --- a/samples/videoplayer/src/main/kotlin/VideoPlayer.kt +++ b/samples/videoplayer/src/main/kotlin/VideoPlayer.kt @@ -132,6 +132,8 @@ class VideoPlayer(val requestedWidth: Int, val requestedHeight: Int) { // Interframe pause, may lead to broken A/V sync, think of better approach. if (state == State.PLAYING) { + // Request decoding. + decoder.requestDecodeChunk() if (hasVideo) { if (hasAudio) { // Use sound for A/V sync.