Small tweaks (#1043)
* Make Future.consume() inline. * Small player tweaks.
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user