Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 99294ec

Browse files
committed
Fix keyframe trimming
1 parent 507de08 commit 99294ec

File tree

4 files changed

+76
-6
lines changed

4 files changed

+76
-6
lines changed

‎lib/src/main/java/com/otaliastudios/transcoder/internal/codec/Decoder.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ internal class Decoder(
3333
private val codec = createDecoderByType(format.getString(MediaFormat.KEY_MIME)!!)
3434
private val buffers by lazy { MediaCodecBuffers(codec) }
3535
private var info = BufferInfo()
36+
private val dropper = DecoderDropper()
3637

3738
override fun initialize(next: DecoderChannel) {
3839
super.initialize(next)
@@ -58,6 +59,7 @@ internal class Decoder(
5859
log.v("feedDecoder(): id=$id isKeyFrame=${chunk.keyframe} bytes=${chunk.bytes} timeUs=${chunk.timeUs} buffer=${chunk.buffer}")
5960
val flag = if (chunk.keyframe) BUFFER_FLAG_SYNC_FRAME else 0
6061
codec.queueInputBuffer(id, 0, chunk.bytes, chunk.timeUs, flag)
62+
dropper.input(chunk.timeUs, chunk.render)
6163
}
6264
}
6365

@@ -75,10 +77,9 @@ internal class Decoder(
7577
}
7678
else -> {
7779
val isEos = info.flags and BUFFER_FLAG_END_OF_STREAM != 0
78-
val hasSize = info.size > 0
79-
if (isEos || hasSize) {
80+
val timeUs = if (isEos) info.presentationTimeUs else dropper.output(info.presentationTimeUs)
81+
if (timeUs != null /* && (isEos || info.size > 0) */) {
8082
val buffer = buffers.getOutputBuffer(result)
81-
val timeUs = info.presentationTimeUs
8283
val data = DecoderData(buffer, timeUs) {
8384
codec.releaseOutputBuffer(result, it)
8485
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.otaliastudios.transcoder.internal.codec
2+
3+
import com.otaliastudios.transcoder.internal.utils.Logger
4+
5+
// Hard to read class that takes decoder input frames along with their render boolean,
6+
// and decides which output frames should be rendered, and with which timestamp.
7+
internal class DecoderDropper {
8+
9+
private val log = Logger("DecoderDropper")
10+
private val closedDeltas = mutableMapOf<LongRange, Long>()
11+
private val closedRanges = mutableListOf<LongRange>()
12+
private var pendingRange: LongRange? = null
13+
14+
private var firstInputUs: Long? = null
15+
private var firstOutputUs: Long? = null
16+
17+
private fun debug(message: String, important: Boolean = false) {
18+
val full = "$message pendingRangeUs=${pendingRange} firstInputUs=$firstInputUs " +
19+
"validInputUs=[${closedRanges.joinToString {
20+
"$it(deltaUs=${closedDeltas[it]})"
21+
}}]"
22+
if (important) log.w(full) else log.v(full)
23+
}
24+
25+
fun input(timeUs: Long, render: Boolean) {
26+
if (firstInputUs == null) {
27+
firstInputUs = timeUs
28+
}
29+
if (render) {
30+
debug("INPUT: inputUs=$timeUs")
31+
if (pendingRange == null) pendingRange = timeUs..Long.MAX_VALUE
32+
else pendingRange = pendingRange!!.first..timeUs
33+
} else {
34+
debug("INPUT: Got SKIPPING input! inputUs=$timeUs")
35+
if (pendingRange != null && pendingRange!!.last != Long.MAX_VALUE) {
36+
closedRanges.add(pendingRange!!)
37+
closedDeltas[pendingRange!!] = if (closedRanges.size >= 2) {
38+
pendingRange!!.first - closedRanges[closedRanges.lastIndex - 1].last
39+
} else 0L
40+
}
41+
pendingRange = null
42+
}
43+
}
44+
45+
fun output(timeUs: Long): Long? {
46+
if (firstOutputUs == null) {
47+
firstOutputUs = timeUs
48+
}
49+
val timeInInputScaleUs = firstInputUs!! + (timeUs - firstOutputUs!!)
50+
var deltaUs = 0L
51+
closedRanges.forEach {
52+
deltaUs += closedDeltas[it]!!
53+
if (it.contains(timeInInputScaleUs)) {
54+
debug("OUTPUT: Rendering! outputTimeUs=$timeUs newOutputTimeUs=${timeUs - deltaUs} deltaUs=$deltaUs")
55+
return timeUs - deltaUs
56+
}
57+
}
58+
if (pendingRange != null && pendingRange!!.last != Long.MAX_VALUE) {
59+
if (pendingRange!!.contains(timeInInputScaleUs)) {
60+
if (closedRanges.isNotEmpty()) {
61+
deltaUs += pendingRange!!.first - closedRanges.last().last
62+
}
63+
debug("OUTPUT: Rendering! outputTimeUs=$timeUs newOutputTimeUs=${timeUs - deltaUs} deltaUs=$deltaUs")
64+
return timeUs - deltaUs
65+
}
66+
}
67+
debug("OUTPUT: SKIPPING! outputTimeUs=$timeUs", important = true)
68+
return null
69+
}
70+
}

‎lib/src/main/java/com/otaliastudios/transcoder/internal/data/ReaderWriterBridge.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ internal class ReaderWriterBridge(private val format: MediaFormat)
2222
next.handleFormat(format)
2323
}
2424

25+
// Can't do much about chunk.render, since we don't even decode.
2526
override fun step(state: State.Ok<ReaderData>, fresh: Boolean): State<WriterData> {
2627
val (chunk, _) = state.value
2728
val flags = if (chunk.keyframe) MediaCodec.BUFFER_FLAG_SYNC_FRAME else 0
2829
val result = WriterData(chunk.buffer, chunk.timeUs, flags) {}
29-
return state.map(result)
30+
return if (state is State.Eos) State.Eos(result) else State.Ok(result)
3031
}
3132
}

‎lib/src/main/java/com/otaliastudios/transcoder/internal/pipeline/steps.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ internal sealed class State<out T> {
44

55
// Running
66
open class Ok<T>(val value: T) : State<T>() {
7-
open fun <O> map(other: O) = Ok(other)
87
override fun toString() = "State.Ok($value)"
98
}
109

1110
// Run for the last time
1211
class Eos<T>(value: T) : Ok<T>(value) {
13-
override fun <O> map(other: O) = Eos(other)
1412
override fun toString() = "State.Eos($value)"
1513
}
1614

0 commit comments

Comments
 (0)