diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/MP4Muxer.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/MP4Muxer.kt index a44aa22f6..118a667e0 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/MP4Muxer.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/MP4Muxer.kt @@ -18,37 +18,52 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4 import io.github.thibaultbee.streampack.data.Config import io.github.thibaultbee.streampack.internal.data.Frame import io.github.thibaultbee.streampack.internal.data.Packet +import io.github.thibaultbee.streampack.internal.interfaces.IOrientationProvider import io.github.thibaultbee.streampack.internal.muxers.IMuxer import io.github.thibaultbee.streampack.internal.muxers.IMuxerListener import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.FileTypeBox -import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.MediaDataBox -import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.MovieBox -import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.MovieHeaderBox -import io.github.thibaultbee.streampack.internal.utils.isVideo +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.* +import io.github.thibaultbee.streampack.internal.utils.TimeUtils +import io.github.thibaultbee.streampack.internal.utils.extensions.isAudio +import io.github.thibaultbee.streampack.internal.utils.extensions.isVideo import java.nio.ByteBuffer class MP4Muxer(initialListener: IMuxerListener? = null) : IMuxer { override val helper = MP4MuxerHelper() - override var manageVideoOrientation: Boolean = false + override lateinit var orientationProvider: IOrientationProvider override var listener: IMuxerListener? = initialListener - private val tracks = mutableListOf() - private var fileOffset: Long = 0 + private val tracks = mutableListOf() + private val hasAudio: Boolean + get() = tracks.any { it.config.mimeType.isAudio } + private val hasVideo: Boolean + get() = tracks.any { it.config.mimeType.isVideo } + + private var currentSegment: Segment? = null + + private var dataOffset: Long = 0 + private var sequenceNumber = DEFAULT_SEQUENCE_NUMBER + private var hasWriteMoov = false override fun encode(frame: Frame, streamPid: Int) { - getTrack(streamPid).write(frame, fileOffset) + synchronized(this) { + if (mustWriteSegment(frame)) { + writeSegment() + } + currentSegment!!.add(frame, streamPid) + } } override fun addStreams(streamsConfig: List): Map { - val newTrack = mutableListOf() + val newTracks = mutableListOf() streamsConfig.forEach { config -> - newTrack.add(MP4Track(getNewId(), config, 100) { buffer -> writeBuffer(buffer) }) + val track = Track(getNewId(), config, DEFAULT_TIMESCALE) + newTracks.add(track) + tracks.add(track) } - tracks.addAll(newTrack) - val streamMap = mutableMapOf() - newTrack.forEach { streamMap[it.config] = it.id } + newTracks.forEach { streamMap[it.config] = it.id } return streamMap } @@ -56,25 +71,16 @@ class MP4Muxer(initialListener: IMuxerListener? = null) : IMuxer { } override fun startStream() { - writeBuffer(FileTypeBox().write()) - writeBuffer(MediaDataBox().write()) + writeBuffer(FileTypeBox().toByteBuffer()) + currentSegment = createNewSegment(MovieBoxFactory(DEFAULT_TIMESCALE)) } override fun stopStream() { - tracks.forEach { it.writeLastChunk() } - val timescale = try { - tracks.first { it.config.mimeType.isVideo }.timescale - } catch (e: NoSuchElementException) { - tracks[0].timescale - } - val mvhd = MovieHeaderBox( - version = 0, - duration = tracks.maxOf { it.totalDuration * timescale / it.timescale }, - timescale = timescale, - nextTrackId = tracks[0].id - ) - val moov = MovieBox(mvhd, tracks.map { it.getTrak() }) - writeBuffer(moov.write()) + writeSegment(false) + // TODO write mfra + sequenceNumber = DEFAULT_SEQUENCE_NUMBER + hasWriteMoov = false + currentSegment = null } override fun release() { @@ -91,8 +97,21 @@ class MP4Muxer(initialListener: IMuxerListener? = null) : IMuxer { throw IndexOutOfBoundsException("No empty ID left") } - private fun getTrack(id: Int): MP4Track { - return tracks.first { it.id == id } + private fun createNewSegment(movieBoxFactory: AbstractMovieBoxFactory): Segment { + return Segment( + tracks, + movieBoxFactory + ) { buffer -> writeBuffer(buffer) } + } + + private fun writeSegment(createNewFragment: Boolean = true) { + if (currentSegment?.dataSize == 0) { + return + } + currentSegment?.write(dataOffset) + if (createNewFragment) { + currentSegment = createNewSegment(MovieFragmentBoxFactory(sequenceNumber++)) + } } private fun writeBuffer(buffer: ByteBuffer) { @@ -100,7 +119,20 @@ class MP4Muxer(initialListener: IMuxerListener? = null) : IMuxer { val packet = Packet(buffer, 0) listener?.let { it.onOutputFrame(packet) - fileOffset += size + dataOffset += size } } + + private fun mustWriteSegment(frame: Frame): Boolean { + return if (hasVideo) { + frame.isVideo && frame.isKeyFrame + } else { + true //TODO: only write every X frames + } + } + + companion object { + private const val DEFAULT_SEQUENCE_NUMBER = 1 + private const val DEFAULT_TIMESCALE = TimeUtils.TIME_SCALE + } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/MP4Track.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/MP4Track.kt deleted file mode 100644 index f5b230e3f..000000000 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/MP4Track.kt +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2022 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.internal.muxers.mp4 - -import android.media.MediaFormat -import android.util.Size -import io.github.thibaultbee.streampack.data.AudioConfig -import io.github.thibaultbee.streampack.data.Config -import io.github.thibaultbee.streampack.data.VideoConfig -import io.github.thibaultbee.streampack.internal.data.Frame -import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.* -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.Chunk -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.DecodingTime -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleToChunk -import io.github.thibaultbee.streampack.internal.utils.TimeUtils -import io.github.thibaultbee.streampack.internal.utils.av.video.avc.AVCDecoderConfigurationRecord -import java.nio.ByteBuffer - -class MP4Track( - val id: Int, - val config: Config, - private val numOfSamplePerChunk: Int, - var onNewChunk: (ByteBuffer) -> Unit -) { - private var firstTimestamp: Long = 0 - private var _totalDuration: Long = 0 - val totalDuration: Long - get() = _totalDuration - val timescale = TimeUtils.TIME_SCALE - - private var frameId: Int = 1 - - private var onlySyncFrame = true - private val syncFrameList = mutableListOf() - private val extradata = mutableListOf>() - - private val sampleDts = mutableListOf() - - private val sampleSizes = mutableListOf() - - private val chunkOffsets = mutableListOf() - - private var currentChunk = Chunk(firstChunk = 1, sampleDescriptionId = 1) - private val sampleToChunks = mutableListOf() - - init { - require(id != 0) { "id must be greater than 0" } - } - - fun write(frame: Frame, frameOffset: Long) { - frame.extra?.let { - extradata.add(it) - } - - if (frame.isKeyFrame) { - syncFrameList.add(frameId) - } else { - onlySyncFrame = false - } - frameId++ - - if (firstTimestamp == 0L) { - firstTimestamp = frame.pts - } - _totalDuration = (frame.pts - firstTimestamp) * timescale / TimeUtils.TIME_SCALE - - frame.dts?.let { - sampleDts.add(it) - } ?: sampleDts.add(frame.pts) - - sampleSizes.add(frame.buffer.remaining()) - - if (currentChunk.size == numOfSamplePerChunk) { - writeCurrentChunk() - chunkOffsets.add(frameOffset) - currentChunk = Chunk( - firstChunk = currentChunk.firstChunk + 1, - sampleDescriptionId = currentChunk.sampleDescriptionId - ) - } else { - currentChunk.add(frame) - } - } - - private fun writeCurrentChunk() { - sampleToChunks.add(currentChunk.toSampleToChunk()) - val byteBuffer = currentChunk.toByteBuffer() - onNewChunk(byteBuffer) - } - - fun writeLastChunk() { - if (currentChunk.size > 0) { - writeCurrentChunk() - } - } - - fun getTrak(): TrackBox { - val tkhd = createTrackHeaderBox(config) - val mdhd = - MediaHeaderBox(version = 0, timescale = timescale, duration = totalDuration) - val hdlr = config.createHandlerBox() - val mhd = config.createTypeMediaHeaderBox() - val dinf = DataInformationBox(DataReferenceBox(DataEntryUrlBox())) - val stsd = createSampleDescriptionBox() - val stts = createTimeToSampleBox() - val stss = createSyncSampleBox() - val stsc = createSampleToChunkBox() - val stsz = SampleSizeBox(sampleSizeEntries = sampleSizes) - val co = ChunkLargeOffsetBox(chunkOffsets) - - val stbl = SampleTableBox(stsd, stts, stss, stsc, stsz, co) - val minf = MediaInformationBox(mhd, dinf, stbl) - val mdia = MediaBox(mdhd, hdlr, minf) - return TrackBox(tkhd, mdia) - } - - private fun createTrackHeaderBox(config: Config): TrackHeaderBox { - val resolution = when (config) { - is AudioConfig -> Size(0, 0) - is VideoConfig -> config.resolution - else -> throw IllegalArgumentException("Unsupported config") - } - val volume = when (config) { - is AudioConfig -> 1.0f - else -> 0.0f - } - return TrackHeaderBox( - id = id, - version = 0, - flags = listOf( - TrackHeaderBox.TrackFlag.ENABLED, - TrackHeaderBox.TrackFlag.IN_MOVIE, - TrackHeaderBox.TrackFlag.IN_PREVIEW - ), - duration = totalDuration, - volume = volume, - resolution = resolution - ) - } - - private fun createSampleToChunkBox(): SampleToChunkBox { - val sampleToChunkEntries = mutableListOf() - sampleToChunks.forEachIndexed { index, sampleToChunk -> - if (index != 0) { - val previousSampleToChunk = sampleToChunks[index - 1] - if ((sampleToChunk.samplesPerChunk != previousSampleToChunk.samplesPerChunk) - || (sampleToChunk.sampleDescriptionId != previousSampleToChunk.sampleDescriptionId) - ) { - sampleToChunkEntries.add(sampleToChunk) - } - } - } - return SampleToChunkBox(sampleToChunkEntries) - } - - private fun createTimeToSampleBox(): TimeToSampleBox { - val sampleDurations = mutableListOf() - sampleDts.forEachIndexed { index, dts -> - if (index != 0) { - sampleDurations.add((dts - sampleDts[index - 1]).toInt()) - } - } - val sampleEntries = mutableListOf() - var count = 1 - sampleDurations.forEachIndexed { index, duration -> - if (index != 0) { - val previousDuration = sampleDurations[index - 1] - if (duration == previousDuration) { - count++ - } else { - sampleEntries.add(DecodingTime(count, previousDuration)) - count = 1 - } - } - } - return TimeToSampleBox(sampleEntries) - } - - private fun createSampleDescriptionBox(): SampleDescriptionBox { - val sampleEntry = when (config.mimeType) { - MediaFormat.MIMETYPE_VIDEO_AVC -> { - (config as VideoConfig) - AVCSampleEntry( - config.resolution, - avcc = AVCConfigurationBox( - AVCDecoderConfigurationRecord.fromParameterSets( - extradata[0][0], - extradata[0][1] - ) - ), - ) - } - MediaFormat.MIMETYPE_AUDIO_AAC -> { - (config as AudioConfig) - MP4AudioSampleEntry( - AudioConfig.getNumberOfChannels(config.channelConfig).toShort(), - AudioConfig.getNumOfBytesPerSample(config.byteFormat).toShort(), - config.sampleRate, - edts = extradata[0][0] - ) - } - else -> throw IllegalArgumentException("Unsupported mimeType") - } - return SampleDescriptionBox(sampleEntry) - } - - private fun createSyncSampleBox(): SyncSampleBox? { - return if (!onlySyncFrame) { - SyncSampleBox(syncFrameList) - } else { - null - } - } -} - -private fun Config.createTypeMediaHeaderBox(): TypeMediaHeaderBox { - return when (this) { - is AudioConfig -> SoundMediaHeaderBox() - is VideoConfig -> VideoMediaHeaderBox() - else -> throw IllegalArgumentException("Unsupported config") - } -} - -private fun Config.createHandlerBox(): HandlerBox { - return when (this) { - is AudioConfig -> HandlerBox(HandlerBox.HandlerType.SOUND, "SoundHandler") - is VideoConfig -> HandlerBox(HandlerBox.HandlerType.VIDEO, "VideoHandler") - else -> throw IllegalArgumentException("Unsupported config") - } -} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBox.kt index 41c7b5a38..a003bc500 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBox.kt @@ -21,8 +21,8 @@ import java.nio.ByteBuffer class AVCConfigurationBox(private val config: AVCDecoderConfigurationRecord) : Box("avcC") { override val size: Int = super.size + config.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - config.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) + config.write(output) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBox.kt index c8a38bddb..26ae599a4 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBox.kt @@ -24,10 +24,10 @@ class BitRateBox( ) : Box("btrt") { override val size: Int = super.size + 12 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(bufferSizeDB) - buffer.putInt(maxBitrate) - buffer.putInt(avgBitrate) + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(bufferSizeDB) + output.putInt(maxBitrate) + output.putInt(avgBitrate) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/Box.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/Box.kt index 7168cfcf1..760c99f71 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/Box.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/Box.kt @@ -15,12 +15,14 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.putInt24 -import io.github.thibaultbee.streampack.internal.utils.putString +import io.github.thibaultbee.streampack.internal.utils.av.ByteBufferWriter +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt24 +import io.github.thibaultbee.streampack.internal.utils.extensions.putString import java.nio.ByteBuffer -sealed class Box(private val type: String, private val isCompact: Boolean = true) { - open val size: Int = 8 + if (!isCompact) { +sealed class Box(private val type: String, private val isCompact: Boolean = true) : + ByteBufferWriter() { + override val size: Int = 8 + if (!isCompact) { 8 } else { 0 @@ -30,14 +32,14 @@ sealed class Box(private val type: String, private val isCompact: Boolean = true 0 } - open fun write(buffer: ByteBuffer) { + override fun write(output: ByteBuffer) { if (isCompact) { - buffer.putInt(size) + output.putInt(size) } else { - buffer.putInt(1) + output.putInt(1) } - buffer.putString(type) + output.putString(type) if (!isCompact) { throw NotImplementedError("Large size not implemented yet") } @@ -45,16 +47,6 @@ sealed class Box(private val type: String, private val isCompact: Boolean = true throw NotImplementedError("UUID not implemented yet") } } - - fun write(): ByteBuffer { - val buffer = ByteBuffer.allocateDirect(size) - - write(buffer) - - buffer.rewind() - - return buffer - } } abstract class FullBox( @@ -65,9 +57,9 @@ abstract class FullBox( ) : Box(type, isCompact) { override val size: Int = super.size + 4 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.put(version) - buffer.putInt24(flags) + override fun write(output: ByteBuffer) { + super.write(output) + output.put(version) + output.putInt24(flags) } } diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBox.kt index b3c699dcd..239109d79 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBox.kt @@ -17,24 +17,37 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes import java.nio.ByteBuffer -abstract class BaseChunkOffsetBox(type: String) : FullBox(type, 0, 0) +abstract class BaseChunkOffsetBox(type: String, protected var chunkOffsetEntries: List) : + FullBox(type, 0, 0) { + abstract fun addChunkOffset(chunkOffset: T) +} -class ChunkOffsetBox(private val chunkOffsetEntries: List) : BaseChunkOffsetBox("stco") { +class ChunkOffsetBox(chunkOffsetEntries: List) : + BaseChunkOffsetBox("stco", chunkOffsetEntries) { override val size: Int = super.size + 4 + 4 * chunkOffsetEntries.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(chunkOffsetEntries.size) - chunkOffsetEntries.forEach { buffer.putInt(it) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(chunkOffsetEntries.size) + chunkOffsetEntries.forEach { output.putInt(it) } + } + + override fun addChunkOffset(chunkOffset: Int) { + chunkOffsetEntries = chunkOffsetEntries.map { it + chunkOffset } } } -class ChunkLargeOffsetBox(private val chunkOffsetEntries: List) : BaseChunkOffsetBox("co64") { +class ChunkLargeOffsetBox(chunkOffsetEntries: List) : + BaseChunkOffsetBox("co64", chunkOffsetEntries) { override val size: Int = super.size + 4 + 8 * chunkOffsetEntries.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(chunkOffsetEntries.size) - chunkOffsetEntries.forEach { buffer.putLong(it) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(chunkOffsetEntries.size) + chunkOffsetEntries.forEach { output.putLong(it) } + } + + override fun addChunkOffset(chunkOffset: Long) { + chunkOffsetEntries = chunkOffsetEntries.map { it + chunkOffset } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/CleanApertureBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/CleanApertureBox.kt index a59ff0882..8c19a61e7 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/CleanApertureBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/CleanApertureBox.kt @@ -20,8 +20,8 @@ import java.nio.ByteBuffer class CleanApertureBox : Box("clap") { override val size: Int = super.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) throw NotImplementedError("CleanApertureBox is not implemented yet") } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataEntryBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataEntryBox.kt index d8eaa7a9e..093bdee46 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataEntryBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataEntryBox.kt @@ -15,7 +15,7 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.putString +import io.github.thibaultbee.streampack.internal.utils.extensions.putString import java.nio.ByteBuffer sealed class DataEntryBox(type: String, flags: Int) : FullBox(type, 0, flags) @@ -30,11 +30,11 @@ class DataEntryUrlBox(private val location: String? = null) : ) { override val size: Int = super.size + (location?.let { it.length + 1 } ?: 0) - override fun write(buffer: ByteBuffer) { - super.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) location?.let { - buffer.putString(it) - buffer.put(0.toByte()) + output.putString(it) + output.put(0.toByte()) } } } @@ -43,13 +43,13 @@ class DataEntryUrnBox(private val name: String, private val location: String? = DataEntryBox("urn ", flags) { override val size: Int = super.size + name.length + 1 + (location?.let { it.length + 1 } ?: 0) - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putString(name) - buffer.put(0.toByte()) + override fun write(output: ByteBuffer) { + super.write(output) + output.putString(name) + output.put(0.toByte()) location?.let { - buffer.putString(it) - buffer.put(0.toByte()) + output.putString(it) + output.put(0.toByte()) } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBox.kt index 8b5e3cf1b..163f17b71 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBox.kt @@ -20,8 +20,8 @@ import java.nio.ByteBuffer class DataInformationBox(private val dref: DataReferenceBox) : Box("dinf") { override val size: Int = super.size + dref.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - dref.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) + dref.write(output) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBox.kt index a1808f129..879184c74 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBox.kt @@ -26,9 +26,9 @@ class DataReferenceBox(private val entries: List) : FullBox("dref" override val size: Int = super.size + 4 + entries.sumOf { it.size } - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(entries.size) - entries.forEach { it.write(buffer) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(entries.size) + entries.forEach { it.write(output) } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ESDSBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ESDSBox.kt new file mode 100644 index 000000000..fb4c9653c --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ESDSBox.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.utils.av.descriptors.ESDescriptor +import java.nio.ByteBuffer + +class ESDSBox(private val esDescriptor: ESDescriptor) : FullBox("esds", 0, 0) { + override val size: Int = super.size + esDescriptor.size + + override fun write(output: ByteBuffer) { + super.write(output) + esDescriptor.write(output) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBox.kt index ae159f1b8..ec8d20cc1 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBox.kt @@ -15,7 +15,7 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.putString +import io.github.thibaultbee.streampack.internal.utils.extensions.putString import java.nio.ByteBuffer class FileTypeBox( @@ -30,10 +30,10 @@ class FileTypeBox( override val size: Int = super.size + 8 + compatibleBrands.sumOf { it.length } - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putString(majorBrand) - buffer.putInt(minorVersion) - compatibleBrands.forEach { buffer.putString(it) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putString(majorBrand) + output.putInt(minorVersion) + compatibleBrands.forEach { output.putString(it) } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBox.kt index 992a3b550..7adc4a0f6 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBox.kt @@ -15,19 +15,19 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.putString +import io.github.thibaultbee.streampack.internal.utils.extensions.putString import java.nio.ByteBuffer class HandlerBox(private val type: HandlerType, private val name: String) : FullBox("hdlr", 0, 0) { override val size: Int = super.size + type.value.length + name.length + 17 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(0) // pre_defined - buffer.putString(type.value) - buffer.put(ByteArray(12)) // reserved - buffer.putString(name) - buffer.put(0.toByte()) // Null terminated string + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(0) // pre_defined + output.putString(type.value) + output.put(ByteArray(12)) // reserved + output.putString(name) + output.put(0.toByte()) // Null terminated string } enum class HandlerType(val value: String) { diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaBox.kt index 98e3967eb..dc328b754 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaBox.kt @@ -20,14 +20,14 @@ import java.nio.ByteBuffer class MediaBox( private val mdhd: MediaHeaderBox, private val hdlr: HandlerBox, - private val minf: MediaInformationBox + val minf: MediaInformationBox ) : Box("mdia") { override val size: Int = super.size + mdhd.size + hdlr.size + minf.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - mdhd.write(buffer) - hdlr.write(buffer) - minf.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) + mdhd.write(output) + hdlr.write(output) + minf.write(output) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaDataBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaDataBox.kt index 8de3b116c..07d6f9f2a 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaDataBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaDataBox.kt @@ -15,4 +15,15 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -class MediaDataBox : Box("mdat") \ No newline at end of file +import java.nio.ByteBuffer + +class MediaDataBox(dataSize: Int) : Box("mdat") { + override val size: Int = super.size + dataSize + + fun writeHeader(): ByteBuffer { + val buffer = ByteBuffer.allocateDirect(super.size) + write(buffer) + buffer.rewind() + return buffer + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaHeaderBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaHeaderBox.kt index 6a7ad1184..da2809281 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaHeaderBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaHeaderBox.kt @@ -16,7 +16,7 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes import io.github.thibaultbee.streampack.internal.muxers.mp4.utils.TimeUtils -import io.github.thibaultbee.streampack.internal.utils.putInt +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt import java.nio.ByteBuffer class MediaHeaderBox( @@ -33,26 +33,26 @@ class MediaHeaderBox( 16 } + 4 - override fun write(buffer: ByteBuffer) { - super.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) when (version) { 1.toByte() -> { - buffer.putLong(creationTime) - buffer.putLong(modificationTime) - buffer.putInt(timescale) - buffer.putLong(duration) + output.putLong(creationTime) + output.putLong(modificationTime) + output.putInt(timescale) + output.putLong(duration) } 0.toByte() -> { - buffer.putInt(creationTime) - buffer.putInt(modificationTime) - buffer.putInt(timescale) - buffer.putInt(duration) + output.putInt(creationTime) + output.putInt(modificationTime) + output.putInt(timescale) + output.putInt(duration) } else -> { throw IllegalArgumentException("Version must be 0 or 1") } } - buffer.putShort(language) - buffer.putShort(0) // pre_defined + output.putShort(language) + output.putShort(0) // pre_defined } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaInformationBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaInformationBox.kt index 6e733a630..07785baa7 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaInformationBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MediaInformationBox.kt @@ -20,15 +20,15 @@ import java.nio.ByteBuffer class MediaInformationBox( private val mhd: TypeMediaHeaderBox, private val dinf: DataInformationBox, - private val stbl: SampleTableBox + val stbl: SampleTableBox ) : Box("minf") { override val size: Int = super.size + mhd.size + dinf.size + stbl.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - mhd.write(buffer) - dinf.write(buffer) - stbl.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) + mhd.write(output) + dinf.write(output) + stbl.write(output) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieBox.kt index 463594f40..b49cdf1f0 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieBox.kt @@ -17,17 +17,22 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes import java.nio.ByteBuffer -class MovieBox(private val mvhd: MovieHeaderBox, private val trak: List) : Box("moov") { +class MovieBox( + private val mvhd: MovieHeaderBox, + val trak: List, + private val mvex: MovieExtendsBox? = null +) : Box("moov") { init { require(trak.isNotEmpty()) { "At least one track is required" } require(trak.distinctBy { it.tkhd.id }.size == trak.size) { "All tracks must have different trackId" } } - override val size: Int = super.size + mvhd.size + trak.sumOf { it.size } + override val size: Int = super.size + mvhd.size + trak.sumOf { it.size } + (mvex?.size ?: 0) - override fun write(buffer: ByteBuffer) { - super.write(buffer) - mvhd.write(buffer) - trak.forEach { it.write(buffer) } + override fun write(output: ByteBuffer) { + super.write(output) + mvhd.write(output) + trak.forEach { it.write(output) } + mvex?.write(output) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieExtendsBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieExtendsBox.kt new file mode 100644 index 000000000..8049b77ea --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieExtendsBox.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import java.nio.ByteBuffer + +class MovieExtendsBox( + private val trex: List, +) : Box("mvex") { + override val size: Int = super.size + trex.sumOf { it.size } + + override fun write(output: ByteBuffer) { + super.write(output) + trex.forEach { it.write(output) } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentBox.kt new file mode 100644 index 000000000..44a87c86f --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentBox.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import java.nio.ByteBuffer + +class MovieFragmentBox( + private val mfhd: MovieFragmentHeaderBox, + private val traf: List = emptyList() +) : Box("moof") { + override val size: Int = super.size + mfhd.size + traf.sumOf { it.size } + + override fun write(output: ByteBuffer) { + super.write(output) + mfhd.write(output) + traf.forEach { + it.write(output) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/DecodingTime.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentHeaderBox.kt similarity index 66% rename from core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/DecodingTime.kt rename to core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentHeaderBox.kt index 66d3b201f..4bed1921d 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/DecodingTime.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentHeaderBox.kt @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.thibaultbee.streampack.internal.muxers.mp4.models +package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes import java.nio.ByteBuffer -data class DecodingTime(val count: Int, val delta: Int) +class MovieFragmentHeaderBox(private val sequenceNumber: Int) : FullBox("mfhd", 0, 0) { + override val size: Int = super.size + 4 -fun ByteBuffer.put(d: DecodingTime) { - putInt(d.count) - putInt(d.delta) + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(sequenceNumber) + } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentRandomAccessBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentRandomAccessBox.kt new file mode 100644 index 000000000..aae4f9947 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentRandomAccessBox.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import java.nio.ByteBuffer + +class MovieFragmentRandomAccessBox( + private val traf: List +) : + Box("mfra") { + constructor( + traf: TrackFragmentRandomAccessBox, + ) : this(listOf(traf)) + + private val mfro = MovieFragmentRandomAccessOffsetBox(traf.sumOf { it.size }) + + override val size: Int = super.size + traf.sumOf { it.size } + mfro.size + + override fun write(output: ByteBuffer) { + super.write(output) + traf.forEach { it.write(output) } + mfro.write(output) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentRandomAccessOffsetBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentRandomAccessOffsetBox.kt new file mode 100644 index 000000000..e6f043e8a --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentRandomAccessOffsetBox.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import java.nio.ByteBuffer + +class MovieFragmentRandomAccessOffsetBox(private val mfraSize: Int, version: Byte = 0) : + FullBox("mfro", version, 0) { + override val size: Int = super.size + 4 + + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(mfraSize) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBox.kt index ad2b2d96d..2bea029c9 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBox.kt @@ -16,10 +16,10 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes import io.github.thibaultbee.streampack.internal.muxers.mp4.utils.TimeUtils -import io.github.thibaultbee.streampack.internal.utils.put3x3Matrix -import io.github.thibaultbee.streampack.internal.utils.putFixed1616 -import io.github.thibaultbee.streampack.internal.utils.putFixed88 -import io.github.thibaultbee.streampack.internal.utils.putInt +import io.github.thibaultbee.streampack.internal.utils.extensions.put3x3Matrix +import io.github.thibaultbee.streampack.internal.utils.extensions.putFixed1616 +import io.github.thibaultbee.streampack.internal.utils.extensions.putFixed88 +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt import java.nio.ByteBuffer class MovieHeaderBox( @@ -53,28 +53,28 @@ class MovieHeaderBox( 16 } + 80 - override fun write(buffer: ByteBuffer) { - super.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) when (version) { 1.toByte() -> { - buffer.putLong(creationTime) - buffer.putLong(modificationTime) - buffer.putInt(timescale) - buffer.putLong(duration) + output.putLong(creationTime) + output.putLong(modificationTime) + output.putInt(timescale) + output.putLong(duration) } 0.toByte() -> { - buffer.putInt(creationTime) - buffer.putInt(modificationTime) - buffer.putInt(timescale) - buffer.putInt(duration) + output.putInt(creationTime) + output.putInt(modificationTime) + output.putInt(timescale) + output.putInt(duration) } else -> throw IllegalArgumentException("version must be 0 or 1") } - buffer.putFixed1616(rate) - buffer.putFixed88(volume) - buffer.put(ByteArray(10)) // reserved - buffer.put3x3Matrix(transformationMatrix) - buffer.put(ByteArray(24)) // pre_defined - buffer.putInt(nextTrackId) + output.putFixed1616(rate) + output.putFixed88(volume) + output.put(ByteArray(10)) // reserved + output.put3x3Matrix(transformationMatrix) + output.put(ByteArray(24)) // pre_defined + output.putInt(nextTrackId) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/PixelAspectRatioBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/PixelAspectRatioBox.kt index 85acea426..053642647 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/PixelAspectRatioBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/PixelAspectRatioBox.kt @@ -20,9 +20,9 @@ import java.nio.ByteBuffer class PixelAspectRatioBox(private val hSpacing: Int, private val vSpacing: Int) : Box("pasp") { override val size: Int = super.size + 8 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(hSpacing) - buffer.putInt(vSpacing) + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(hSpacing) + output.putInt(vSpacing) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleDescriptionBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleDescriptionBox.kt index 747d719ef..aa7966e2e 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleDescriptionBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleDescriptionBox.kt @@ -28,9 +28,9 @@ class SampleDescriptionBox(private val sampleEntries: List) : FullB override val size: Int = super.size + 4 + sampleEntries.sumOf { it.size } - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(sampleEntries.size) - sampleEntries.forEach { it.write(buffer) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(sampleEntries.size) + sampleEntries.forEach { it.write(output) } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleEntry.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleEntry.kt index 16d781de5..0e3c9f6a6 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleEntry.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleEntry.kt @@ -16,20 +16,20 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes import android.util.Size -import io.github.thibaultbee.streampack.internal.utils.put -import io.github.thibaultbee.streampack.internal.utils.putFixed1616 -import io.github.thibaultbee.streampack.internal.utils.putShort -import io.github.thibaultbee.streampack.internal.utils.putString +import io.github.thibaultbee.streampack.internal.utils.extensions.put +import io.github.thibaultbee.streampack.internal.utils.extensions.putFixed1616 +import io.github.thibaultbee.streampack.internal.utils.extensions.putShort +import io.github.thibaultbee.streampack.internal.utils.extensions.putString import java.nio.ByteBuffer sealed class SampleEntry(type: String, private val dataReferenceId: Short, val version: Int = 0) : Box(type) { override val size: Int = super.size + 8 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.put(ByteArray(6)) - buffer.putShort(dataReferenceId) + override fun write(output: ByteBuffer) { + super.write(output) + output.put(ByteArray(6)) + output.putShort(dataReferenceId) } } @@ -53,26 +53,26 @@ open class VisualSampleEntry( override val size: Int = super.size + 70 + otherBoxes.sumOf { it.size } + (clap?.size ?: 0) + (pasp?.size ?: 0) - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.put(ByteArray(16)) // pre_defined + reserved + pre_defined - buffer.putShort(resolution.width) - buffer.putShort(resolution.height) - buffer.putFixed1616(horizontalResolution) - buffer.putFixed1616(verticalResolution) - buffer.putInt(0) // reserved - buffer.putShort(frameCount) // reserved + override fun write(output: ByteBuffer) { + super.write(output) + output.put(ByteArray(16)) // pre_defined + reserved + pre_defined + output.putShort(resolution.width) + output.putShort(resolution.height) + output.putFixed1616(horizontalResolution) + output.putFixed1616(verticalResolution) + output.putInt(0) // reserved + output.putShort(frameCount) // reserved compressorName?.let { - buffer.put(it.length) - buffer.putString(it) + output.put(it.length) + output.putString(it) } - buffer.put(ByteArray(32 - (compressorName?.let { it.length + 1 /* size */ } + output.put(ByteArray(32 - (compressorName?.let { it.length + 1 /* size */ } ?: 0))) // reserved - buffer.putShort(depth) - buffer.putShort(-1) // pre_defined - otherBoxes.forEach { it.write(buffer) } - clap?.write(buffer) - pasp?.write(buffer) + output.putShort(depth) + output.putShort(-1) // pre_defined + otherBoxes.forEach { it.write(output) } + clap?.write(output) + pasp?.write(output) } } @@ -108,7 +108,7 @@ class MP4AudioSampleEntry( channelCount: Short, sampleSize: Short, sampleRate: Int, - private val edts: ByteBuffer, + esds: ESDSBox, btrt: BitRateBox? = null, ) : AudioSampleEntry( @@ -117,13 +117,9 @@ class MP4AudioSampleEntry( channelCount, sampleSize, sampleRate, - mutableListOf().apply { btrt?.let { add(it) } }) { - override val size: Int = super.size + edts.remaining() - - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.put(edts) - } + mutableListOf(esds).apply { + btrt?.let { add(it) } + }) { } open class AudioSampleEntry( @@ -133,17 +129,17 @@ open class AudioSampleEntry( private val sampleSize: Short, private val sampleRate: Int, private val otherBoxes: List = emptyList(), -) : SampleEntry(type, 3, version) { +) : SampleEntry(type, 1, version) { override val size: Int = super.size + 20 + otherBoxes.sumOf { it.size } - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putShort(version) - buffer.put(ByteArray(6)) // reserved - buffer.putShort(channelCount) - buffer.putShort(sampleSize) - buffer.putInt(0) // pre_defined + reserved - buffer.putFixed1616(sampleRate) - otherBoxes.forEach { it.write(buffer) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putShort(version) + output.put(ByteArray(6)) // reserved + output.putShort(channelCount) + output.putShort(sampleSize) + output.putInt(0) // pre_defined + reserved + output.putInt(sampleRate shl 16) + otherBoxes.forEach { it.write(output) } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBox.kt index 069f64e62..92597914f 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBox.kt @@ -29,10 +29,10 @@ class SampleSizeBox( override val size: Int = super.size + 8 + (sampleSizeEntries?.size ?: 0) * 4 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(sampleSize) - buffer.putInt(sampleSizeEntries?.size ?: 0) - sampleSizeEntries?.forEach { buffer.putInt(it) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(sampleSize) + output.putInt(sampleSizeEntries?.size ?: 0) + sampleSizeEntries?.forEach { output.putInt(it) } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleTableBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleTableBox.kt index 756e29ea9..bfe5905ae 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleTableBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleTableBox.kt @@ -23,18 +23,18 @@ class SampleTableBox( private val stss: SyncSampleBox? = null, private val stsc: SampleToChunkBox, private val stsz: SampleSizeBox, - private val co: BaseChunkOffsetBox + val co64: ChunkLargeOffsetBox ) : Box("stbl") { override val size: Int = - super.size + stsd.size + stts.size + (stss?.size ?: 0) + stsc.size + stsz.size + co.size + super.size + stsd.size + stts.size + (stss?.size ?: 0) + stsc.size + stsz.size + co64.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - stsd.write(buffer) - stts.write(buffer) - stss?.write(buffer) - stsc.write(buffer) - stsz.write(buffer) - co.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) + stsd.write(output) + stts.write(output) + stss?.write(output) + stsc.write(output) + stsz.write(output) + co64.write(output) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBox.kt index f6fd3d7ab..f96e17642 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBox.kt @@ -15,16 +15,26 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleToChunk -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.put import java.nio.ByteBuffer -class SampleToChunkBox(private val chunkEntries: List) : FullBox("stsc", 0, 0) { +class SampleToChunkBox(private val chunkEntries: List) : FullBox("stsc", 0, 0) { override val size: Int = super.size + 4 + 12 * chunkEntries.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(chunkEntries.size) - chunkEntries.forEach { buffer.put(it) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(chunkEntries.size) + chunkEntries.forEach { output.put(it) } + } + + open class Entry( + val firstChunk: Int, + val sampleDescriptionId: Int, + open val samplesPerChunk: Int = 0 + ) + + fun ByteBuffer.put(c: Entry) { + putInt(c.firstChunk) + putInt(c.samplesPerChunk) + putInt(c.sampleDescriptionId) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBox.kt index a748cc112..9be2e1341 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBox.kt @@ -20,9 +20,9 @@ import java.nio.ByteBuffer class SyncSampleBox(private val sampleNumber: List) : FullBox("stss", 0, 0) { override val size: Int = super.size + 4 + 4 * sampleNumber.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(sampleNumber.size) - sampleNumber.forEach { buffer.putInt(it) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(sampleNumber.size) + sampleNumber.forEach { output.putInt(it) } } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBox.kt index 855b48d48..8d3ae4ace 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBox.kt @@ -15,18 +15,23 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.DecodingTime -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.put import java.nio.ByteBuffer class TimeToSampleBox( - private val decodingTimes: List + private val decodingTimes: List ) : FullBox("stts", 0, 0) { override val size: Int = super.size + 4 + 8 * decodingTimes.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putInt(decodingTimes.size) - decodingTimes.forEach { buffer.put(it) } + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(decodingTimes.size) + decodingTimes.forEach { output.put(it) } + } + + data class Entry(val count: Int, val delta: Int) + + fun ByteBuffer.put(d: Entry) { + putInt(d.count) + putInt(d.delta) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackBox.kt index df8d063cf..6a2366e4b 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackBox.kt @@ -20,9 +20,9 @@ import java.nio.ByteBuffer class TrackBox(val tkhd: TrackHeaderBox, val mdia: MediaBox) : Box("trak") { override val size: Int = super.size + tkhd.size + mdia.size - override fun write(buffer: ByteBuffer) { - super.write(buffer) - tkhd.write(buffer) - mdia.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) + tkhd.write(output) + mdia.write(output) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackExtendsBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackExtendsBox.kt new file mode 100644 index 000000000..7b75e3613 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackExtendsBox.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleFlags +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.putInt +import java.nio.ByteBuffer + +class TrackExtendsBox( + private val id: Int, + private val defaultSampleDescriptionIndex: Int = 1, + private val defaultSampleDuration: Int = 0, + private val defaultSampleSize: Int = 0, + private val defaultSampleFlags: SampleFlags = SampleFlags(isNonSyncSample = false) +) : FullBox("trex", 0, 0) { + override val size: Int = super.size + 20 + + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(id) + output.putInt(defaultSampleDescriptionIndex) + output.putInt(defaultSampleDuration) + output.putInt(defaultSampleSize) + output.putInt(defaultSampleFlags) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBaseMediaDecodeTimeBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBaseMediaDecodeTimeBox.kt new file mode 100644 index 000000000..43ad92451 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBaseMediaDecodeTimeBox.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt +import java.nio.ByteBuffer + +class TrackFragmentBaseMediaDecodeTimeBox(private val baseMediaDecodeTime: Long, version: Byte) : + FullBox("tfdt", version, 0) { + override val size: Int = super.size + if (version == 1.toByte()) { + 8 + } else { + 4 + } + + override fun write(output: ByteBuffer) { + super.write(output) + when (version) { + 1.toByte() -> output.putLong(baseMediaDecodeTime) + 0.toByte() -> output.putInt(baseMediaDecodeTime) + else -> throw IllegalArgumentException("version must be 0 or 1") + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBox.kt new file mode 100644 index 000000000..cc99d9265 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBox.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import java.nio.ByteBuffer + +class TrackFragmentBox( + private val tfhd: TrackFragmentHeaderBox, + private val tfdt: TrackFragmentBaseMediaDecodeTimeBox? = null, + private val trun: TrackRunBox? = null +) : Box("traf") { + override val size: Int = + super.size + tfhd.size + (tfdt?.size ?: 0) + (trun?.size ?: 0) + + override fun write(output: ByteBuffer) { + super.write(output) + tfhd.write(output) + tfdt?.write(output) + trun?.write(output) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentHeaderBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentHeaderBox.kt new file mode 100644 index 000000000..999af5487 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentHeaderBox.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleFlags +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.putInt +import java.nio.ByteBuffer + +class TrackFragmentHeaderBox( + private val id: Int, + private val baseDataOffset: Long? = null, + private val sampleDescriptionIndex: Int? = null, + private val defaultSampleDuration: Int? = null, + private val defaultSampleSize: Int? = null, + private val defaultSampleFlags: SampleFlags? = null, + durationIsEmpty: Boolean = false +) : + FullBox( + "tfhd", + 0, + createFlags( + baseDataOffset, + sampleDescriptionIndex, + defaultSampleDuration, + defaultSampleSize, + defaultSampleFlags, + durationIsEmpty + ) + ) { + override val size: Int = + super.size + 4 + (baseDataOffset?.let { 8 } ?: 0) + (sampleDescriptionIndex?.let { 4 } + ?: 0) + (defaultSampleDuration?.let { 4 } ?: 0) + (defaultSampleSize?.let { 4 } + ?: 0) + (defaultSampleFlags?.let { 4 } ?: 0) + + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(id) + baseDataOffset?.let { output.putLong(it) } + sampleDescriptionIndex?.let { output.putInt(it) } + defaultSampleDuration?.let { output.putInt(it) } + defaultSampleSize?.let { output.putInt(it) } + defaultSampleFlags?.let { output.putInt(it) } + } + + enum class TrackFragmentFlag(val value: Int) { + BASE_DATA_OFFSET_PRESENT(0x000001), + SAMPLE_DESCRIPTION_INDEX_PRESENT(0x000002), + DEFAULT_SAMPLE_DURATION_PRESENT(0x000008), + DEFAULT_SAMPLE_SIZE_PRESENT(0x000010), + DEFAULT_SAMPLE_FLAGS_PRESENT(0x000020), + DURATION_IS_EMPTY(0x010000), + DEFAULT_BASE_IS_MOOF(0x020000) + } + + companion object { + private fun createFlags( + baseDataOffset: Long?, + sampleDescriptionIndex: Int?, + defaultSampleDuration: Int?, + defaultSampleSize: Int?, + defaultSampleFlags: SampleFlags?, + durationIsEmpty: Boolean + ): Int { + var flags = 0 + baseDataOffset?.let { flags += TrackFragmentFlag.BASE_DATA_OFFSET_PRESENT.value } + sampleDescriptionIndex?.let { flags += TrackFragmentFlag.SAMPLE_DESCRIPTION_INDEX_PRESENT.value } + defaultSampleDuration?.let { flags += TrackFragmentFlag.DEFAULT_SAMPLE_DURATION_PRESENT.value } + defaultSampleSize?.let { flags += TrackFragmentFlag.DEFAULT_SAMPLE_SIZE_PRESENT.value } + defaultSampleFlags?.let { flags += TrackFragmentFlag.DEFAULT_SAMPLE_FLAGS_PRESENT.value } + if (durationIsEmpty) flags += TrackFragmentFlag.DURATION_IS_EMPTY.value + return flags + } + } +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentRandomAccessBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentRandomAccessBox.kt new file mode 100644 index 000000000..bd14d5632 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentRandomAccessBox.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import java.nio.ByteBuffer + +class TrackFragmentRandomAccessBox(private val id: Int, private val entries: List) : + FullBox("tfra", 1, 0) { + override val size: Int = super.size + 4 + + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(id) + /** + * length_size_of_traf_num + length_size_of_trun_num + length_size_of_sample_num are forced to 0. + */ + output.putInt(0) // reserved + length_size_of_traf_num + length_size_of_trun_num + length_size_of_sample_num + + output.putInt(entries.size) + entries.forEach { + output.putLong(it.time) + output.putLong(it.moofOffset) + output.put(it.trafNumber) + output.put(it.trunNumber) + output.put(it.sampleNumber) + } + } + + data class Entry( + val time: Long, + val moofOffset: Long, + val trafNumber: Byte = 1, + val trunNumber: Byte = 1, + val sampleNumber: Byte = 1 + ) +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBox.kt index 5a985ba4d..dcb2bfbb5 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBox.kt @@ -17,10 +17,10 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes import android.util.Size import io.github.thibaultbee.streampack.internal.muxers.mp4.utils.TimeUtils -import io.github.thibaultbee.streampack.internal.utils.put3x3Matrix -import io.github.thibaultbee.streampack.internal.utils.putFixed1616 -import io.github.thibaultbee.streampack.internal.utils.putFixed88 -import io.github.thibaultbee.streampack.internal.utils.putInt +import io.github.thibaultbee.streampack.internal.utils.extensions.put3x3Matrix +import io.github.thibaultbee.streampack.internal.utils.extensions.putFixed1616 +import io.github.thibaultbee.streampack.internal.utils.extensions.putFixed88 +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt import java.nio.ByteBuffer class TrackHeaderBox( @@ -59,33 +59,33 @@ class TrackHeaderBox( 20 } + 60 - override fun write(buffer: ByteBuffer) { - super.write(buffer) + override fun write(output: ByteBuffer) { + super.write(output) when (version) { 1.toByte() -> { - buffer.putLong(creationTime) - buffer.putLong(modificationTime) - buffer.putInt(id) - buffer.putInt(0) // reserved - buffer.putLong(duration) + output.putLong(creationTime) + output.putLong(modificationTime) + output.putInt(id) + output.putInt(0) // reserved + output.putLong(duration) } 0.toByte() -> { - buffer.putInt(creationTime) - buffer.putInt(modificationTime) - buffer.putInt(id) - buffer.putInt(0) // reserved - buffer.putInt(duration) + output.putInt(creationTime) + output.putInt(modificationTime) + output.putInt(id) + output.putInt(0) // reserved + output.putInt(duration) } else -> throw IllegalArgumentException("version must be 0 or 1") } - buffer.put(ByteArray(8)) - buffer.putShort(layer) // layer - buffer.putShort(alternateGroup) // alternate_group - buffer.putFixed88(volume) - buffer.putShort(0) // reserved - buffer.put3x3Matrix(transformationMatrix) - buffer.putFixed1616(resolution.width.toFloat()) - buffer.putFixed1616(resolution.height.toFloat()) + output.put(ByteArray(8)) + output.putShort(layer) // layer + output.putShort(alternateGroup) // alternate_group + output.putFixed88(volume) + output.putShort(0) // reserved + output.put3x3Matrix(transformationMatrix) + output.putFixed1616(resolution.width.toFloat()) + output.putFixed1616(resolution.height.toFloat()) } enum class TrackFlag(val value: Int) { diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackRunBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackRunBox.kt new file mode 100644 index 000000000..dc758c372 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackRunBox.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleFlags +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.putInt +import java.nio.ByteBuffer + +class TrackRunBox( + version: Byte, + private val dataOffset: Int? = null, + private val firstSampleFlags: SampleFlags? = null, + private val entries: List = emptyList() +) : + FullBox("trun", version, createFlags(dataOffset, firstSampleFlags, entries)) { + init { + require(entries.all { it.duration != null } or entries.all { it.duration == null }) + require(entries.all { it.size != null } or entries.all { it.size == null }) + require(entries.all { it.flags != null } or entries.all { it.flags == null }) + require(entries.all { it.compositionTimeOffset != null } or entries.all { it.compositionTimeOffset == null }) + } + + override val size: Int = + super.size + 4 + (dataOffset?.let { 4 } ?: 0) + (firstSampleFlags?.let { 4 } + ?: 0) + entries.sumOf { + (it.duration?.let { 4 } ?: 0) + (it.size?.let { 4 } ?: 0) + (it.flags?.let { 4 } + ?: 0) + (it.compositionTimeOffset?.let { 4 } ?: 0) + } + + override fun write(output: ByteBuffer) { + super.write(output) + output.putInt(entries.size) + dataOffset?.let { output.putInt(it) } + firstSampleFlags?.let { output.putInt(it) } + entries.forEach { + output.put(it) + } + } + + enum class TrackRunFlag(val value: Int) { + DATA_OFFSET_PRESENT(0x000001), + FIRST_SAMPLE_FLAGS_PRESENT(0x000004), + SAMPLE_DURATION_PRESENT(0x000100), + SAMPLE_SIZE_PRESENT(0x000200), + SAMPLE_FLAGS_PRESENT(0x000400), + SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT(0x000800) + } + + companion object { + private fun createFlags( + dataOffset: Int?, + firstSampleFlags: SampleFlags?, + entries: List + ): Int { + var flags = 0 + dataOffset?.let { flags += TrackRunFlag.DATA_OFFSET_PRESENT.value } + firstSampleFlags?.let { flags += TrackRunFlag.FIRST_SAMPLE_FLAGS_PRESENT.value } + entries[0].duration?.let { flags += TrackRunFlag.SAMPLE_DURATION_PRESENT.value } + entries[0].size?.let { flags += TrackRunFlag.SAMPLE_SIZE_PRESENT.value } + entries[0].flags?.let { flags += TrackRunFlag.SAMPLE_FLAGS_PRESENT.value } + entries[0].compositionTimeOffset?.let { flags += TrackRunFlag.SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT.value } + return flags + } + } + + class Entry( + val duration: Int? = null, + val size: Int? = null, + val flags: Int? = null, + val compositionTimeOffset: Int? = null + ) + + private fun ByteBuffer.put(e: Entry) { + e.duration?.let { this.putInt(it) } + e.size?.let { this.putInt(it) } + e.flags?.let { this.putInt(it) } + e.compositionTimeOffset?.let { this.putInt(it) } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TypeMediaHeaderBox.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TypeMediaHeaderBox.kt index 325920233..6fd8d0b51 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TypeMediaHeaderBox.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TypeMediaHeaderBox.kt @@ -33,10 +33,10 @@ class VideoMediaHeaderBox( override val size: Int = super.size + 8 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putShort(graphicsMode) // graphics mode - opColor.forEach { buffer.putShort(it) } // op color + override fun write(output: ByteBuffer) { + super.write(output) + output.putShort(graphicsMode) // graphics mode + opColor.forEach { output.putShort(it) } // op color } } @@ -45,9 +45,9 @@ class SoundMediaHeaderBox( ) : TypeMediaHeaderBox("smhd", 0, 0) { override val size: Int = super.size + 4 - override fun write(buffer: ByteBuffer) { - super.write(buffer) - buffer.putShort(balance) - buffer.putShort(0) // reserved + override fun write(output: ByteBuffer) { + super.write(output) + output.putShort(balance) + output.putShort(0) // reserved } } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Chunk.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Chunk.kt index 50b0f4380..9ae13c7ba 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Chunk.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Chunk.kt @@ -16,35 +16,57 @@ package io.github.thibaultbee.streampack.internal.muxers.mp4.models import io.github.thibaultbee.streampack.internal.data.Frame -import io.github.thibaultbee.streampack.internal.utils.av.ByteBufferWriter import java.nio.ByteBuffer -data class Chunk(val firstChunk: Int, val sampleDescriptionId: Int) : ByteBufferWriter() { - private val samples = mutableListOf() - override val size: Int - get() = samples.sumOf { it.buffer.remaining() } +/** + * Storage for frames + */ +class Chunk(val id: Int) { + private val samples = mutableListOf() + val samplesPerChunk: Int get() = samples.size - fun add(frame: Frame) { - samples.add(frame) + val dataSize: Int + get() = samples.sumOf { it.frame.buffer.remaining() } + + val firstTimestamp: Long + get() = samples.minOf { it.frame.pts } + + private val lastTimestamp: Long + get() = samples.maxOf { it.frame.pts } + + val duration: Long + get() = lastTimestamp - firstTimestamp + + val onlySyncFrame: Boolean + get() = samples.all { it.frame.isKeyFrame } + + val syncFrameList: List + get() = samples.filter { it.frame.isKeyFrame }.map { it.id } + + val sampleSizes: List + get() = samples.map { it.frame.buffer.remaining() } + + val extradata: List> + get() = samples.mapNotNull { it.frame.extra } + + val sampleDts: List + get() = samples.map { + it.frame.dts ?: it.frame.pts + } + + fun add(id: Int, frame: Frame) { + samples.add(IndexedFrame(id, frame)) } - override fun write(output: ByteBuffer) { - samples.forEach { output.put(it.buffer) } + fun writeTo(action: (Frame) -> Unit) { + samples.forEach { action(it.frame) } } - fun toSampleToChunk(): SampleToChunk { - return SampleToChunk( - firstChunk = firstChunk, - samplesPerChunk = samplesPerChunk, - sampleDescriptionId = sampleDescriptionId - ) + fun write(output: ByteBuffer) { + samples.forEach { output.put(it.frame.buffer) } } -} -fun ByteBuffer.putSampleToChunk(c: Chunk) { - putInt(c.firstChunk) - putInt(c.samplesPerChunk) - putInt(c.sampleDescriptionId) + class IndexedFrame(val id: Int, val frame: Frame) } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/MovieBoxFactory.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/MovieBoxFactory.kt new file mode 100644 index 000000000..eba52a1c2 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/MovieBoxFactory.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.models + +import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.* + +abstract class AbstractMovieBoxFactory { + abstract fun build(trackChunks: List, dataOffset: Long): Box +} + +class MovieBoxFactory(val timescale: Int) : AbstractMovieBoxFactory() { + override fun build(trackChunks: List, dataOffset: Long): Box { + val mvhd = MovieHeaderBox( + version = 0, + duration = trackChunks.maxOf { it.duration }, + timescale = timescale, + nextTrackId = trackChunks[0].track.id + ) + val moov = MovieBox( + mvhd, + trackChunks.map { it.createTrak(0L) }, + MovieExtendsBox(trackChunks.map { it.createTref() }) + ) + + var mdatOffset = dataOffset + moov.size + 8 /*Mdat header*/ + moov.trak.forEachIndexed { index, trak -> + trak.mdia.minf.stbl.co64.addChunkOffset(mdatOffset) // 8 - MediaBoxHeader // TODO: multitrak + mdatOffset += trackChunks[index].dataSize + } + return moov + } +} + +class MovieFragmentBoxFactory(private val sequenceNumber: Int) : AbstractMovieBoxFactory() { + override fun build(trackChunks: List, dataOffset: Long): Box { + val mfhd = MovieFragmentHeaderBox( + sequenceNumber = sequenceNumber, + ) + val moofSize = getMoofSize(trackChunks) + var mdatOffset = moofSize + 8 /*Mdat header*/ + // TODO: update data size dynamically + return MovieFragmentBox( + mfhd, + trackChunks.map { + it.createTraf(dataOffset, mdatOffset).also { _ -> + mdatOffset += it.dataSize + } + }) + } + + private fun getMoofSize(tracksSegment: List): Int { + val mfhd = MovieFragmentHeaderBox( + sequenceNumber = 0, + ) + return MovieFragmentBox(mfhd, tracksSegment.map { it.createTraf(0, 0) }).size + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/SampleFlags.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/SampleFlags.kt new file mode 100644 index 000000000..da421e501 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/SampleFlags.kt @@ -0,0 +1,54 @@ +package io.github.thibaultbee.streampack.internal.muxers.mp4.models + +import io.github.thibaultbee.streampack.internal.utils.extensions.put +import io.github.thibaultbee.streampack.internal.utils.extensions.shl +import io.github.thibaultbee.streampack.internal.utils.extensions.toInt +import java.nio.ByteBuffer +import kotlin.experimental.and + +data class SampleFlags( + val isLeading: IsLeading = IsLeading.UNKNOWN, + val dependsOn: SampleDependsOn = SampleDependsOn.UNKNOWN, + val isDependedOn: SampleIsDependedOn = SampleIsDependedOn.UNKNOWN, + val hasRedundancy: HasRedundancy = HasRedundancy.UNKNOWN, + val paddingValue: Byte = 0, + val isNonSyncSample: Boolean, + val degradationPriority: Short = 0 +) + +fun ByteBuffer.putInt(f: SampleFlags) { + this.put(((f.isLeading.value and 0x3) shl 2) or ((f.dependsOn.value and 0x3).toInt())) + this.put( + ((f.isDependedOn.value and 0x3) shl 6) + or ((f.hasRedundancy.value and 0x3) shl 4) + or ((f.paddingValue and 0x7) shl 1) + or f.isNonSyncSample.toInt() + ) + this.putShort(f.degradationPriority) +} + + +enum class IsLeading(val value: Byte) { + UNKNOWN(0), + LEADING_SAMPLE_WITH_DEPENDENCY(1), + NOT_A_LEADING_SAMPLE(2), + LEADING_SAMPLE_WITHOUT_DEPENDENCY(3) +} + +enum class SampleDependsOn(val value: Byte) { + UNKNOWN(0), + OTHERS(1), + NO_OTHER(2) +} + +enum class SampleIsDependedOn(val value: Byte) { + UNKNOWN(0), + THIS_ONE(1), + NOT_THIS_ONE(2) +} + +enum class HasRedundancy(val value: Byte) { + UNKNOWN(0), + REDUNDANT_CODING(1), + NO_REDUNDANT_CODING(2) +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Segment.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Segment.kt new file mode 100644 index 000000000..037515f54 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Segment.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.models + +import io.github.thibaultbee.streampack.internal.data.Frame +import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.MediaDataBox +import java.nio.ByteBuffer + +/** + * A class that represent a single segment: MOOV + MDAT or a single fragment: MOOF + MDAT. + */ +class Segment( + tracks: List, + private val movieBoxFactory: AbstractMovieBoxFactory, + private val onNewSample: (ByteBuffer) -> Unit +) { + /** + * True if this segment is a fragment (MOOF + MDAT). + */ + val isFragment = movieBoxFactory is MovieFragmentBoxFactory + + private val trackChunks = tracks.map { TrackChunks(it, onNewSample) } + val dataSize: Int + get() = trackChunks.sumOf { it.dataSize } + + fun add(frame: Frame, streamPid: Int) { + val trackSegment = getTrackSegment(streamPid) + trackSegment.add(frame) + } + + fun write(dataOffset: Long) { + if (dataSize == 0) { + return + } + + onNewSample( + movieBoxFactory.build(trackChunks, dataOffset).toByteBuffer() + ) + + // Only write MDAT header, then write data + onNewSample(MediaDataBox(dataSize).writeHeader()) + trackChunks.forEach { it.write() } + } + + private fun getTrackSegment(id: Int): TrackChunks = trackChunks.first { it.track.id == id } +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/SampleToChunk.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Track.kt similarity index 65% rename from core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/SampleToChunk.kt rename to core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Track.kt index 4b59d3a21..e0bac40b4 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/SampleToChunk.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/Track.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Thibault B. + * Copyright (C) 2023 Thibault B. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,17 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.models -import java.nio.ByteBuffer +import io.github.thibaultbee.streampack.data.Config +import io.github.thibaultbee.streampack.internal.utils.TimeUtils + +class Track( + val id: Int, + val config: Config, + val timescale: Int = TimeUtils.TIME_SCALE, +) { + init { + require(id != 0) { "id must be greater than 0" } + } +} -data class SampleToChunk( - val firstChunk: Int, - val samplesPerChunk: Int, - val sampleDescriptionId: Int -) -fun ByteBuffer.put(c: SampleToChunk) { - putInt(c.firstChunk) - putInt(c.samplesPerChunk) - putInt(c.sampleDescriptionId) -} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/TrackChunks.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/TrackChunks.kt new file mode 100644 index 000000000..f8d7b278f --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/models/TrackChunks.kt @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.models + +import android.media.MediaFormat +import android.util.Size +import io.github.thibaultbee.streampack.data.AudioConfig +import io.github.thibaultbee.streampack.data.Config +import io.github.thibaultbee.streampack.data.VideoConfig +import io.github.thibaultbee.streampack.internal.data.Frame +import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.* +import io.github.thibaultbee.streampack.internal.muxers.mp4.utils.createHandlerBox +import io.github.thibaultbee.streampack.internal.muxers.mp4.utils.createTypeMediaHeaderBox +import io.github.thibaultbee.streampack.internal.utils.TimeUtils +import io.github.thibaultbee.streampack.internal.utils.av.descriptors.AudioSpecificConfigDescriptor +import io.github.thibaultbee.streampack.internal.utils.av.descriptors.ESDescriptor +import io.github.thibaultbee.streampack.internal.utils.av.descriptors.SLConfigDescriptor +import io.github.thibaultbee.streampack.internal.utils.av.video.avc.AVCDecoderConfigurationRecord +import io.github.thibaultbee.streampack.internal.utils.extensions.clone +import java.nio.ByteBuffer + +/** + * The content of a single track for a fragment or MOOV+MDAT + * + * Chunks are only for MOOV+MDAT otherwise for fragment, they are flatten. + */ +class TrackChunks( + val track: Track, + val onNewSample: (ByteBuffer) -> Unit +) { + private val chunks = mutableListOf() + private var frameId = 1 + + val duration: Long + get() = chunks.sumOf { it.duration } * track.timescale / TimeUtils.TIME_SCALE + + private val firstTimestamp: Long + get() = chunks.minOf { it.firstTimestamp } * track.timescale / TimeUtils.TIME_SCALE + + val dataSize: Int + get() = chunks.sumOf { it.dataSize } + + private val onlySyncFrame: Boolean + get() = chunks.all { it.onlySyncFrame } + + private val syncFrameList: List + get() = chunks.flatMap { it.syncFrameList } + + private val sampleSizes: List + get() = chunks.flatMap { it.sampleSizes } + + private val extradata: List> + get() = chunks.flatMap { it.extradata } + + private val sampleDts: List + get() = chunks.flatMap { it.sampleDts.map { it * track.timescale / TimeUtils.TIME_SCALE } } + + private fun createNewChunk() { + val newChunk = Chunk(chunks.size + 1) + chunks.add(newChunk) + } + + fun add(frame: Frame) { + if (chunks.isEmpty()) { + createNewChunk() + } + + frame.buffer = frame.buffer.clone() // Do not keep mediacodec buffer + chunks.last().add(frameId, frame) + frameId++ + } + + fun write() { + chunks.forEach { chunk -> chunk.writeTo { onNewSample(it.buffer) } } + } + + fun createTrak(firstChunkOffset: Long): TrackBox { + val tkhd = createTrackHeaderBox(track.config) + val mdhd = + MediaHeaderBox(version = 0, timescale = track.timescale, duration = duration) + val hdlr = track.config.createHandlerBox() + val mhd = track.config.createTypeMediaHeaderBox() + val dinf = DataInformationBox(DataReferenceBox(DataEntryUrlBox())) + val stsd = createSampleDescriptionBox() + val stts = createTimeToSampleBox() + val stss = createSyncSampleBox() + val stsc = createSampleToChunkBox() + val stsz = SampleSizeBox(sampleSizeEntries = sampleSizes) + val co = createChunkOffsetBox(firstChunkOffset) + + val stbl = SampleTableBox(stsd, stts, stss, stsc, stsz, co) + val minf = MediaInformationBox(mhd, dinf, stbl) + val mdia = MediaBox(mdhd, hdlr, minf) + return TrackBox(tkhd, mdia) + } + + private fun createTrackHeaderBox(config: Config): TrackHeaderBox { + val resolution = when (config) { + is AudioConfig -> Size(0, 0) + is VideoConfig -> config.resolution + else -> throw IllegalArgumentException("Unsupported config") + } + val volume = when (config) { + is AudioConfig -> 1.0f + else -> 0.0f + } + return TrackHeaderBox( + id = track.id, + version = 0, + flags = listOf( + TrackHeaderBox.TrackFlag.ENABLED, + TrackHeaderBox.TrackFlag.IN_MOVIE, + TrackHeaderBox.TrackFlag.IN_PREVIEW + ), + duration = duration, + volume = volume, + resolution = resolution + ) + } + + private fun createSampleToChunkBox(): SampleToChunkBox { + val filteredSampleToChunkEntries = mutableListOf() + chunks.forEach { + try { + val last = filteredSampleToChunkEntries.last() + if (last.samplesPerChunk != it.samplesPerChunk) { + filteredSampleToChunkEntries.add( + SampleToChunkBox.Entry( + it.id, + 1, + it.samplesPerChunk + ) + ) + } + } catch (e: NoSuchElementException) { + // First entry + filteredSampleToChunkEntries.add( + SampleToChunkBox.Entry( + it.id, + 1, + it.samplesPerChunk + ) + ) + } + } + + return SampleToChunkBox(filteredSampleToChunkEntries) + } + + private fun createChunkOffsetBox(firstChunkOffset: Long): ChunkLargeOffsetBox { + val chunkOffsets = mutableListOf() + chunks.map { it.dataSize }.forEach { + try { + chunkOffsets.add(chunkOffsets.last() + it.toLong()) + } catch (e: NoSuchElementException) { + // First entry + chunkOffsets.add(firstChunkOffset) + } + } + return ChunkLargeOffsetBox(chunkOffsets) + } + + private fun createTimeToSampleBox(): TimeToSampleBox { + require(sampleDts.size > 1) { "sampleDts must have at least 2 entries" } + + val sampleDurations = mutableListOf() + sampleDts.forEachIndexed { index, dts -> + if (index != 0) { + sampleDurations.add((dts - sampleDts[index - 1]).toInt()) + } + } + val filteredTimeToSampleEntries = mutableListOf() + var count = 1 + sampleDurations.forEach { duration -> + try { + val last = filteredTimeToSampleEntries.last() + if (duration != last.delta) { + filteredTimeToSampleEntries.add(TimeToSampleBox.Entry(count, duration)) + count = 1 + } else { + count++ + } + } catch (e: NoSuchElementException) { + // First entry + filteredTimeToSampleEntries.add(TimeToSampleBox.Entry(count, duration)) + } + } + + return TimeToSampleBox(filteredTimeToSampleEntries) + } + + private fun createSampleDescriptionBox(): SampleDescriptionBox { + val sampleEntry = when (track.config.mimeType) { + MediaFormat.MIMETYPE_VIDEO_AVC -> { + (track.config as VideoConfig) + AVCSampleEntry( + track.config.resolution, + avcc = AVCConfigurationBox( + AVCDecoderConfigurationRecord.fromParameterSets( + extradata[0][0], + extradata[0][1] + ) + ), + ) + } + MediaFormat.MIMETYPE_AUDIO_AAC -> { + (track.config as AudioConfig) + MP4AudioSampleEntry( + AudioConfig.getNumberOfChannels(track.config.channelConfig).toShort(), + AudioConfig.getNumOfBytesPerSample(track.config.byteFormat).toShort(), + track.config.sampleRate, + esds = ESDSBox( + ESDescriptor( + esId = 0, + streamPriority = 0, + decoderConfigDescriptor = AudioSpecificConfigDescriptor( + upStream = false, + bufferSize = 1536, //TODO: get from somewhere + maxBitrate = track.config.startBitrate, + avgBitrate = track.config.startBitrate, + extradata[0][0] + ), + slConfigDescriptor = SLConfigDescriptor(predefined = 2) + ) + ) + ) + } + else -> throw IllegalArgumentException("Unsupported mimeType") + } + return SampleDescriptionBox(sampleEntry) + } + + private fun createSyncSampleBox(): SyncSampleBox? { + return if (!onlySyncFrame) { + SyncSampleBox(syncFrameList) + } else { + null + } + } + + fun createTref(): TrackExtendsBox { + return TrackExtendsBox(track.id) + } + + fun createTraf(baseDataOffset: Long, moofSize: Int): TrackFragmentBox { + val tfhd = createTrackFragmentHeaderBox(baseDataOffset) + val tfdt = null //TODO TrackFragmentBaseMediaDecodeTimeBox(firstTimestamp) + val trun = createTrackRunBox(moofSize) + return TrackFragmentBox(tfhd, tfdt, trun) + } + + private fun createTrackFragmentHeaderBox(baseDataOffset: Long): TrackFragmentHeaderBox { + return TrackFragmentHeaderBox( + id = track.id, + baseDataOffset = baseDataOffset, + defaultSampleFlags = SampleFlags( + dependsOn = SampleDependsOn.OTHERS, + isNonSyncSample = true + ) + ) + } + + private fun createTrackRunBox(moofSize: Int): TrackRunBox { + val sampleDts = chunks.flatMap { it.sampleDts } + val sampleSizes = chunks.flatMap { it.sampleSizes } + + require(sampleDts.size == sampleSizes.size) { "Samples dts and sizes must have the same size" } + + val lastEntryIndex = sampleSizes.size - 1 + val entries = sampleSizes.mapIndexed { index, size -> + TrackRunBox.Entry( + duration = if (index == lastEntryIndex) { + 0 + } else { + sampleDts[index + 1] - sampleDts[index] + }.toInt(), size = size + ) + } + + return TrackRunBox( + version = 0, + dataOffset = moofSize, + firstSampleFlags = SampleFlags( + dependsOn = SampleDependsOn.NO_OTHER, + isNonSyncSample = false, + ), + entries = entries + ) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/utils/ConfigExtensions.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/utils/ConfigExtensions.kt new file mode 100644 index 000000000..aeab9f0cf --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/mp4/utils/ConfigExtensions.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.utils + +import io.github.thibaultbee.streampack.data.AudioConfig +import io.github.thibaultbee.streampack.data.Config +import io.github.thibaultbee.streampack.data.VideoConfig +import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.HandlerBox +import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.SoundMediaHeaderBox +import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.TypeMediaHeaderBox +import io.github.thibaultbee.streampack.internal.muxers.mp4.boxes.VideoMediaHeaderBox + +fun Config.createTypeMediaHeaderBox(): TypeMediaHeaderBox { + return when (this) { + is AudioConfig -> SoundMediaHeaderBox() + is VideoConfig -> VideoMediaHeaderBox() + else -> throw IllegalArgumentException("Unsupported config") + } +} + +fun Config.createHandlerBox(): HandlerBox { + return when (this) { + is AudioConfig -> HandlerBox(HandlerBox.HandlerType.SOUND, "SoundHandler") + is VideoConfig -> HandlerBox(HandlerBox.HandlerType.VIDEO, "VideoHandler") + else -> throw IllegalArgumentException("Unsupported config") + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/ts/TSMuxer.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/ts/TSMuxer.kt index 30f52ad1e..9debb304d 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/ts/TSMuxer.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/ts/TSMuxer.kt @@ -136,7 +136,7 @@ class TSMuxer( frame.buffer, pes.stream.config as AudioConfig ).toByteBuffer() } else { - LATMFrameWriter.fromEsds(frame.buffer, frame.extra!!.first()).toByteBuffer() + LATMFrameWriter.fromDecoderSpecificInfo(frame.buffer, frame.extra!!.first()).toByteBuffer() } } MediaFormat.MIMETYPE_AUDIO_OPUS -> {} // TODO: optional control header diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/BitBuffer.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/BitBuffer.kt index edd94bdf7..a49eb53fb 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/BitBuffer.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/BitBuffer.kt @@ -107,6 +107,10 @@ open class BitBuffer( buffer.position(ceil(bitPosition.toDouble() / Byte.SIZE_BITS).toInt()) } + fun put(b: Short, numBits: Int = Byte.SIZE_BITS) { + put(if (b < 0) b.toInt() + 256 else b.toInt(), numBits) + } + fun put(b: Byte, numBits: Int = Byte.SIZE_BITS) { put(if (b < 0) b.toInt() + 256 else b.toInt(), numBits) } diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/ByteBufferWriter.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/ByteBufferWriter.kt index 8e031661b..dc2a7d894 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/ByteBufferWriter.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/ByteBufferWriter.kt @@ -24,7 +24,7 @@ abstract class ByteBufferWriter { open val bitSize: Int get() = size * Byte.SIZE_BITS - fun toByteBuffer(): ByteBuffer { + open fun toByteBuffer(): ByteBuffer { val output = ByteBuffer.allocate(size) write(output) output.rewind() diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioObjectType.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioObjectType.kt index 698489553..7d9a8416a 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioObjectType.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioObjectType.kt @@ -81,7 +81,7 @@ enum class AudioObjectType(val value: Int) { return values().first { it.value == value } } - fun fromMimeType(mimeType: String, profile: Int) = when (mimeType) { + fun fromProfile(mimeType: String, profile: Int) = when (mimeType) { MediaFormat.MIMETYPE_AUDIO_AAC -> { when (profile) { MediaCodecInfo.CodecProfileLevel.AACObjectMain -> AAC_MAIN diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfig.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfig.kt index c54209ede..7aefb86ba 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfig.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfig.kt @@ -253,7 +253,7 @@ data class AudioSpecificConfig( fun fromMediaFormat( format: MediaFormat, ): AudioSpecificConfig { - val audioObjectType = AudioObjectType.fromMimeType( + val audioObjectType = AudioObjectType.fromProfile( format.getString(MediaFormat.KEY_MIME)!!, format.getInteger(MediaFormat.KEY_AAC_PROFILE) ) @@ -264,7 +264,7 @@ data class AudioSpecificConfig( audioObjectType, channelConfig, frameLengthFlag = false, - dependsOnCodeCoder = false, + dependsOnCoreCoder = false, extensionFlag = false ) return AudioSpecificConfig( @@ -278,14 +278,14 @@ data class AudioSpecificConfig( fun fromAudioConfig( config: AudioConfig, ): AudioSpecificConfig { - val audioObjectType = AudioObjectType.fromMimeType(config.mimeType, config.profile) + val audioObjectType = AudioObjectType.fromProfile(config.mimeType, config.profile) val channelConfig = ChannelConfiguration.fromChannelConfig(config.channelConfig) val specificConfig = GASpecificConfig( audioObjectType, channelConfig, frameLengthFlag = false, - dependsOnCodeCoder = false, + dependsOnCoreCoder = false, extensionFlag = false ) return AudioSpecificConfig( @@ -298,11 +298,11 @@ data class AudioSpecificConfig( fun writeFromByteBuffer( buffer: ByteBuffer, - esds: ByteBuffer, + decoderSpecificInfo: ByteBuffer, audioConfig: AudioConfig ) { if (audioConfig.mimeType == MediaFormat.MIMETYPE_AUDIO_AAC) { - buffer.put(esds) + buffer.put(decoderSpecificInfo) } else { throw NotImplementedError("No support for ${audioConfig.mimeType}") } diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AACFrameWriter.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AACFrameWriter.kt index 8278ec2d2..4b0e9a14b 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AACFrameWriter.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AACFrameWriter.kt @@ -55,9 +55,9 @@ class LATMFrameWriter( } companion object { - fun fromEsds(frameBuffer: ByteBuffer, esds: ByteBuffer): LATMFrameWriter { + fun fromDecoderSpecificInfo(frameBuffer: ByteBuffer, decoderSpecificInfo: ByteBuffer): LATMFrameWriter { return LATMFrameWriter( - AudioMuxElement.fromEsds(frameBuffer, esds) + AudioMuxElement.fromDecoderSpecificInfo(frameBuffer, decoderSpecificInfo) ) } } diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElement.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElement.kt index af6584ec5..86eee1cc9 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElement.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElement.kt @@ -115,11 +115,11 @@ class AudioMuxElement( ) } - fun fromEsds(payload: ByteBuffer, esds: ByteBuffer): AudioMuxElement { + fun fromDecoderSpecificInfo(payload: ByteBuffer, decoderSpecificInfo: ByteBuffer): AudioMuxElement { return AudioMuxElement( muxConfigPresent = true, useSameStreamMuxConfig = false, - StreamMuxConfig.fromEsds(esds), + StreamMuxConfig.fromDecoderSpecificInfo(decoderSpecificInfo), payload = payload ) } @@ -249,9 +249,9 @@ class StreamMuxConfig( ) } - fun fromEsds(esds: ByteBuffer): StreamMuxConfig { - val audioSpecificConfig = AudioSpecificConfig.parse(esds) - esds.rewind() + fun fromDecoderSpecificInfo(decoderSpecificInfo: ByteBuffer): StreamMuxConfig { + val audioSpecificConfig = AudioSpecificConfig.parse(decoderSpecificInfo) + decoderSpecificInfo.rewind() return StreamMuxConfig( allStreamsSameTimeFraming = true, numSubFrames = 0, @@ -259,7 +259,7 @@ class StreamMuxConfig( numLayer = 0, frameLengthType = 0, audioSpecificConfig = BitBuffer( - esds, + decoderSpecificInfo, bitEnd = audioSpecificConfig.bitSize - 1 ) ) diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/config/GASpecificConfig.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/config/GASpecificConfig.kt index 4486dc4fd..41930ec95 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/config/GASpecificConfig.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/config/GASpecificConfig.kt @@ -25,7 +25,7 @@ data class GASpecificConfig( val channelConfiguration: ChannelConfiguration, val frameLengthFlag: Boolean, - val dependsOnCodeCoder: Boolean, + val dependsOnCoreCoder: Boolean, val extensionFlag: Boolean, val coreCoderDelay: Short? = null, @@ -39,7 +39,7 @@ data class GASpecificConfig( val extensionFlag3: Boolean? = null ) : SpecificConfig() { override val bitSize = - 3 + if (dependsOnCodeCoder) 14 else 0 + if (channelConfiguration == ChannelConfiguration.SPECIFIC) { + 3 + if (dependsOnCoreCoder) 14 else 0 + if (channelConfiguration == ChannelConfiguration.SPECIFIC) { programConfigElement!!.bitSize } else { 0 diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/AudioSpecificConfigDescriptor.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/AudioSpecificConfigDescriptor.kt new file mode 100644 index 000000000..0233dcc8a --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/AudioSpecificConfigDescriptor.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +import java.nio.ByteBuffer + +class AudioSpecificConfigDescriptor( + upStream: Boolean, + bufferSize: Int, + maxBitrate: Int, + avgBitrate: Int, + audioSpecificConfig: ByteBuffer, +) : DecoderConfigDescriptor( + ObjectTypeIndication.AUDIO_ISO_14496_3_AAC, + StreamType.AudioStream, + upStream, + bufferSize, + maxBitrate, + avgBitrate, + DecoderSpecificInfo(audioSpecificConfig) +) \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/BaseDescriptor.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/BaseDescriptor.kt new file mode 100644 index 000000000..66e86b9c5 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/BaseDescriptor.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +import io.github.thibaultbee.streampack.internal.utils.av.ByteBufferWriter + +abstract class BaseDescriptor(private val tag: Tags, private val sizeOfInstance: Int) : + ByteBufferWriter() { + override val size = 2 + sizeOfInstance + + override fun write(output: java.nio.ByteBuffer) { + output.put(tag.value) + output.put(sizeOfInstance.toByte()) // TODO: deal with larger sizeOfInstance + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/DecoderConfigDescriptor.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/DecoderConfigDescriptor.kt new file mode 100644 index 000000000..e4671af26 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/DecoderConfigDescriptor.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +import io.github.thibaultbee.streampack.internal.utils.extensions.put +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt24 +import io.github.thibaultbee.streampack.internal.utils.extensions.shl +import java.nio.ByteBuffer + +open class DecoderConfigDescriptor( + private val objectTypeIndication: ObjectTypeIndication, + private val streamType: StreamType, + private val upStream: Boolean, + private val bufferSize: Int, + private val maxBitrate: Int, + private val avgBitrate: Int, + private val decoderSpecificInfo: DecoderSpecificInfo, + // TODO profileLevelIndicationIndexDescriptor +) : BaseDescriptor(Tags.DecoderConfigDescr, 13 + decoderSpecificInfo.size) { + + override fun write(output: ByteBuffer) { + super.write(output) + output.put(objectTypeIndication.value) + output.put((streamType.value shl 2) or (upStream shl 1) or 1) + output.putInt24(bufferSize) + output.putInt(maxBitrate) + output.putInt(avgBitrate) + decoderSpecificInfo.write(output) + } +} + +enum class ObjectTypeIndication(val value: Byte) { + AUDIO_ISO_14496_3_AAC(0x40), + AUDIO_ISO_13818_3_MP3(0x69); + + companion object { + fun fromValue(value: Byte) = values().first { it.value == value } + } +} + +enum class StreamType(val value: Byte) { + ObjectDescriptorStream(1), + ClockReferenceStream(2), + SceneDescriptionStream(3), + VisualStream(4), + AudioStream(5), + MPEG7Stream(6), + IPMPStream(7), + ObjectContentInfoStream(8), + MPEGJStream(9); + + companion object { + fun fromValue(value: Byte) = values().first { it.value == value } + } +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/DecoderSpecificInfo.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/DecoderSpecificInfo.kt new file mode 100644 index 000000000..2fff7fb06 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/DecoderSpecificInfo.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +import java.nio.ByteBuffer + +class DecoderSpecificInfo( + private val specificConfig: ByteBuffer, +) : BaseDescriptor(Tags.DecSpecificInfo, specificConfig.remaining()) { + + override fun write(output: ByteBuffer) { + super.write(output) + output.put(specificConfig) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/ESDescriptor.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/ESDescriptor.kt new file mode 100644 index 000000000..5c18148a1 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/ESDescriptor.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +import io.github.thibaultbee.streampack.internal.utils.extensions.put +import io.github.thibaultbee.streampack.internal.utils.extensions.putString +import java.nio.ByteBuffer + +class ESDescriptor( + private val esId: Short, + private val streamPriority: Byte, + private val dependsOnEsId: Short? = null, + private val url: String? = null, + private val ocrEsId: Short? = null, + private val decoderConfigDescriptor: DecoderConfigDescriptor, + private val slConfigDescriptor: SLConfigDescriptor + // TODO: other descriptor +) : BaseDescriptor(Tags.ESDescr, 3 + (dependsOnEsId?.let { 2 } ?: 0) + (url?.let { 1 + it.length } + ?: 0) + (ocrEsId?.let { 2 } ?: 0) + decoderConfigDescriptor.size + slConfigDescriptor.size) { + + override fun write(output: ByteBuffer) { + super.write(output) + output.putShort(esId) + output.put(((dependsOnEsId?.let { 1 } + ?: 0) shl 7) or ((url?.let { 1 } + ?: 0) shl 6) or ((url?.let { 1 } + ?: 0) shl 5) or streamPriority.toInt()) + dependsOnEsId?.let { + output.putShort(it) + } + url?.let { + output.put(it.length) + output.putString(it) + } + ocrEsId?.let { + output.putShort(it) + } + decoderConfigDescriptor.write(output) + slConfigDescriptor.write(output) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/SLConfigDescriptor.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/SLConfigDescriptor.kt new file mode 100644 index 000000000..a0c908c93 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/SLConfigDescriptor.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +import java.nio.ByteBuffer + +class SLConfigDescriptor( + private val predefined: Byte, +) : BaseDescriptor(Tags.SLConfigDescr, 1) { + + override fun write(output: ByteBuffer) { + super.write(output) + output.put(predefined) + if (predefined == 0.toByte()) { + throw NotImplementedError("SLConfigDescriptor with predefined == 0 is not implemented") + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/Tags.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/Tags.kt new file mode 100644 index 000000000..d36f01da5 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/Tags.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +enum class Tags(val value: Byte) { + ObjectDescr(0x01), + InitialObjectDescr(0x02), + ESDescr(0x03), + DecoderConfigDescr(0x04), + DecSpecificInfo(0x05), + SLConfigDescr(0x06), + ContentIdentDescr(0x07), + SupplContentIdentDescr(0x08), + IPIDescrPointer(0x09), + IPMPDescrPointer(0x0A), + IPMPDescr(0x0B), + QoSDescr(0x0C), + RegistrationDescr(0x0D), + ES_ID_Inc(0x0E), + ES_ID_Ref(0x0F), + MP4_IOD(0x10), + MP4_OD(0x11), + IPL_DescrPointerRef(0x12), + ExtensionProfileLevelDescr(0x13), + ProfileLevelIndicationIndexDescr(0x14), + ContentClassificationDescr(0x40), + KeyWordDescr(0x41), + RatingDescr(0x42), + LanguageDescr(0x43), + ShortTextualDescr(0x44), + ExpandedTextualDescr(0x45), + ContentCreatorNameDescr(0x46), + ContentCreationDateDescr(0x47), + OCICreatorNameDescr(0x48), + OCICreationDateDescr(0x49), + SmpteCameraPositionDescr(0x4A); + + companion object { + fun from(tag: Byte): Tags { + return values().find { it.value == tag } + ?: throw IllegalArgumentException("Unknown tag: $tag") + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/BitOperationExtensions.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/BitOperationExtensions.kt index 3467fa97f..c36fa2646 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/BitOperationExtensions.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/BitOperationExtensions.kt @@ -55,10 +55,16 @@ infix fun Byte.shr(i: Int) = infix fun Byte.or(b: Byte) = this.toInt() or b.toInt() + +infix fun Byte.or(other: Int) = + this.toInt() or other + + fun Int.toBitList(): List { val list = mutableListOf() for (i in Int.SIZE_BITS downTo 0) { list.add(((this shr i) and 0x01).toBoolean()) } return list -} \ No newline at end of file +} + diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/ByteBufferExtensions.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/ByteBufferExtensions.kt index ed70e9c97..41b8774ab 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/ByteBufferExtensions.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/extensions/ByteBufferExtensions.kt @@ -35,7 +35,6 @@ fun ByteBuffer.putInt24(i: Int) { put(i.toByte()) } - fun ByteBuffer.putLong48(i: Long) { putShort(i shr 32) putInt(i.toInt()) @@ -180,4 +179,9 @@ fun ByteBuffer.extractRbsp(headerLength: Int): ByteBuffer { rbsp.limit(rbsp.position()) rbsp.rewind() return rbsp -} \ No newline at end of file +} + +fun ByteBuffer.clone(): ByteBuffer { + val clone = ByteBuffer.allocate(this.remaining()) + return clone.put(this).apply { rewind() } +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/streamers/bases/BaseStreamer.kt b/core/src/main/java/io/github/thibaultbee/streampack/streamers/bases/BaseStreamer.kt index cff625d82..011f06652 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/streamers/bases/BaseStreamer.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/streamers/bases/BaseStreamer.kt @@ -71,8 +71,8 @@ abstract class BaseStreamer( override var onErrorListener: OnErrorListener? = initialOnErrorListener override val helper = StreamerConfigurationHelper(muxer.helper) - private var audioTsStreamId: Int? = null - private var videoTsStreamId: Int? = null + private var audioStreamId: Int? = null + private var videoStreamId: Int? = null // Keep video configuration protected var videoConfig: VideoConfig? = null @@ -94,7 +94,7 @@ abstract class BaseStreamer( } override fun onOutputFrame(frame: Frame) { - audioTsStreamId?.let { + audioStreamId?.let { try { this@BaseStreamer.muxer.encode(frame, it) } catch (e: Exception) { @@ -110,7 +110,7 @@ abstract class BaseStreamer( } override fun onOutputFrame(frame: Frame) { - videoTsStreamId?.let { + videoStreamId?.let { try { frame.pts += videoCapture!!.timestampOffset frame.dts = if (frame.dts != null) { @@ -147,9 +147,10 @@ abstract class BaseStreamer( private fun onStreamError(error: StreamPackError) { try { stopStream() - onErrorListener?.onError(error) } catch (e: Exception) { Logger.e(TAG, "onStreamError: Can't stop stream") + } finally { + onErrorListener?.onError(error) } } @@ -286,8 +287,8 @@ abstract class BaseStreamer( } val streamsIdMap = this.muxer.addStreams(streams) - videoConfig?.let { videoTsStreamId = streamsIdMap[videoConfig as Config] } - audioConfig?.let { audioTsStreamId = streamsIdMap[audioConfig as Config] } + videoConfig?.let { videoStreamId = streamsIdMap[videoConfig as Config] } + audioConfig?.let { audioStreamId = streamsIdMap[audioConfig as Config] } muxer.startStream() diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBoxTest.kt index 106fc2c63..b2a2b5788 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCConfigurationBoxTest.kt @@ -1,7 +1,22 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes import io.github.thibaultbee.streampack.internal.utils.av.video.avc.AVCDecoderConfigurationRecord -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -50,7 +65,7 @@ class AVCConfigurationBoxTest { pps = listOf(pps) ) ) - val buffer = avcC.write() + val buffer = avcC.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCSampleEntryTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCSampleEntryTest.kt index 1f8d38669..7ed2cab54 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCSampleEntryTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/AVCSampleEntryTest.kt @@ -1,7 +1,22 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes import io.github.thibaultbee.streampack.internal.utils.av.video.avc.AVCDecoderConfigurationRecord -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.MockUtils import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals @@ -57,7 +72,7 @@ class AVCSampleEntryTest { val avc1 = AVCSampleEntry(MockUtils.mockSize(1074, 1920), avcc = avcC, btrt = btrt) - val buffer = avc1.write() + val buffer = avc1.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBoxTest.kt index 027fdb080..ec0166725 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/BitRateBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -10,7 +25,7 @@ class BitRateBoxTest { fun `write valid btrt test`() { val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("btrt.box") val btrt = BitRateBox(1100000, 4840000, 3878679) - val buffer = btrt.write() + val buffer = btrt.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBoxTest.kt index 183699c86..2b2210676 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/ChunkOffsetBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -14,7 +29,7 @@ class ChunkOffsetBoxTest { 48, 1048191, 2070322, 3117965 ) ) - val buffer = stco.write() + val buffer = stco.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBoxTest.kt index 2797e64cf..4960c460d 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataInformationBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -10,7 +25,7 @@ class DataInformationBoxTest { fun `write valid dinf test`() { val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("dinf.box") val dinf = DataInformationBox(DataReferenceBox(listOf(DataEntryUrlBox()))) - val buffer = dinf.write() + val buffer = dinf.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBoxTest.kt index 24053f42f..8c5898407 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/DataReferenceBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Assert.fail @@ -11,7 +26,7 @@ class DataReferenceBoxTest { fun `write valid dref test`() { val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("dref.box") val dref = DataReferenceBox(listOf(DataEntryUrlBox())) - val buffer = dref.write() + val buffer = dref.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBoxTest.kt index 26b4855de..e91130710 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/FileTypeBoxTest.kt @@ -15,7 +15,7 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Assert.fail @@ -30,7 +30,7 @@ class FileTypeBoxTest { minorVersion = 512, compatibleBrands = listOf("isom", "iso2", "avc1", "mp41") ) - val buffer = ftyp.write() + val buffer = ftyp.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBoxTest.kt index 572e087a4..a8a36b432 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/HandlerBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.* import org.junit.Test @@ -13,7 +28,7 @@ class HandlerBoxTest { type = HandlerBox.HandlerType.VIDEO, name = "VideoHandler" ) - val buffer = hdlr.write() + val buffer = hdlr.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MP4AudioSampleEntryTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MP4AudioSampleEntryTest.kt new file mode 100644 index 000000000..a6ef76218 --- /dev/null +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MP4AudioSampleEntryTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.utils.av.descriptors.* +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray +import io.github.thibaultbee.streampack.utils.ResourcesUtils +import org.junit.Assert +import org.junit.Test +import java.nio.ByteBuffer + +class MP4AudioSampleEntryTest { + @Test + fun `write valid mp4a test`() { + val decoderSpecificInfo = + ByteBuffer.wrap(byteArrayOf(0x11, 0x90.toByte(), 0x56, 0xE5.toByte(), 0)) + val esDescriptor = ESDescriptor( + esId = 0, + streamPriority = 0, + dependsOnEsId = null, + url = null, + ocrEsId = null, + decoderConfigDescriptor = DecoderConfigDescriptor( + objectTypeIndication = ObjectTypeIndication.AUDIO_ISO_14496_3_AAC, + streamType = StreamType.AudioStream, + upStream = false, + bufferSize = 478, + maxBitrate = 139520, + avgBitrate = 95928, + decoderSpecificInfo = DecoderSpecificInfo(decoderSpecificInfo) + ), + slConfigDescriptor = SLConfigDescriptor(predefined = 2) + ) + + val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("mp4a.box") + val mp4a = MP4AudioSampleEntry( + channelCount = 2, + sampleSize = 16, + sampleRate = 48000, + esds = ESDSBox(esDescriptor) + ) + val buffer = mp4a.toByteBuffer() + Assert.assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentHeaderBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentHeaderBoxTest.kt new file mode 100644 index 000000000..484cdc7a7 --- /dev/null +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieFragmentHeaderBoxTest.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray +import io.github.thibaultbee.streampack.utils.ResourcesUtils +import org.junit.Assert.assertArrayEquals +import org.junit.Test + +class MovieFragmentHeaderBoxTest { + @Test + fun `write valid mfhd test`() { + val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("mfhd.box") + val mfhd = MovieFragmentHeaderBox( + sequenceNumber = 2 + ) + val buffer = mfhd.toByteBuffer() + assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBoxTest.kt index d926bbd88..a6c3c40a7 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/MovieHeaderBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -19,7 +34,7 @@ class MovieHeaderBoxTest { duration = 45000, nextTrackId = 2 ) - val buffer = mvhd.write() + val buffer = mvhd.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBoxTest.kt index 023e6d8ae..eb03c05f5 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleSizeBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -1363,7 +1378,7 @@ class SampleSizeBoxTest { 1558 ) ) - val buffer = stsz.write() + val buffer = stsz.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBoxTest.kt index 8d95b56ee..ffbc84706 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SampleToChunkBoxTest.kt @@ -1,8 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.Chunk -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleToChunk -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -13,12 +26,12 @@ class SampleToChunkBoxTest { val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("stsc.box") val stsc = SampleToChunkBox( listOf( - SampleToChunk(1, 435, 1), - SampleToChunk(3, 432, 1), - SampleToChunk(4, 48, 1) + SampleToChunkBox.Entry(1, 435, 1), + SampleToChunkBox.Entry(3, 432, 1), + SampleToChunkBox.Entry(4, 48, 1) ) ) - val buffer = stsc.write() + val buffer = stsc.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SoundMediaHeaderBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SoundMediaHeaderBoxTest.kt index 80dfbb9e3..387c1b16f 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SoundMediaHeaderBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SoundMediaHeaderBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.* import org.junit.Test @@ -10,7 +25,7 @@ class SoundMediaHeaderBoxTest { fun `write valid smhd test`() { val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("smhd.box") val smhd = SoundMediaHeaderBox() - val buffer = smhd.write() + val buffer = smhd.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBoxTest.kt index c5ceb8993..2d5ab76a5 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/SyncSampleBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -58,7 +73,7 @@ class SyncSampleBoxTest { 1321 ) ) - val buffer = stss.write() + val buffer = stss.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBoxTest.kt index 9132c4cf1..191e78bb5 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TimeToSampleBoxTest.kt @@ -1,7 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.muxers.mp4.models.DecodingTime -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -11,9 +25,9 @@ class TimeToSampleBoxTest { fun `write valid stts test`() { val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("stts.box") val stts = TimeToSampleBox( - listOf(DecodingTime(1350, 512)) + listOf(TimeToSampleBox.Entry(1350, 512)) ) - val buffer = stts.write() + val buffer = stts.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBaseMediaDecodeTimeBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBaseMediaDecodeTimeBoxTest.kt new file mode 100644 index 000000000..370c4109b --- /dev/null +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentBaseMediaDecodeTimeBoxTest.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray +import io.github.thibaultbee.streampack.utils.ResourcesUtils +import org.junit.Assert.assertArrayEquals +import org.junit.Test + +class TrackFragmentBaseMediaDecodeTimeBoxTest { + @Test + fun `write valid tfdt test`() { + val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("tfdt.box") + val tfdt = TrackFragmentBaseMediaDecodeTimeBox( + baseMediaDecodeTime = 15360, + version = 1 + ) + val buffer = tfdt.toByteBuffer() + assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentHeaderBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentHeaderBoxTest.kt new file mode 100644 index 000000000..e6dbac1a1 --- /dev/null +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackFragmentHeaderBoxTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleFlags +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleDependsOn +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray +import io.github.thibaultbee.streampack.utils.ResourcesUtils +import org.junit.Assert.assertArrayEquals +import org.junit.Test + +class TrackFragmentHeaderBoxTest { + @Test + fun `write valid tfhd test`() { + val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("tfhd.box") + val tfhd = TrackFragmentHeaderBox( + id = 1, + baseDataOffset = 72077L, + defaultSampleDuration = 512, + defaultSampleSize = 28381, + defaultSampleFlags = SampleFlags( + dependsOn = SampleDependsOn.OTHERS, + isNonSyncSample = true + ), + durationIsEmpty = false + + ) + val buffer = tfhd.toByteBuffer() + assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBoxTest.kt index 8e89abc68..ee59892e0 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackHeaderBoxTest.kt @@ -15,8 +15,7 @@ */ package io.github.thibaultbee.streampack.internal.muxers.mp4.boxes -import android.util.Size -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.MockUtils import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals @@ -38,7 +37,7 @@ class TrackHeaderBoxTest { duration = 45000, resolution = MockUtils.mockSize(2048, 1152) ) - val buffer = tkhd.write() + val buffer = tkhd.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackRunBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackRunBoxTest.kt new file mode 100644 index 000000000..c8f47e661 --- /dev/null +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/TrackRunBoxTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes + +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleDependsOn +import io.github.thibaultbee.streampack.internal.muxers.mp4.models.SampleFlags +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray +import io.github.thibaultbee.streampack.utils.ResourcesUtils +import org.junit.Assert.assertArrayEquals +import org.junit.Test + +class TrackRunBoxTest { + @Test + fun `write valid trun test`() { + val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("trun.box") + val entries = listOf( + TrackRunBox.Entry(size = 28381), + TrackRunBox.Entry(size = 889), + TrackRunBox.Entry(size = 1383), + TrackRunBox.Entry(size = 1276), + TrackRunBox.Entry(size = 1167), + TrackRunBox.Entry(size = 1367), + TrackRunBox.Entry(size = 1570), + TrackRunBox.Entry(size = 1206), + TrackRunBox.Entry(size = 2088), + TrackRunBox.Entry(size = 1466), + TrackRunBox.Entry(size = 1741), + TrackRunBox.Entry(size = 1069), + TrackRunBox.Entry(size = 1451), + TrackRunBox.Entry(size = 1393), + TrackRunBox.Entry(size = 1196), + TrackRunBox.Entry(size = 1363), + TrackRunBox.Entry(size = 1667), + TrackRunBox.Entry(size = 1247), + TrackRunBox.Entry(size = 2091), + TrackRunBox.Entry(size = 1475), + TrackRunBox.Entry(size = 2164), + TrackRunBox.Entry(size = 1043), + TrackRunBox.Entry(size = 1324), + TrackRunBox.Entry(size = 1402), + TrackRunBox.Entry(size = 1206), + TrackRunBox.Entry(size = 1366), + TrackRunBox.Entry(size = 1671), + TrackRunBox.Entry(size = 1638), + TrackRunBox.Entry(size = 2223), + TrackRunBox.Entry(size = 1548) + ) + val trun = TrackRunBox( + version = 0, + dataOffset = 240, + firstSampleFlags = SampleFlags( + dependsOn = SampleDependsOn.NO_OTHER, + isNonSyncSample = false + ), + entries = entries + ) + val buffer = trun.toByteBuffer() + assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/VideoMediaHeaderBoxTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/VideoMediaHeaderBoxTest.kt index 8f0df7d8b..210cb991a 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/VideoMediaHeaderBoxTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/mp4/boxes/VideoMediaHeaderBoxTest.kt @@ -1,6 +1,21 @@ +/* + * Copyright (C) 2023 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.internal.muxers.mp4.boxes -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test @@ -10,7 +25,7 @@ class VideoMediaHeaderBoxTest { fun `write valid vmhd test`() { val expectedBuffer = ResourcesUtils.readMP4ByteBuffer("vmhd.box") val vmhd = VideoMediaHeaderBox() - val buffer = vmhd.write() + val buffer = vmhd.toByteBuffer() assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) } } \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/ByteBufferExtensionsKtTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/ByteBufferExtensionsKtTest.kt index fba43fb3c..f3e700b73 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/ByteBufferExtensionsKtTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/ByteBufferExtensionsKtTest.kt @@ -2,6 +2,7 @@ package io.github.thibaultbee.streampack.internal.utils import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.internal.utils.extensions.extractRbsp +import io.github.thibaultbee.streampack.internal.utils.extensions.putString import io.github.thibaultbee.streampack.internal.utils.extensions.slices import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfigTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfigTest.kt index c5097febd..50e14202a 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfigTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/AudioSpecificConfigTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.audio import io.github.thibaultbee.streampack.internal.utils.av.audio.aac.config.GASpecificConfig @@ -17,7 +32,7 @@ class AudioSpecificConfigTest { audioObjectType, channelConfiguration, frameLengthFlag = false, - dependsOnCodeCoder = false, + dependsOnCoreCoder = false, extensionFlag = false ) val audioSpecificConfig = @@ -41,7 +56,7 @@ class AudioSpecificConfigTest { audioObjectType, channelConfiguration, frameLengthFlag = false, - dependsOnCodeCoder = false, + dependsOnCoreCoder = false, extensionFlag = false ) val audioSpecificConfig = @@ -69,7 +84,7 @@ class AudioSpecificConfigTest { assertTrue(audioSpecificConfig.specificConfig is GASpecificConfig) val gaSpecificConfig = audioSpecificConfig.specificConfig as GASpecificConfig assertEquals(false, gaSpecificConfig.frameLengthFlag) - assertEquals(false, gaSpecificConfig.dependsOnCodeCoder) + assertEquals(false, gaSpecificConfig.dependsOnCoreCoder) assertEquals(false, gaSpecificConfig.extensionFlag) } diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElementTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElementTest.kt index a9dabb75d..00d422b11 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElementTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/AudioMuxElementTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.audio.aac import android.media.AudioFormat @@ -23,8 +38,8 @@ class AudioMuxElementTest { profile = MediaCodecInfo.CodecProfileLevel.AACObjectLC ) - val esds = AudioSpecificConfig.fromAudioConfig(config).toByteBuffer() - val streamMuxConfig = StreamMuxConfig.fromEsds(esds) + val decoderSpecificInfo = AudioSpecificConfig.fromAudioConfig(config).toByteBuffer() + val streamMuxConfig = StreamMuxConfig.fromDecoderSpecificInfo(decoderSpecificInfo) assertArrayEquals( expectedAudioMuxElement, streamMuxConfig.toByteBuffer().extractArray() @@ -45,8 +60,8 @@ class AudioMuxElementTest { channelConfig = AudioFormat.CHANNEL_IN_MONO, ) - val esds = AudioSpecificConfig.fromAudioConfig(config).toByteBuffer() - val audioMuxElement = AudioMuxElement.fromEsds(payload, esds) + val decoderSpecificInfo = AudioSpecificConfig.fromAudioConfig(config).toByteBuffer() + val audioMuxElement = AudioMuxElement.fromDecoderSpecificInfo(payload, decoderSpecificInfo) assertArrayEquals( expectedAudioMuxElement.array(), audioMuxElement.toByteBuffer().extractArray() diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/LATMFrameWriterTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/LATMFrameWriterTest.kt index fc4b79584..300d0029d 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/LATMFrameWriterTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/audio/aac/LATMFrameWriterTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.audio.aac import android.media.AudioFormat @@ -26,8 +41,8 @@ class LATMFrameWriterTest { profile = MediaCodecInfo.CodecProfileLevel.AACObjectLC ) - val esds = AudioSpecificConfig.fromAudioConfig(config).toByteBuffer() - val latm = LATMFrameWriter.fromEsds(payload, esds) + val decoderSpecificInfo = AudioSpecificConfig.fromAudioConfig(config).toByteBuffer() + val latm = LATMFrameWriter.fromDecoderSpecificInfo(payload, decoderSpecificInfo) assertArrayEquals( expectedLatm.array(), latm.toByteBuffer().extractArray() @@ -36,18 +51,16 @@ class LATMFrameWriterTest { @Test fun `test AAC-HE 44100Hz mono with payload`() { - val expectedLatm = ByteBuffer.wrap( - ResourcesUtils.readResources("test-samples/audio/latm/aac-he-44100Hz-mono/aac.latm") - ) + val expectedLatm = + ResourcesUtils.readByteBuffer("test-samples/audio/latm/aac-he-44100Hz-mono/aac.latm") - val esds = ByteBuffer.wrap( - ResourcesUtils.readResources("test-samples/audio/latm/aac-he-44100Hz-mono/esds.raw") - ) - val payload = ByteBuffer.wrap( - ResourcesUtils.readResources("test-samples/audio/latm/aac-he-44100Hz-mono/frame.raw") - ) + val decoderSpecificInfo = + ResourcesUtils.readByteBuffer("test-samples/audio/latm/aac-he-44100Hz-mono/esds.raw") + + val payload = + ResourcesUtils.readByteBuffer("test-samples/audio/latm/aac-he-44100Hz-mono/frame.raw") - val latm = LATMFrameWriter.fromEsds(payload, esds) + val latm = LATMFrameWriter.fromDecoderSpecificInfo(payload, decoderSpecificInfo) assertArrayEquals( expectedLatm.array(), latm.toByteBuffer().extractArray() @@ -56,18 +69,16 @@ class LATMFrameWriterTest { @Test fun `test AAC-HEv2 44100Hz stereo with payload`() { - val expectedLatm = ByteBuffer.wrap( - ResourcesUtils.readResources("test-samples/audio/latm/aac-hev2-44100Hz-stereo/aac.latm") - ) + val expectedLatm = + ResourcesUtils.readByteBuffer("test-samples/audio/latm/aac-hev2-44100Hz-stereo/aac.latm") - val esds = ByteBuffer.wrap( - ResourcesUtils.readResources("test-samples/audio/latm/aac-hev2-44100Hz-stereo/esds.raw") - ) - val payload = ByteBuffer.wrap( - ResourcesUtils.readResources("test-samples/audio/latm/aac-hev2-44100Hz-stereo/frame.raw") - ) + val decoderSpecificInfo = + ResourcesUtils.readByteBuffer("test-samples/audio/latm/aac-hev2-44100Hz-stereo/esds.raw") + + val payload = + ResourcesUtils.readByteBuffer("test-samples/audio/latm/aac-hev2-44100Hz-stereo/frame.raw") - val latm = LATMFrameWriter.fromEsds(payload, esds) + val latm = LATMFrameWriter.fromDecoderSpecificInfo(payload, decoderSpecificInfo) assertArrayEquals( expectedLatm.array(), latm.toByteBuffer().extractArray() diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/ESDescriptorTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/ESDescriptorTest.kt new file mode 100644 index 000000000..82c738987 --- /dev/null +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/descriptors/ESDescriptorTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 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.internal.utils.av.descriptors + +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray +import io.github.thibaultbee.streampack.utils.ResourcesUtils +import org.junit.Assert +import org.junit.Test +import java.nio.ByteBuffer + + +class ESDescriptorTest { + @Test + fun `write valid ESDescriptor test`() { + val expectedBuffer = + ResourcesUtils.readByteBuffer("test-samples/utils/av/descriptors/es.descriptor") + val decoderSpecificInfo = + ByteBuffer.wrap(byteArrayOf(0x11, 0x90.toByte(), 0x56, 0xE5.toByte(), 0)) + + val esDescriptor = ESDescriptor( + esId = 0, + streamPriority = 0, + dependsOnEsId = null, + url = null, + ocrEsId = null, + decoderConfigDescriptor = DecoderConfigDescriptor( + objectTypeIndication = ObjectTypeIndication.AUDIO_ISO_14496_3_AAC, + streamType = StreamType.AudioStream, + upStream = false, + bufferSize = 478, + maxBitrate = 139520, + avgBitrate = 95928, + decoderSpecificInfo = DecoderSpecificInfo(decoderSpecificInfo) + ), + slConfigDescriptor = SLConfigDescriptor(predefined = 2) + ) + + val buffer = esDescriptor.toByteBuffer() + Assert.assertArrayEquals(expectedBuffer.extractArray(), buffer.extractArray()) + } +} diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/video/AVCDecoderConfigurationRecordTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/video/AVCDecoderConfigurationRecordTest.kt index 54bb777a7..e8e0c3785 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/video/AVCDecoderConfigurationRecordTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/utils/av/video/AVCDecoderConfigurationRecordTest.kt @@ -1,7 +1,7 @@ package io.github.thibaultbee.streampack.internal.utils.av.video import io.github.thibaultbee.streampack.internal.utils.av.video.avc.AVCDecoderConfigurationRecord -import io.github.thibaultbee.streampack.internal.utils.extractArray +import io.github.thibaultbee.streampack.internal.utils.extensions.extractArray import io.github.thibaultbee.streampack.utils.ResourcesUtils import org.junit.Assert.assertArrayEquals import org.junit.Test diff --git a/core/src/test/resources/test-samples/muxer/mp4/mfhd.box b/core/src/test/resources/test-samples/muxer/mp4/mfhd.box new file mode 100644 index 000000000..7e66be797 Binary files /dev/null and b/core/src/test/resources/test-samples/muxer/mp4/mfhd.box differ diff --git a/core/src/test/resources/test-samples/muxer/mp4/mp4a.box b/core/src/test/resources/test-samples/muxer/mp4/mp4a.box new file mode 100644 index 000000000..1012e41fc Binary files /dev/null and b/core/src/test/resources/test-samples/muxer/mp4/mp4a.box differ diff --git a/core/src/test/resources/test-samples/muxer/mp4/tfdt.box b/core/src/test/resources/test-samples/muxer/mp4/tfdt.box new file mode 100644 index 000000000..a80ea4a1a Binary files /dev/null and b/core/src/test/resources/test-samples/muxer/mp4/tfdt.box differ diff --git a/core/src/test/resources/test-samples/muxer/mp4/tfhd.box b/core/src/test/resources/test-samples/muxer/mp4/tfhd.box new file mode 100644 index 000000000..f4ab8ce2a Binary files /dev/null and b/core/src/test/resources/test-samples/muxer/mp4/tfhd.box differ diff --git a/core/src/test/resources/test-samples/muxer/mp4/trun.box b/core/src/test/resources/test-samples/muxer/mp4/trun.box new file mode 100644 index 000000000..7fac00942 Binary files /dev/null and b/core/src/test/resources/test-samples/muxer/mp4/trun.box differ diff --git a/core/src/test/resources/test-samples/utils/av/descriptors/es.descriptor b/core/src/test/resources/test-samples/utils/av/descriptors/es.descriptor new file mode 100644 index 000000000..944edeb42 Binary files /dev/null and b/core/src/test/resources/test-samples/utils/av/descriptors/es.descriptor differ