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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/src/main/java/com/pedro/common/BitBuffer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class BitBuffer(val buffer: ByteBuffer) {

rbsp.put(buffer, 0, headerLength)

var previous = buffer.position()
var previous = headerLength
indices.forEach {
rbsp.put(buffer, previous, it + 2 - previous)
previous = it + 3 // skip emulation_prevention_three_byte
Expand Down
10 changes: 6 additions & 4 deletions common/src/main/java/com/pedro/common/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ fun ByteBuffer.toByteArray(
}

fun ByteBuffer.getStartCodeSize(): Int {
if (this.remaining() < 4) return 0
//the start code is searched using absolute indexes so the limit is the value that matters
if (this.limit() < 4) return 0
var startCodeSize = 0
if (this.get(0).toInt() == 0x00 && this.get(1).toInt() == 0x00
&& this.get(2).toInt() == 0x00 && this.get(3).toInt() == 0x01) {
Expand Down Expand Up @@ -348,9 +349,10 @@ fun InetAddress.addressToString(): String {
fun ByteBuffer.getData(): ByteArray = removeHeader().toByteArray()

fun ByteBuffer.removeHeader(): ByteBuffer {
val startCodeSize = this.getStartCodeSize()
this.position(startCodeSize)
return this.slice()
val buffer = this.duplicate()
val startCodeSize = buffer.getStartCodeSize()
buffer.position(startCodeSize)
return buffer.slice()
}

fun ByteArray.writeUInt32(offset: Int, value: Int) {
Expand Down
56 changes: 56 additions & 0 deletions common/src/main/java/com/pedro/common/SpsH264Parser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.pedro.common

import java.nio.ByteBuffer

/**
* Created by pedro on 27/07/26.
*
* ISO/IEC 14496-10 7.3.2.1.1
*
*/
class SpsH264Parser {
var profileIdc = 0
var profileCompatibility = 0
var levelIdc = 0
//only present in the profiles of profilesWithChromaInfo, in other case the inferred values are used
var chromaFormat = 1
var bitDepthLumaMinus8 = 0
var bitDepthChromaMinus8 = 0

fun parse(sps: ByteArray) {
parse(ByteBuffer.wrap(sps))
}

fun parse(sps: ByteBuffer) {
val rbsp = BitBuffer.extractRbsp(ByteBuffer.wrap(sps.getData()), 1)
val bitBuffer = BitBuffer(rbsp)
bitBuffer.skip(8)
//profile_idc
profileIdc = bitBuffer.getInt(8)
//constraint_set0_flag to constraint_set5_flag and reserved_zero_2bits
profileCompatibility = bitBuffer.getInt(8)
//level_idc
levelIdc = bitBuffer.getInt(8)
//seq_parameter_set_id
bitBuffer.readUE()
if (profileIdc in profilesWithChromaInfo) {
//chroma_format_idc
chromaFormat = bitBuffer.readUE()
if (chromaFormat == 3) {
//separate_colour_plane_flag
bitBuffer.skipBool()
}
//bit_depth_luma_minus8
bitDepthLumaMinus8 = bitBuffer.readUE()
//bit_depth_chroma_minus8
bitDepthChromaMinus8 = bitBuffer.readUE()
}
//The buffer continue but we don't need read more
}

companion object {
private val profilesWithChromaInfo = listOf(
100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135
)
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
/*
* Copyright (C) 2024 pedroSG94.
*
* 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 com.pedro.rtmp.flv.video.config
package com.pedro.common

import com.pedro.common.BitBuffer
import com.pedro.common.toInt
import java.nio.ByteBuffer

/**
Expand All @@ -26,7 +8,7 @@ import java.nio.ByteBuffer
* ISO/IEC 23008-2 7.3.2.2.1
*
*/
class SPSH265Parser {
class SpsH265Parser {
var generalProfileSpace = 0
var generalTierFlag = 0
var generalProfileIdc = 0
Expand All @@ -42,7 +24,7 @@ class SPSH265Parser {
}

fun parse(sps: ByteBuffer) {
val rbsp = BitBuffer.extractRbsp(sps, 2)
val rbsp = BitBuffer.extractRbsp(ByteBuffer.wrap(sps.getData()), 2)
val bitBuffer = BitBuffer(rbsp)
//Dropping nal_unit_header
bitBuffer.skip(16)
Expand All @@ -69,7 +51,7 @@ class SPSH265Parser {
}

if (maxSubLayersMinus1 > 0) {
repeat((maxSubLayersMinus1..8).count()) {
repeat((maxSubLayersMinus1 until 8).count()) {
bitBuffer.skip(2) // reserved_zero_2bits
}
}
Expand Down Expand Up @@ -117,4 +99,4 @@ class SPSH265Parser {
bitDepthChromaMinus8 = bitBuffer.readUE()
//The buffer continue but we don't need read more
}
}
}
57 changes: 41 additions & 16 deletions common/src/main/java/com/pedro/common/av1/Av1Parser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,38 +54,45 @@ class Av1Parser {
val obuList = mutableListOf<Obu>()
var index = 0
while (index < av1Data.size) {
val header = readHeader(av1Data, index)
val header = readHeader(av1Data, index) ?: break
index += header.size
val leb128Value = readLeb128(av1Data, index)
val length = av1Data.sliceArray(index until index + leb128Value.second)
index += length.size
val data = av1Data.sliceArray(index until index + leb128Value.first.toInt())
val leb128 = if (((header[0].toInt() ushr 1) and 0x01) == 1) {
val b = readLeb128(av1Data, index) ?: break
index += b.size
b
} else {
header[0] = (header[0].toInt() or 0x02).toByte()
writeLeb128(av1Data.size.toLong() - index)
}
val leb128Length = leb128.leb128ToLength()
if (index + leb128Length > av1Data.size) break //discard obu with invalid leb128
val data = av1Data.sliceArray(index until index + leb128Length.toInt())
index += data.size
val obu = Obu(header, length, data)
obuList.add(obu)
obuList.add(Obu(header, leb128, data))
}
return obuList
}

private fun readHeader(av1Data: ByteArray, offset: Int): ByteArray {
val header = mutableListOf<Byte>()
private fun readHeader(av1Data: ByteArray, offset: Int): ByteArray? {
if (offset >= av1Data.size) return null
val info = av1Data[offset]
header.add(info)
val containExtended = ((info.toInt() ushr 2) and 0x01) == 1
if (containExtended) header.add(av1Data[offset + 1])
return header.toByteArray()
if (containExtended) {
if (offset + 1 >= av1Data.size) return null
return byteArrayOf(info, av1Data[offset + 1])
}
return byteArrayOf(info)
}

private fun readLeb128(data: ByteArray, offset: Int): Pair<Long, Int> {
var result: Long = 0
private fun readLeb128(data: ByteArray, offset: Int): ByteArray? {
var index = 0
var b: Byte
do {
if (index >= 8 || offset + index >= data.size) return null
b = data[offset + index]
result = result or ((b.toLong() and 0x7F) shl (index * 7))
index++
} while (b.toInt() and 0x80 != 0)
return Pair(result, index)
return data.sliceArray(offset until offset + index)
}

fun writeLeb128(length: Long) : ByteArray {
Expand All @@ -101,4 +108,22 @@ class Av1Parser {
} while (remainingValue != 0L)
return result.toByteArray()
}

fun leb128Size(value: Int): Int {
var size = 1
var v = value ushr 7
while (v != 0) {
size++
v = v ushr 7
}
return size
}

private fun ByteArray.leb128ToLength(): Long {
var result = 0L
for (i in this.indices) {
result = result or ((this[i].toLong() and 0x7F) shl (i * 7))
}
return result
}
}
163 changes: 163 additions & 0 deletions common/src/main/java/com/pedro/common/av1/Av1SequenceHeaderParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.pedro.common.av1

import com.pedro.common.BitBuffer
import com.pedro.common.toByteArray
import java.nio.ByteBuffer

class Av1SequenceHeaderParser {

var seqProfile = 0
var seqLevelIdx = 0
var seqTier = false
var highBitDepth = false
var twelveBit = false
var monochrome = false
var subsamplingX = false
var subsamplingY = false
var samplePosition = 0
var initialDisplayDelayPresentFlag = false
var initialPresentationDelay = 0


fun parse(sequenceObu: ByteBuffer) {
parse(sequenceObu.toByteArray())
}

fun parse(sequenceObu: ByteArray) {
val av1Parser = Av1Parser()
val obu = av1Parser.getObus(sequenceObu).firstOrNull {
av1Parser.getObuType(it.header[0]) == ObuType.SEQUENCE_HEADER
} ?: throw IllegalArgumentException("sequence header obu not found")
val bitBuffer = BitBuffer(ByteBuffer.wrap(obu.data))

seqProfile = bitBuffer.getInt(3)
bitBuffer.skipBool()
val reducedStillPictureHeader = bitBuffer.getBool()
initialDisplayDelayPresentFlag = false
if (reducedStillPictureHeader) {
seqLevelIdx = bitBuffer.getInt(5)
} else {
val timingInfoPresentFlag = bitBuffer.getBool()
var decoderModelInfoPresentFlag = false
var bufferDelayLengthMinus1 = 0
if (timingInfoPresentFlag) {
bitBuffer.skip(64)
val equalPictureInterval = bitBuffer.getBool()
if (equalPictureInterval) {
bitBuffer.readUVLC()
}
decoderModelInfoPresentFlag = bitBuffer.getBool()
if (decoderModelInfoPresentFlag) {
bufferDelayLengthMinus1 = bitBuffer.getInt(5)
bitBuffer.skip(42) //skip this
}
}
initialDisplayDelayPresentFlag = bitBuffer.getBool()
val operatingPointsCntMinus1 = bitBuffer.getInt(5)
for (i in 0..operatingPointsCntMinus1) {
bitBuffer.skip(12) //skip
val levelIdx = bitBuffer.getInt(5)
if (i == 0) seqLevelIdx = levelIdx
if (levelIdx > 7) {
val sTier = bitBuffer.getBool()
if (i == 0) seqTier = sTier
}
if (decoderModelInfoPresentFlag) {
val decoderModelPresentForThisOp = bitBuffer.getBool()
if (decoderModelPresentForThisOp) {
val n = bufferDelayLengthMinus1 + 1
bitBuffer.skip(n * 2 + 1) //skip this
}
}
if (initialDisplayDelayPresentFlag) {
val initialDisplayDelayPresentForThisOp = bitBuffer.getBool()
if (initialDisplayDelayPresentForThisOp) {
val initialDisplayDelayMinus1 = bitBuffer.getInt(4)
if (i == 0) initialPresentationDelay = initialDisplayDelayMinus1
}
}
}
}

val frameWidthBitsMinus1 = bitBuffer.getInt(4)
val frameHeightBitsMinus1 = bitBuffer.getInt(4)
bitBuffer.skip(frameWidthBitsMinus1 + 1 + frameHeightBitsMinus1 + 1)
var frameIdNumbersPresentFlag = false
if (!reducedStillPictureHeader) {
frameIdNumbersPresentFlag = bitBuffer.getBool()
}
if (frameIdNumbersPresentFlag) bitBuffer.skip(7)
bitBuffer.skip(3)
if (!reducedStillPictureHeader) {
bitBuffer.skip(4)
val enableOrderHint = bitBuffer.getBool()
if (enableOrderHint) bitBuffer.skip(2)
val seqChooseScreenContentTools = bitBuffer.getBool()
val seqForceScreenContentTools = seqChooseScreenContentTools || bitBuffer.getBool()
if (seqForceScreenContentTools) {
val seqChooseIntegerMv = bitBuffer.getBool()
if (!seqChooseIntegerMv) bitBuffer.skipBool()
}
if (enableOrderHint) bitBuffer.skip(3)
}
bitBuffer.skip(3)
//config color
highBitDepth = bitBuffer.getBool()
twelveBit = false
var bitDepth = 0
if (seqProfile == 2 && highBitDepth) {
twelveBit = bitBuffer.getBool()
bitDepth = if (twelveBit) 12 else 10
} else if (seqProfile <= 2) {
bitDepth = if (highBitDepth) 10 else 8
}
monochrome = if (seqProfile == 1) {
false
} else {
val chrome = bitBuffer.getBool()
chrome
}
val colorDescriptionPresentFlag = bitBuffer.getBool()
var colorPrimaries = 0
var transferCharacteristics = 0
var matrixCoefficients = 0
if (colorDescriptionPresentFlag) {
colorPrimaries = bitBuffer.getInt(8)
transferCharacteristics = bitBuffer.getInt(8)
matrixCoefficients = bitBuffer.getInt(8)
}
samplePosition = 0
if (monochrome) {
bitBuffer.getBool()
subsamplingX = true
subsamplingY = true
} else if (colorPrimaries == 1 && transferCharacteristics == 13 && matrixCoefficients == 0) {
subsamplingX = false
subsamplingY = false
} else {
bitBuffer.skipBool()
if (seqProfile == 0) {
subsamplingX = true
subsamplingY = true
} else if (seqProfile == 1) {
subsamplingX = false
subsamplingY = false
} else {
if (bitDepth == 12) {
subsamplingX = bitBuffer.getBool()
subsamplingY = if (subsamplingX) {
bitBuffer.getBool()
} else {
false
}
} else {
subsamplingX = true
subsamplingY = false
}
}
if (subsamplingX && subsamplingY) {
samplePosition = bitBuffer.getInt(2)
}
}
}
}
Loading
Loading