Skip to content

[Bug] H.264 1080p macroblock padding (codedHeight 1088) causes incorrect non-square 'pasp' (136:135) and squeezed video in Premiere Pro #527

Description

@faizur9

Summary
When encoding Full HD (1920×1080) video with H.264 / AVC via WebCodecs, Chromium encoders often pad the vertical dimension to a multiple of 16 ($1088$ px, as $1080$ is not divisible by 16).

In isobmff-muxer.ts, Mediabunny computes pixelAspectRatio using codedHeight ($1088$) against displayAspectHeight ($1080$), which simplifies to 136:135 (1.0074). Mediabunny then:

Injects a pasp (Pixel Aspect Ratio) atom into the sample entry with hSpacing: 136, vSpacing: 135.
Sets trackData.info.height (and consequently the tkhd track height and avc1 sample entry height) to codedHeight ($1088$) instead of the visible height ($1080$).
While casual media players (like VLC) ignore this, professional NLEs like Adobe Premiere Pro strictly honor container metadata. Premiere interprets the footage as anamorphic non-square pixels (PAR 1.0074), causing the video to be squeezed horizontally and vertically when imported into standard 1080p square-pixel timelines unless manually overridden with "Interpret Footage -> Square Pixels (1.0)".

Root Cause Details
In src/isobmff/isobmff-muxer.ts (around line 470):

typescript
const displayAspectWidth = decoderConfig.displayAspectWidth;
const displayAspectHeight = decoderConfig.displayAspectHeight;
const pixelAspectRatio = displayAspectWidth === undefined || displayAspectHeight === undefined
? { num: 1, den: 1 }
: simplifyRational({
num: displayAspectWidth * decoderConfig.codedHeight, // 1920 * 1088 = 2,088,960
den: displayAspectHeight * decoderConfig.codedWidth, // 1080 * 1920 = 2,073,600
}); // Simplifies to 136 / 135!
And in src/isobmff/isobmff-boxes.ts:

typescript
export const pasp = (trackData: IsobmffVideoTrackData) => {
if (trackData.info.pixelAspectRatio.num === trackData.info.pixelAspectRatio.den) {
return null; // Only omitted if 1:1
}
return box('pasp', [
u32(trackData.info.pixelAspectRatio.num), // writes 136
u32(trackData.info.pixelAspectRatio.den), // writes 135
]);
};
In H.264, the extra 8 lines from $1080$ to $1088$ are macroblock padding (which is already cropped out by frame_crop_bottom_offset in the SPS NAL unit), not non-square pixels. The pixels themselves are strictly Square Pixels (1:1).

Furthermore:

typescript
info: {
width: decoderConfig.codedWidth,
height: decoderConfig.codedHeight, // Sets 1088 in tkhd and visual sample entry instead of 1080
pixelAspectRatio,
...
}
Steps to Reproduce
Encode a 1920×1080 canvas using CanvasSource + Mp4OutputFormat in Chrome/Edge on Windows with codec: 'avc'.
Inspect the resulting MP4 container (e.g. with an MP4 box parser or MediaInfo):
A pasp atom is present with 136:135.
tkhd track height is 1088.
Import the MP4 file into Adobe Premiere Pro into a standard 1080p sequence.
Result: Video appears distorted/squeezed with pillarbox borders because Premiere treats it as 1.0074 PAR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions