add orientation and aspect ratio handling for landscape recording

This commit is contained in:
2025-12-23 21:56:17 -05:00
parent e60c1a4eb1
commit c43f4d3a80
3 changed files with 53 additions and 33 deletions

View File

@@ -429,15 +429,15 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
// Get actual device rotation from WindowManager since the React Native orientation hook // Get actual device rotation from WindowManager since the React Native orientation hook
// doesn't update when rotating between landscape-left and landscape-right on Android. // doesn't update when rotating between landscape-left and landscape-right on Android.
// Map device rotation to the correct orientationHint for video recording: // Map device rotation to the correct orientationHint for video recording:
// - Counter-clockwise (ROTATION_90) → 270° hint // - Counter-clockwise (ROTATION_90) → 90° hint
// - Clockwise (ROTATION_270) → 90° hint // - Clockwise (ROTATION_270) → 270° hint
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val deviceRotation = windowManager.defaultDisplay.rotation val deviceRotation = windowManager.defaultDisplay.rotation
val recordingOrientation = when (deviceRotation) { val recordingOrientation = when (deviceRotation) {
Surface.ROTATION_0 -> Orientation.PORTRAIT Surface.ROTATION_0 -> Orientation.PORTRAIT
Surface.ROTATION_90 -> Orientation.LANDSCAPE_RIGHT Surface.ROTATION_90 -> Orientation.LANDSCAPE_LEFT
Surface.ROTATION_180 -> Orientation.PORTRAIT_UPSIDE_DOWN Surface.ROTATION_180 -> Orientation.PORTRAIT_UPSIDE_DOWN
Surface.ROTATION_270 -> Orientation.LANDSCAPE_LEFT Surface.ROTATION_270 -> Orientation.LANDSCAPE_RIGHT
else -> Orientation.PORTRAIT else -> Orientation.PORTRAIT
} }
@@ -448,7 +448,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
enableAudio, enableAudio,
fps, fps,
videoOutput.enableHdr, videoOutput.enableHdr,
orientation, recordingOrientation,
options, options,
filePath, filePath,
callback, callback,

View File

@@ -39,17 +39,21 @@ class FragmentedRecordingManager(
segmentDurationSeconds: Int = DEFAULT_SEGMENT_DURATION_SECONDS segmentDurationSeconds: Int = DEFAULT_SEGMENT_DURATION_SECONDS
): FragmentedRecordingManager { ): FragmentedRecordingManager {
val mimeType = options.videoCodec.toMimeType() val mimeType = options.videoCodec.toMimeType()
val cameraOrientationDegrees = cameraOrientation.toDegrees() // Use cameraOrientation from Android (computed from device rotation)
val recordingOrientationDegrees = (options.orientation ?: Orientation.PORTRAIT).toDegrees() // instead of options.orientation from JS which may be stale
val recordingOrientationDegrees = cameraOrientation.toDegrees()
// Use size dimensions directly - the encoder output format will have the actual dimensions // Swap dimensions based on orientation - same logic as ChunkedRecordingManager
// Don't swap based on orientation here; the camera pipeline handles that // When camera is in landscape orientation, we need to swap width/height for the encoder
val width = size.width val (width, height) = if (cameraOrientation.isLandscape()) {
val height = size.height 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}, " +
"cameraOrientation: $cameraOrientation ($cameraOrientationDegrees°), " + "encoder size: ${width}x${height}, " +
"recordingOrientation: $recordingOrientationDegrees°") "orientation: $cameraOrientation ($recordingOrientationDegrees°)")
val format = MediaFormat.createVideoFormat(mimeType, width, height) val format = MediaFormat.createVideoFormat(mimeType, width, height)
val codec = MediaCodec.createEncoderByType(mimeType) val codec = MediaCodec.createEncoderByType(mimeType)

View File

@@ -478,40 +478,56 @@ 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 - use identity and rely on correct dimensions from encoder // Rotation matrix
// The encoder output format already has the correct dimensions for the content
writeRotationMatrix(dos) writeRotationMatrix(dos)
// Use dimensions as-is from encoder output format // Display dimensions should be post-rotation dimensions
dos.writeInt(width shl 16) // width (16.16 fixed point) // For 90° or 270° rotation, swap width and height
dos.writeInt(height shl 16) // height (16.16 fixed point) val (displayWidth, displayHeight) = when (orientationDegrees) {
90, 270 -> height to width
else -> width to 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: encoded=${width}x${height}, display=${displayWidth}x${displayHeight}, rotation=$orientationDegrees")
return wrapBox("tkhd", output.toByteArray()) return wrapBox("tkhd", output.toByteArray())
} }
/** /**
* Writes the 3x3 transformation matrix for video rotation. * Writes the 3x3 transformation matrix for video rotation.
* Uses simple rotation values - the encoder already outputs correctly oriented frames.
*/ */
private fun writeRotationMatrix(dos: DataOutputStream) { private fun writeRotationMatrix(dos: DataOutputStream) {
// Fixed-point constants
val one = 0x00010000 // 1.0 in 16.16 val one = 0x00010000 // 1.0 in 16.16
val negOne = 0xFFFF0000.toInt() // -1.0 in 16.16
val w = 0x40000000 // 1.0 in 2.30 val w = 0x40000000 // 1.0 in 2.30
// Identity matrix - no transformation // For 270° device orientation (landscape-right), apply 90° CW rotation
// Most HLS players handle rotation via the dimensions themselves // For 90° device orientation (landscape-left), apply 270° CW rotation
// or we can add rotation metadata separately if needed val a: Int
dos.writeInt(one) // a = 1 val b: Int
dos.writeInt(0) // b = 0 val c: Int
val d: Int
when (orientationDegrees) {
90 -> { a = 0; b = negOne; c = one; d = 0 }
180 -> { a = negOne; b = 0; c = 0; d = negOne }
270 -> { a = 0; b = one; c = negOne; d = 0 }
else -> { a = one; b = 0; c = 0; d = one }
}
dos.writeInt(a)
dos.writeInt(b)
dos.writeInt(0) // u = 0 dos.writeInt(0) // u = 0
dos.writeInt(0) // c = 0 dos.writeInt(c)
dos.writeInt(one) // d = 1 dos.writeInt(d)
dos.writeInt(0) // v = 0 dos.writeInt(0) // v = 0
dos.writeInt(0) // x = 0 dos.writeInt(0) // tx = 0
dos.writeInt(0) // y = 0 dos.writeInt(0) // ty = 0
dos.writeInt(w) // w = 1 dos.writeInt(w) // w = 1.0
Log.d(TAG, "Rotation matrix for $orientationDegrees°")
} }
private fun buildMdiaBox(width: Int, height: Int, sps: ByteArray, pps: ByteArray): ByteArray { private fun buildMdiaBox(width: Int, height: Int, sps: ByteArray, pps: ByteArray): ByteArray {