Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
package io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.descriptors

import io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.data.ITSElement
import io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.utils.TSConst
import io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.utils.TSTimeUtils
import io.github.thibaultbee.streampack.core.elements.utils.extensions.put
import io.github.thibaultbee.streampack.core.elements.utils.extensions.putShort
import io.github.thibaultbee.streampack.core.elements.utils.extensions.shl
import java.nio.ByteBuffer
import kotlin.math.pow

class AdaptationField(
private val discontinuityIndicator: Boolean = false,
Expand Down Expand Up @@ -53,15 +52,16 @@ class AdaptationField(
val buffer = ByteBuffer.allocate(size)

buffer.put(adaptationFieldLength)
buffer.put(((discontinuityIndicator shl 7)
or (randomAccessIndicator shl 6)
or (elementaryStreamPriorityIndicator shl 5)
or ((programClockReference?.let { 1 } ?: 0) shl 4)
or ((originalProgramClockReference?.let { 1 } ?: 0) shl 3)
or ((spliceCountdown?.let { 1 } ?: 0) shl 2)
or ((transportPrivateData?.let { 1 } ?: 0) shl 1)
or (adaptationFieldExtension?.let { 1 } ?: 0)
))
buffer.put(
((discontinuityIndicator shl 7)
or (randomAccessIndicator shl 6)
or (elementaryStreamPriorityIndicator shl 5)
or ((programClockReference?.let { 1 } ?: 0) shl 4)
or ((originalProgramClockReference?.let { 1 } ?: 0) shl 3)
or ((spliceCountdown?.let { 1 } ?: 0) shl 2)
or ((transportPrivateData?.let { 1 } ?: 0) shl 1)
or (adaptationFieldExtension?.let { 1 } ?: 0)
))

programClockReference?.let {
addClockReference(buffer, it)
Expand All @@ -86,23 +86,22 @@ class AdaptationField(
return buffer
}

private fun addClockReference(buffer: ByteBuffer, timestamp: Long) {
val pcrBase =
(TSConst.SYSTEM_CLOCK_FREQ * timestamp / 1000000 /* µs -> s */ / 300) % 2.toDouble()
.pow(33)
.toLong()
val pcrExt = (TSConst.SYSTEM_CLOCK_FREQ * timestamp / 1000000 /* µs -> s */) % 300
companion object {
private fun addClockReference(buffer: ByteBuffer, timestamp: Long) {
val pcrBase = TSTimeUtils.computeTimestamp90kHz(timestamp)
val pcrExt = TSTimeUtils.computePcrExt(timestamp)

/**
* PCR Base -> 33 bits
* Reserved -> 6 bits (0b111111)
* PCR Ext -> 9 bits
*/
buffer.putInt((pcrBase shr 1).toInt())
buffer.putShort(
(((pcrBase and 0x1) shl 15)
or (0b111111 shl 9)
or (pcrExt and 0x1FF))
)
/**
* PCR Base -> 33 bits
* Reserved -> 6 bits (0b111111)
* PCR Ext -> 9 bits
*/
buffer.putInt((pcrBase shr 1).toInt())
buffer.putShort(
(((pcrBase and 0x1) shl 15)
or (0b111111 shl 9)
or (pcrExt and 0x1FF))
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@
package io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.packets

import io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.data.ITSElement
import io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.utils.TSConst
import io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.utils.TSTimeUtils
import io.github.thibaultbee.streampack.core.elements.utils.extensions.put
import io.github.thibaultbee.streampack.core.elements.utils.extensions.putShort
import io.github.thibaultbee.streampack.core.elements.utils.extensions.shl
import io.github.thibaultbee.streampack.core.elements.utils.extensions.toInt
import java.nio.ByteBuffer
import kotlin.experimental.and
import kotlin.math.pow

class PesHeader(
private val streamId: Short,
Expand Down Expand Up @@ -129,10 +128,7 @@ class PesHeader(
}

private fun addTimestamp(buffer: ByteBuffer, timestamp: Long, fourBits: Byte) {
val pts =
(TSConst.SYSTEM_CLOCK_FREQ * timestamp / 1000000 /* µs -> s */ / 300) % 2.toDouble()
.pow(33)
.toLong()
val pts = TSTimeUtils.computeTimestamp90kHz(timestamp)

buffer.put(
(((fourBits and 0xF).toInt() shl 4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@
package io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.utils

object TSConst {
const val SYSTEM_CLOCK_FREQ = 27000000
const val BASE_PID = 0x00020.toShort()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2026 Thibault B.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.utils

object TSTimeUtils {
private const val PCR_BASE_MASK = 0x1FFFFFFFFL // 33 bits mask

/**
* Converts a timestamp in microseconds to a 33-bit 90 kHz timestamp.
* Used for PCR Base, PTS, and DTS.
*/
fun computeTimestamp90kHz(timestamp: Long): Long {
// 90 kHz ticks per microsecond = 90,000 / 1,000,000 = 9 / 100
return (timestamp * 9 / 100) and PCR_BASE_MASK
}

/**
* Converts a timestamp in microseconds to a 9-bit 27 MHz extension clock.
* Used for PCR Ext.
*/
fun computePcrExt(timestamp: Long): Long {
// To convert microseconds to clock ticks, we use fractions to avoid early integer overflow.
// 27 MHz ticks per microsecond = 27,000,000 / 1,000,000 = 27
return (timestamp * 27 % 300)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.thibaultbee.streampack.core.elements.endpoints.composites.muxers.ts.utils

import org.junit.Assert.assertEquals
import org.junit.Test

class TSTimeUtilsTest {
@Test
fun `test computeTimestamp90kHz with very large timestamp avoiding overflow`() {
// 400,000,000,000 µs (about 111 hours)
val timestamp = 400_000_000_000L
val expectedPcrBase = 1_640_261_632L
assertEquals(expectedPcrBase, TSTimeUtils.computeTimestamp90kHz(timestamp))
}

@Test
fun `test computePcrExt with very large timestamp avoiding overflow`() {
val timestamp = 400_000_000_000L
val expectedPcrExt = 0L
assertEquals(expectedPcrExt, TSTimeUtils.computePcrExt(timestamp))
}
}
Loading