fix: Use identity matrix for web player compatibility
Web players like Shaka don't support the tkhd rotation matrix properly. Use identity matrix and coded dimensions instead - the video will appear in the orientation it was encoded. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -544,91 +544,38 @@ class HlsMuxer(
|
|||||||
dos.writeShort(0) // volume (0 for video)
|
dos.writeShort(0) // volume (0 for video)
|
||||||
dos.writeShort(0) // reserved
|
dos.writeShort(0) // reserved
|
||||||
|
|
||||||
// Rotation matrix based on orientationDegrees
|
// Web players like Shaka don't support the tkhd rotation matrix properly.
|
||||||
writeRotationMatrix(dos, width, height)
|
// Use identity matrix and coded dimensions - the video will appear in the
|
||||||
|
// orientation it was encoded (usually portrait for mobile recordings).
|
||||||
|
// TODO: Find a better solution for web rotation (CSS transform, etc.)
|
||||||
|
writeIdentityMatrix(dos)
|
||||||
|
|
||||||
// For 90° and 270° rotations, the display dimensions are swapped
|
// Use coded dimensions (not rotated) for web compatibility
|
||||||
// The tkhd width/height represent the final display size after rotation
|
dos.writeInt(width shl 16) // width (16.16 fixed point)
|
||||||
val (displayWidth, displayHeight) = when (orientationDegrees) {
|
dos.writeInt(height shl 16) // height (16.16 fixed point)
|
||||||
90, 270 -> Pair(height, width)
|
|
||||||
else -> Pair(width, height)
|
|
||||||
}
|
|
||||||
dos.writeInt(displayWidth shl 16) // width (16.16 fixed point)
|
|
||||||
dos.writeInt(displayHeight shl 16) // height (16.16 fixed point)
|
|
||||||
|
|
||||||
Log.d(TAG, "tkhd: encoder=${width}x${height}, display=${displayWidth}x${displayHeight}, rotation=$orientationDegrees")
|
Log.d(TAG, "tkhd: encoder=${width}x${height}, display=${width}x${height}, rotation=0 (identity matrix for web compatibility)")
|
||||||
|
|
||||||
return wrapBox("tkhd", output.toByteArray())
|
return wrapBox("tkhd", output.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the 3x3 transformation matrix for video rotation.
|
* Writes identity (no rotation) transformation matrix.
|
||||||
* The matrix is applied to rotate the video content for correct display.
|
* Used for web compatibility since players like Shaka don't handle rotation matrices.
|
||||||
*
|
|
||||||
* Matrix format in tkhd box (all values in fixed-point):
|
|
||||||
* | a b u | where a,b,c,d are 16.16 fixed-point
|
|
||||||
* | c d v | and u,v are 2.30 fixed-point (always 0)
|
|
||||||
* | x y w | x,y are 16.16, w is 2.30 (always 1.0)
|
|
||||||
*
|
|
||||||
* For rotation by θ: a=cos(θ), b=sin(θ), c=-sin(θ), d=cos(θ)
|
|
||||||
* Translation (x,y) keeps the rotated video in the visible area.
|
|
||||||
*/
|
*/
|
||||||
private fun writeRotationMatrix(dos: DataOutputStream, width: Int, height: Int) {
|
private fun writeIdentityMatrix(dos: DataOutputStream) {
|
||||||
// Fixed-point constants
|
|
||||||
val one = 0x00010000 // 1.0 in 16.16
|
val one = 0x00010000 // 1.0 in 16.16
|
||||||
val negOne = -0x00010000 // -1.0 in 16.16 (will be written as unsigned)
|
|
||||||
val w = 0x40000000 // 1.0 in 2.30
|
val w = 0x40000000 // 1.0 in 2.30
|
||||||
|
|
||||||
when (orientationDegrees) {
|
dos.writeInt(one) // a = 1
|
||||||
90 -> {
|
dos.writeInt(0) // b = 0
|
||||||
// 90° rotation: x' = y, y' = -x + width
|
dos.writeInt(0) // u = 0
|
||||||
dos.writeInt(0) // a = 0
|
dos.writeInt(0) // c = 0
|
||||||
dos.writeInt(negOne) // b = -1
|
dos.writeInt(one) // d = 1
|
||||||
dos.writeInt(0) // u = 0
|
dos.writeInt(0) // v = 0
|
||||||
dos.writeInt(one) // c = 1
|
dos.writeInt(0) // x = 0
|
||||||
dos.writeInt(0) // d = 0
|
dos.writeInt(0) // y = 0
|
||||||
dos.writeInt(0) // v = 0
|
dos.writeInt(w) // w = 1
|
||||||
dos.writeInt(0) // x = 0
|
|
||||||
dos.writeInt(width shl 16) // y = width (translation)
|
|
||||||
dos.writeInt(w) // w = 1
|
|
||||||
}
|
|
||||||
180 -> {
|
|
||||||
// 180° rotation
|
|
||||||
dos.writeInt(negOne) // a = -1
|
|
||||||
dos.writeInt(0) // b = 0
|
|
||||||
dos.writeInt(0) // u = 0
|
|
||||||
dos.writeInt(0) // c = 0
|
|
||||||
dos.writeInt(negOne) // d = -1
|
|
||||||
dos.writeInt(0) // v = 0
|
|
||||||
dos.writeInt(width shl 16) // x = width (translation)
|
|
||||||
dos.writeInt(height shl 16) // y = height (translation)
|
|
||||||
dos.writeInt(w) // w = 1
|
|
||||||
}
|
|
||||||
270 -> {
|
|
||||||
// 270° rotation: x' = -y + height, y' = x
|
|
||||||
dos.writeInt(0) // a = 0
|
|
||||||
dos.writeInt(one) // b = 1
|
|
||||||
dos.writeInt(0) // u = 0
|
|
||||||
dos.writeInt(negOne) // c = -1
|
|
||||||
dos.writeInt(0) // d = 0
|
|
||||||
dos.writeInt(0) // v = 0
|
|
||||||
dos.writeInt(height shl 16) // x = height (translation)
|
|
||||||
dos.writeInt(0) // y = 0
|
|
||||||
dos.writeInt(w) // w = 1
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// 0° or unknown: identity matrix
|
|
||||||
dos.writeInt(one) // a = 1
|
|
||||||
dos.writeInt(0) // b = 0
|
|
||||||
dos.writeInt(0) // u = 0
|
|
||||||
dos.writeInt(0) // c = 0
|
|
||||||
dos.writeInt(one) // d = 1
|
|
||||||
dos.writeInt(0) // v = 0
|
|
||||||
dos.writeInt(0) // x = 0
|
|
||||||
dos.writeInt(0) // y = 0
|
|
||||||
dos.writeInt(w) // w = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildMdiaBox(width: Int, height: Int, sps: ByteArray, pps: ByteArray): ByteArray {
|
private fun buildMdiaBox(width: Int, height: Int, sps: ByteArray, pps: ByteArray): ByteArray {
|
||||||
|
|||||||
Reference in New Issue
Block a user