From b71660837912cd48e7e6f3805f6b44dc5c8079c0 Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 13 Jan 2026 11:52:08 -0800 Subject: [PATCH 1/2] fix: Skip NAL header byte when reading SPS profile data in HlsMuxer The SPS NAL unit format is: [NAL header, profile_idc, constraint_flags, level_idc, ...] The code was incorrectly reading from byte 0 (NAL header, typically 0x67) instead of byte 1 (profile_idc). This produced invalid codec strings like `avc1.676400` instead of valid ones like `avc1.64001f`, causing Shaka Player on web to fail with error 4032 (unable to parse codec). Co-Authored-By: Claude Opus 4.5 --- .../src/main/java/com/mrousavy/camera/core/HlsMuxer.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt b/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt index 1e41589..63be9ed 100644 --- a/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt +++ b/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt @@ -761,9 +761,11 @@ class HlsMuxer( val output = ByteArrayOutputStream() val dos = DataOutputStream(output) - val profileIdc = if (sps.isNotEmpty()) sps[0].toInt() and 0xFF else 0x42 - val profileCompat = if (sps.size > 1) sps[1].toInt() and 0xFF else 0x00 - val levelIdc = if (sps.size > 2) sps[2].toInt() and 0xFF else 0x1F + // SPS NAL unit format: [NAL header, profile_idc, constraint_flags, level_idc, ...] + // Skip byte 0 (NAL header, typically 0x67) to get the actual profile data + val profileIdc = if (sps.size > 1) sps[1].toInt() and 0xFF else 0x42 + val profileCompat = if (sps.size > 2) sps[2].toInt() and 0xFF else 0x00 + val levelIdc = if (sps.size > 3) sps[3].toInt() and 0xFF else 0x1F dos.writeByte(1) // configuration version dos.writeByte(profileIdc) // AVC profile -- 2.49.1 From dd26812a9c058d18118145cdfa30c81cf53085b8 Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 13 Jan 2026 12:22:43 -0800 Subject: [PATCH 2/2] fix: Add pasp box to declare square pixels (1:1) for web playback The codec string fix caused videos to appear squished on web players like Shaka. Adding an explicit pixel aspect ratio (pasp) box with 1:1 ratio tells the player not to apply any SAR scaling. Co-Authored-By: Claude Opus 4.5 --- .../main/java/com/mrousavy/camera/core/HlsMuxer.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt b/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt index 63be9ed..891844c 100644 --- a/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt +++ b/package/android/src/main/java/com/mrousavy/camera/core/HlsMuxer.kt @@ -753,10 +753,23 @@ class HlsMuxer( dos.writeShort(-1) // pre-defined output.write(buildAvcCBox(sps, pps)) + output.write(buildPaspBox()) return wrapBox("avc1", output.toByteArray()) } + /** + * Builds pixel aspect ratio box to explicitly declare square pixels (1:1). + * This helps players correctly interpret video dimensions without SAR scaling. + */ + private fun buildPaspBox(): ByteArray { + val output = ByteArrayOutputStream() + val dos = DataOutputStream(output) + dos.writeInt(1) // hSpacing (horizontal) + dos.writeInt(1) // vSpacing (vertical) + return wrapBox("pasp", output.toByteArray()) + } + private fun buildAvcCBox(sps: ByteArray, pps: ByteArray): ByteArray { val output = ByteArrayOutputStream() val dos = DataOutputStream(output) -- 2.49.1