Small tweaks (#1043)

* Make Future.consume() inline.

* Small player tweaks.
This commit is contained in:
Nikolay Igotti
2017-11-16 18:24:37 +03:00
committed by GitHub
parent 4d8fddef6e
commit 579e4a0d4c
3 changed files with 34 additions and 26 deletions
@@ -89,10 +89,10 @@ class Future<T> internal constructor(val id: FutureId) {
/** /**
* Blocks execution until the future is ready. * Blocks execution until the future is ready.
*/ */
fun <R> consume(code: (T) -> R) = inline fun <R> consume(code: (T) -> R) =
when (state) { when (state) {
FutureState.SCHEDULED, FutureState.COMPUTED -> { 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) code(value)
} }
FutureState.INVALID -> FutureState.INVALID ->
@@ -101,9 +101,7 @@ class Future<T> internal constructor(val id: FutureId) {
throw IllegalStateException("Future is cancelled") throw IllegalStateException("Future is cancelled")
} }
fun result(): T { fun result(): T = consume { it -> it }
return consume { it -> it }
}
val state: FutureState val state: FutureState
get() = FutureState.values()[stateOfFuture(id)] get() = FutureState.values()[stateOfFuture(id)]
@@ -123,7 +121,7 @@ class Worker(val id: WorkerId) {
* until all scheduled jobs processed, or terminate immediately. * until all scheduled jobs processed, or terminate immediately.
*/ */
fun requestTermination(processScheduledJobs: Boolean = true) = fun requestTermination(processScheduledJobs: Boolean = true) =
Future<Any?>(requestTerminationInternal(id, processScheduledJobs)) Future<FutureId>(requestTerminationInternal(id, processScheduledJobs))
/** /**
* Schedule a job for further execution in the worker. Schedule is a two-phase operation, * 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 external internal fun stateOfFuture(id: FutureId): Int
@SymbolName("Kotlin_Worker_consumeFuture") @SymbolName("Kotlin_Worker_consumeFuture")
@kotlin.internal.InlineExposed
external internal fun consumeFuture(id: FutureId): Any? external internal fun consumeFuture(id: FutureId): Any?
@SymbolName("Kotlin_Worker_waitForAnyFuture") @SymbolName("Kotlin_Worker_waitForAnyFuture")
@@ -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, data class VideoInfo(val width: Int, val height: Int, val fps: Double,
val sampleRate: Int, val channels: Int) val sampleRate: Int, val channels: Int)
private data class AskNextAudioFrame(val size: Int) data class VideoFrame(val buffer: CPointer<AVBufferRef>, val lineSize: Int, val timeStamp: Double) {
data class VideoFrame(val buffer: CPointer<AVBufferRef>, val lineSize: Int) {
fun unref() { fun unref() {
av_buffer_unref2(buffer) av_buffer_unref2(buffer)
} }
} }
data class AudioFrame(val buffer: CPointer<AVBufferRef>, var position: Int, val size: Int) { data class AudioFrame(val buffer: CPointer<AVBufferRef>, var position: Int, val size: Int, val timeStamp: Double) {
fun unref() { fun unref() {
av_buffer_unref2(buffer) av_buffer_unref2(buffer)
} }
@@ -68,15 +67,21 @@ class DecodeWorkerState(val formatContext: CPointer<AVFormatContext>,
var windowHeight = 0 var windowHeight = 0
var scaledFrameSize = 0 var scaledFrameSize = 0
var noMoreFrames = false var noMoreFrames = false
val minAudioFrames = 1 val minAudioFrames = 2
val maxAudioFrames = 3 val maxAudioFrames = 5
val minVideoFrames = 5 val minVideoFrames = 5
fun makeVideoFrame(): VideoFrame { fun makeVideoFrame(): VideoFrame {
// TODO: reuse buffers! // 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 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()) 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 { fun makeAudioFrame(): AudioFrame {
@@ -87,9 +92,11 @@ class DecodeWorkerState(val formatContext: CPointer<AVFormatContext>,
resampledAudioFrame!!.pointed.nb_samples, resampledAudioFrame!!.pointed.nb_samples,
resampledAudioFrame!!.pointed.format, resampledAudioFrame!!.pointed.format,
1) 1)
val ts = av_frame_get_best_effort_timestamp(audioFrame) * av_q2d(
audioCodecContext!!.pointed.time_base.readValue())
val buffer = av_buffer_alloc(audioFrameSize)!! val buffer = av_buffer_alloc(audioFrameSize)!!
memcpy(buffer.pointed.data, resampledAudioFrame!!.pointed.data[0], audioFrameSize.signExtend()) 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) = private fun setResampleOpt(name: String, value: Int) =
@@ -188,7 +195,8 @@ class DecodeWorkerState(val formatContext: CPointer<AVFormatContext>,
} }
fun decodeIfNeeded() { fun decodeIfNeeded() {
if (!needMoreBuffers()) return if (!needMoreBuffers() || audioQueue.size() > audioQueue.maxSize - 20 ||
videoQueue.size() > videoQueue.maxSize - 5) return
memScoped { memScoped {
val packet = alloc<AVPacket>() val packet = alloc<AVPacket>()
@@ -200,10 +208,6 @@ class DecodeWorkerState(val formatContext: CPointer<AVFormatContext>,
avcodec_decode_video2(videoCodecContext, videoFrame, frameFinished.ptr, packet.ptr) avcodec_decode_video2(videoCodecContext, videoFrame, frameFinished.ptr, packet.ptr)
// Did we get a video frame? // Did we get a video frame?
if (frameFinished.value != 0) { 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()) videoQueue.push(makeVideoFrame())
} }
} }
@@ -233,7 +237,8 @@ class DecodeWorkerState(val formatContext: CPointer<AVFormatContext>,
if (videoQueue.isEmpty()) { if (videoQueue.isEmpty()) {
return null return null
} }
return videoQueue.pop() val frame = videoQueue.pop()!!
return frame
} }
fun nextAudioFrame(size: Int): AudioFrame? { fun nextAudioFrame(size: Int): AudioFrame? {
@@ -247,7 +252,7 @@ class DecodeWorkerState(val formatContext: CPointer<AVFormatContext>,
if (frame.position + realSize == frame.size) { if (frame.position + realSize == frame.size) {
return audioQueue.pop() return audioQueue.pop()
} else { } 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 frame.position += realSize
return result return result
} }
@@ -271,8 +276,6 @@ class DecodeWorker {
fun workerId() = decodeWorker.id fun workerId() = decodeWorker.id
fun init() {}
fun renderPixelFormat(pixelFormat: PixelFormat) = when (pixelFormat) { fun renderPixelFormat(pixelFormat: PixelFormat) = when (pixelFormat) {
PixelFormat.RGB24 -> AV_PIX_FMT_RGB24 PixelFormat.RGB24 -> AV_PIX_FMT_RGB24
PixelFormat.ARGB32 -> AV_PIX_FMT_RGB32 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) { fun start(width: Int, height: Int, pixelFormat: PixelFormat) {
decodeWorker.schedule(TransferMode.CHECKED, { decodeWorker.schedule(TransferMode.CHECKED, {
OutputInfo(width, height, renderPixelFormat(pixelFormat)) OutputInfo(width, height, renderPixelFormat(pixelFormat))
@@ -361,7 +371,7 @@ class DecodeWorker {
decodeWorker.schedule( decodeWorker.schedule(
TransferMode.CHECKED, TransferMode.CHECKED,
{ null }) { _ -> { null }) { _ ->
state!!.stop() state?.stop()
state = null state = null
}.result() }.result()
} }
@@ -372,9 +382,6 @@ class DecodeWorker {
{ null }) { _ -> (state == null || state!!.done()) as Boolean? { null }) { _ -> (state == null || state!!.done()) as Boolean?
}.consume { it -> it!! } }.consume { it -> it!! }
fun deinit() {
decodeWorker.requestTermination().result()
}
fun requestDecodeChunk() = decodeWorker.schedule( fun requestDecodeChunk() = decodeWorker.schedule(
TransferMode.CHECKED, TransferMode.CHECKED,
@@ -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. // Interframe pause, may lead to broken A/V sync, think of better approach.
if (state == State.PLAYING) { if (state == State.PLAYING) {
// Request decoding.
decoder.requestDecodeChunk()
if (hasVideo) { if (hasVideo) {
if (hasAudio) { if (hasAudio) {
// Use sound for A/V sync. // Use sound for A/V sync.