Fix orientation issues

This commit is contained in:
2025-12-28 01:14:44 -08:00
parent e60c1a4eb1
commit b79f876114
2 changed files with 79 additions and 26 deletions

View File

@@ -42,12 +42,14 @@ class FragmentedRecordingManager(
val cameraOrientationDegrees = cameraOrientation.toDegrees()
val recordingOrientationDegrees = (options.orientation ?: Orientation.PORTRAIT).toDegrees()
// Use size dimensions directly - the encoder output format will have the actual dimensions
// Don't swap based on orientation here; the camera pipeline handles that
val width = size.width
val height = size.height
// Swap dimensions based on camera orientation, same as ChunkedRecordingManager
val (width, height) = if (cameraOrientation.isLandscape()) {
size.height to size.width
} else {
size.width to size.height
}
Log.d(TAG, "Input size: ${size.width}x${size.height}, " +
Log.d(TAG, "Input size: ${size.width}x${size.height}, encoder size: ${width}x${height}, " +
"cameraOrientation: $cameraOrientation ($cameraOrientationDegrees°), " +
"recordingOrientation: $recordingOrientationDegrees°")

View File

@@ -478,31 +478,80 @@ class HlsMuxer(
dos.writeShort(0) // volume (0 for video)
dos.writeShort(0) // reserved
// Rotation matrix - use identity and rely on correct dimensions from encoder
// The encoder output format already has the correct dimensions for the content
writeRotationMatrix(dos)
// Rotation matrix based on orientationDegrees
writeRotationMatrix(dos, width, height)
// Use dimensions as-is from encoder output format
dos.writeInt(width shl 16) // width (16.16 fixed point)
dos.writeInt(height shl 16) // height (16.16 fixed point)
// For 90° and 270° rotations, the display dimensions are swapped
// The tkhd width/height represent the final display size after rotation
val (displayWidth, displayHeight) = when (orientationDegrees) {
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: ${width}x${height}, rotation=$orientationDegrees")
Log.d(TAG, "tkhd: encoder=${width}x${height}, display=${displayWidth}x${displayHeight}, rotation=$orientationDegrees")
return wrapBox("tkhd", output.toByteArray())
}
/**
* Writes the 3x3 transformation matrix for video rotation.
* Uses simple rotation values - the encoder already outputs correctly oriented frames.
* The matrix is applied to rotate the video content for correct display.
*
* 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) {
private fun writeRotationMatrix(dos: DataOutputStream, width: Int, height: Int) {
// Fixed-point constants
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
// Identity matrix - no transformation
// Most HLS players handle rotation via the dimensions themselves
// or we can add rotation metadata separately if needed
when (orientationDegrees) {
90 -> {
// 90° rotation: x' = y, y' = -x + width
dos.writeInt(0) // a = 0
dos.writeInt(negOne) // b = -1
dos.writeInt(0) // u = 0
dos.writeInt(one) // c = 1
dos.writeInt(0) // d = 0
dos.writeInt(0) // v = 0
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
@@ -513,6 +562,8 @@ class HlsMuxer(
dos.writeInt(0) // y = 0
dos.writeInt(w) // w = 1
}
}
}
private fun buildMdiaBox(width: Int, height: Int, sps: ByteArray, pps: ByteArray): ByteArray {
val content = ByteArrayOutputStream()