diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index b711f456b..0644a3201 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -112,6 +112,7 @@ This snapshot pins or classifies the available references and failures; it does | `HeifPixelAspectRatio`, `HeifItem.PixelAspectRatio`, and `HeifDecoderCore.ApplyItemPixelAspectRatioMetadata` | ISO/IEC 14496-12 section 12.1.4.3 pixel aspect ratio | libavif `src/read.c` function `avifParsePixelAspectRatioBox`, `src/write.c` function `avifEncoderWritePaspProperty`, and the presented-image property selection in `src/read.c` at `092276ce89098ead06db80975173191e5fee1826` | Preserve the two unsigned 32-bit relative spacings on the associated image item, reject zero or duplicate ratios, and map the displayed pixel width-to-height ratio into ImageSharp's existing unitless resolution metadata. Exchange the metadata axes after a quarter-turn presentation rotation and fall back from a derived grid to its first decodable tile only when the grid does not declare `pasp`. This remains one still-image presentation property and introduces no generic transform, sample-entry, or display model. | | `Av1CodecConfiguration`, `HeifItem.Av1CodecConfiguration`, `Av1HeifItemDecoder`, and AV1 grid configuration checks | AV1-ISOBMFF sections 2.3.3 and 2.3.4 codec-configuration record syntax and semantics; AVIF sections 2.1, 2.2.1, and 2.2.3 AV1 image-item, item-configuration, and HDR metadata constraints; AV1 sections 5.8.3, 5.8.4, 6.7.3, and 6.7.4 HDR metadata syntax and semantics; ISOBMFF mastering-display and content-light image properties; ITU-T H.274 section 8.9 mastering-display field semantics; MIAF section 7.3.11.4.1 grid input constraints | libavif `src/read.c` functions `avifParseCodecConfiguration`, `avifDecoderItemValidateProperties`, `avifReadCodecConfigProperty`, `avifParseContentLightLevelInformation`, and `avifSkipMasteringDisplayColourVolume` at `092276ce89098ead06db80975173191e5fee1826`; libaom `av1/decoder/obu.c` functions `read_metadata`, `read_metadata_hdr_cll`, and `read_metadata_hdr_mdcv` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Associate exactly one `av1C` property with each decoded `av01` image item, validate the fixed record and its bit depth/chroma fields against the item's AV1 sequence header and optional `pixi` channel depths, require matching configurations across grid tiles, and report the encoded image precision and monochrome shape through `HeifMetadata`. Validate low-overhead OBU framing, require exactly one sequence header in the image item, allow at most one first-position sequence header in `configOBUs`, and compare a repeated header's extension and payload exactly while ignoring only its legal size-field representation. Decode `clli` and `mdcv` as still-image item properties, validate matching HDR CLL and HDR MDCV metadata OBUs from the combined configuration/item sequence, and account for the different primary order and fixed-point precision of the ISOBMFF and AV1 MDCV representations. Expose the effective HDR values without adding sample groups, tracks, or media metadata. Related still-image HDR properties remain required. Consume but do not retain presentation-delay syntax, and introduce no sample entry, sample description, track, timing, or generic decoder-configuration model. | | `HevcCodecConfiguration`, `HevcNalUnitArray`, and `HeifItem.HevcCodecConfiguration` | ISO/IEC 14496-15 `HEVCDecoderConfigurationRecord`, HEVC NAL-unit header syntax, and HEVC image-item configuration requirements | HM `source/Lib/TLibDecoder/NALread.cpp` and parameter-header paths at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` v1.6.0 decoder NAL and parameter-header paths at `c83a76b084498d55f252f48b2e3786804cdf24b7`; independently produced HEIC fixtures for `hvcC` record layout | Associate exactly one `hvcC` property with each `hvc1` image item; validate the fixed profile, level, chroma, bit-depth, reserved, length-size, array, and NAL-header fields within the property boundary; and retain only the image description plus bounded configuration NAL units required to decode that item. Consume but do not retain average-frame-rate or temporal-layer presentation fields. Do not add visual sample entries, sample descriptions, tracks, timing, access-unit timelines, or a generic decoder-configuration model. BSD and Apache-2.0 notice preservation and separate HEVC patent/release clearance remain final integration gates. | +| `HevcNalUnit`, `HevcNalUnitHeader`, `HevcRbspDecoder`, and `HevcBitReader` | HEVC sections 7.3.1.1 and 7.3.1.2 NAL-unit and header syntax, section 7.3.2.11 RBSP trailing bits, and section 9.1 Exp-Golomb parsing | HM `source/Lib/TLibDecoder/NALread.cpp` and `SyntaxElementParser.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` NAL extraction and bitstream paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Validate the fixed NAL header, remove only legal emulation-prevention bytes, and provide bounded fixed-width, flag, unsigned/signed Exp-Golomb, and RBSP-trailing-bit reads. Configuration arrays immediately use the shared parser. The primitives consume only NAL units delivered by a supported HEIF image item or its `hvcC` property and introduce no Annex B API, access-unit model, timeline, track, sample table, or generic ISO BMFF surface. | | `HeifContentColorVolume`, `HeifItem.ContentColorVolume`, and `HeifDecoderCore` content color-volume parsing and presentation | HEIF content color-volume item property; AVIF 1.2 content color-volume requirements; ITU-T H.274 (V4) content colour volume syntax and semantics | libavif `src/read.c` function `avifSkipContentColourVolume` at `092276ce89098ead06db80975173191e5fee1826`; official ITU-T H.274 (V4), January 2026 | Decode only the bounded per-image `cclv` property: require zero cancellation, persistence, and reserved bits; preserve optional signed G/B/R primary coordinates and normalized minimum, maximum, and average luminance values; and validate their registered ranges and ordering. Expose the effective grid-or-tile still-image value through `HeifMetadata`. Do not add SEI persistence, retained video state, tracks, samples, timing, or a generic ISO BMFF color-volume box model. | | `HeifAmbientViewingEnvironment`, `HeifReferenceViewingEnvironment`, `HeifNominalDiffuseWhite`, and their per-item presentation metadata | ISOBMFF ambient viewing environment; ITU-T H.274 (V4) section 8.13; HEIF Amendment 1 sections 6.5.44 and 6.5.45; AVIF 1.2 image-item box requirements | libavif `src/read.c` functions `avifSkipAmbientViewingEnvironment`, `avifSkipReferenceViewingEnvironment`, and `avifSkipNominalDiffuseWhite` at `092276ce89098ead06db80975173191e5fee1826`; official ITU-T H.274 (V4), January 2026 | Decode the fixed-size `amve`, version-zero `reve`, and version-zero `ndwt` properties only when associated with the presented still-image item. Preserve physical illuminance and luminance units, distinct surround/periphery chromaticities, and the coded zero that requests the standard nominal diffuse-white default. Validate registered coordinate ranges and duplicate associations. Retain no video-SEI persistence, visual sample entry, display pipeline, track, timing, or generic viewing-environment box model. | | `GridHeifItemDecoder` and `HeifDecoderCore` grid/thumbnail selection | ISO/IEC 23008-12 section 6.6.2.3 image-grid syntax and MIAF grid-cell constraints | libavif `src/read.c` functions `avifParseImageGridBox`, `avifDecoderDataAllocateImagePlanes`, and `avifDecoderDataCopyTileToImage` at `092276ce89098ead06db80975173191e5fee1826` | Parse version-zero 16-bit and 32-bit grid descriptors, preserve row-major `dimg` order, require the declared tile count and one coding format, validate canvas coverage and edge overlap, and crop only the rightmost column and bottom row while copying through ImageSharp pixel buffers. A primary grid whose tile codec is unavailable may use only a decodable thumbnail that explicitly references that grid. | diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 3c43774f2..2d7aeee80 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -174,6 +174,46 @@ Alliance for Open Media Patent License 1.0 License was issued. +License notice for the HM HEVC Reference Software +----- + +Parts of the HEVC implementation are adapted from the HM HEVC Reference +Software. +https://vcgit.hhi.fraunhofer.de/jvet/HM + +The copyright in this software is being made available under the BSD +License, included below. This software may be subject to other third party +and contributor rights, including patent rights, and no such rights are +granted under this license. + +Copyright (c) 2010-2026, ITU/ISO/IEC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + License notice for the Ittiam libhevc Codec Library ----- diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcBitReader.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcBitReader.cs new file mode 100644 index 000000000..d0e5b6196 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcBitReader.cs @@ -0,0 +1,180 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Reads fixed-width and Exp-Golomb HEVC syntax from a most-significant-bit-first byte span. +/// +internal ref struct HevcBitReader +{ + /// + /// The complete raw byte sequence buffer. + /// + private readonly ReadOnlySpan data; + + /// + /// The zero-based position of the next bit to read. + /// + private int bitPosition; + + /// + /// Initializes a new instance of the struct. + /// + /// The bounded HEVC syntax bytes. + public HevcBitReader(ReadOnlySpan data) + { + this.data = data; + this.bitPosition = 0; + } + + /// + /// Gets the zero-based position of the next bit to read. + /// + public readonly int BitPosition => this.bitPosition; + + /// + /// Gets the number of unread bits in the bounded byte span. + /// + public readonly int BitsRemaining => (this.data.Length * 8) - this.bitPosition; + + /// + /// Gets a value indicating whether the next bit begins a byte. + /// + public readonly bool IsByteAligned => (this.bitPosition & 7) == 0; + + /// + /// Reads an unsigned fixed-width value in most-significant-bit-first order. + /// + /// The number of bits to read. + /// The decoded unsigned value. + /// + /// The requested value extends beyond the bounded HEVC syntax. + /// + public uint ReadBits(int bitCount) + { + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); + if (bitCount > this.BitsRemaining) + { + throw new InvalidImageContentException("The HEVC bitstream is truncated."); + } + + uint value = 0; + int remaining = bitCount; + while (remaining > 0) + { + // HEVC fixed-width syntax is MSB-first. Reading only the available portion of each byte keeps the + // same operation valid for both aligned parameter fields and fields that straddle byte boundaries. + int byteOffset = this.bitPosition >> 3; + int bitOffset = this.bitPosition & 7; + int bitsFromByte = Math.Min(remaining, 8 - bitOffset); + int shift = 8 - bitOffset - bitsFromByte; + uint mask = (1U << bitsFromByte) - 1; + + value = (value << bitsFromByte) | ((uint)(this.data[byteOffset] >> shift) & mask); + this.bitPosition += bitsFromByte; + remaining -= bitsFromByte; + } + + return value; + } + + /// + /// Reads a one-bit HEVC flag. + /// + /// when the coded flag is one; otherwise, . + /// The flag extends beyond the bounded HEVC syntax. + public bool ReadFlag() => this.ReadBits(1) != 0; + + /// + /// Reads an unsigned exponential-Golomb value. + /// + /// The decoded unsigned value. + /// + /// The code is truncated or exceeds the range of a 32-bit unsigned integer. + /// + public uint ReadUnsignedExpGolomb() + { + int leadingZeroBits = 0; + while (!this.ReadFlag()) + { + leadingZeroBits++; + if (leadingZeroBits > 32) + { + throw new InvalidImageContentException("The HEVC unsigned Exp-Golomb value exceeds 32 bits."); + } + } + + // In ue(v), the zero-prefix length selects an all-one basis and the equally wide suffix selects the + // offset from that basis. Keeping those parts separate makes the 32-bit overflow boundary explicit. + uint suffix = this.ReadBits(leadingZeroBits); + if (leadingZeroBits == 32) + { + // Only an all-zero suffix fits after the 32-bit all-one basis. + if (suffix != 0) + { + throw new InvalidImageContentException("The HEVC unsigned Exp-Golomb value exceeds 32 bits."); + } + + return uint.MaxValue; + } + + return ((1U << leadingZeroBits) - 1) + suffix; + } + + /// + /// Reads a signed exponential-Golomb value. + /// + /// The decoded signed value. + /// + /// The code is truncated or exceeds the range of a 32-bit signed integer. + /// + public int ReadSignedExpGolomb() + { + uint codeNumber = this.ReadUnsignedExpGolomb(); + + // HEVC's se(v) mapping alternates positive and negative magnitudes: 0, 1, -1, 2, -2, and so on. + if ((codeNumber & 1) == 0) + { + return -(int)(codeNumber >> 1); + } + + ulong magnitude = ((ulong)codeNumber + 1) >> 1; + if (magnitude > int.MaxValue) + { + throw new InvalidImageContentException("The HEVC signed Exp-Golomb value exceeds 32 bits."); + } + + return (int)magnitude; + } + + /// + /// Reads and validates the stop bit and zero alignment bits that terminate an HEVC raw byte sequence payload. + /// + /// + /// The trailing-bit pattern is truncated, malformed, or followed by additional data. + /// + public void ReadRbspTrailingBits() + { + // An RBSP ends with one stop bit followed only by zero bits up to the next byte boundary. + if (!this.ReadFlag()) + { + throw new InvalidImageContentException("The HEVC RBSP stop bit is not set."); + } + + while (!this.IsByteAligned) + { + if (this.ReadFlag()) + { + throw new InvalidImageContentException("The HEVC RBSP has a nonzero alignment bit."); + } + } + + // Each reader is bounded to one RBSP, so reaching alignment before the buffer end means the caller left + // syntax unread or the NAL unit contains bytes beyond its normative terminator. + if (this.BitsRemaining != 0) + { + throw new InvalidImageContentException("The HEVC RBSP contains unexpected trailing data."); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs index c3375549c..c7551222d 100644 --- a/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs @@ -98,7 +98,7 @@ internal sealed class HevcCodecConfiguration seenNalUnitTypes[nalUnitType] = true; int nalUnitCount = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); offset += 2; - ReadOnlyMemory[] nalUnits = new ReadOnlyMemory[nalUnitCount]; + HevcNalUnit[] nalUnits = new HevcNalUnit[nalUnitCount]; for (int nalUnitIndex = 0; nalUnitIndex < nalUnitCount; nalUnitIndex++) { if (data.Length - offset < 2) @@ -113,17 +113,16 @@ internal sealed class HevcCodecConfiguration throw new InvalidImageContentException("The HEVC codec configuration contains an invalid NAL-unit length."); } - ReadOnlySpan nalUnit = data.Slice(offset, nalUnitLength); - byte actualNalUnitType = (byte)((nalUnit[0] >> 1) & 0x3F); + HevcNalUnit nalUnit = new(data.Slice(offset, nalUnitLength)); - // The two-byte HEVC NAL header repeats the array's type. temporal_id_plus1 cannot be zero because - // zero is reserved to make header damage detectable before any parameter syntax is consumed. - if ((nalUnit[0] & 0x80) != 0 || (nalUnit[1] & 7) == 0 || actualNalUnitType != nalUnitType) + // The array header repeats the type so a damaged or misrouted parameter set is rejected before + // its RBSP syntax can affect the image configuration. + if (nalUnit.Header.NalUnitType != nalUnitType) { - throw new InvalidImageContentException("The HEVC codec configuration contains an invalid NAL-unit header."); + throw new InvalidImageContentException("The HEVC codec configuration NAL-unit type does not match its array."); } - nalUnits[nalUnitIndex] = nalUnit.ToArray(); + nalUnits[nalUnitIndex] = nalUnit; offset += nalUnitLength; } @@ -250,8 +249,8 @@ internal sealed class HevcNalUnitArray /// /// The six-bit HEVC NAL-unit type. /// A value indicating whether the array contains every NAL unit of this type. - /// The complete bounded NAL units, including their two-byte headers. - public HevcNalUnitArray(byte nalUnitType, bool isComplete, ReadOnlyMemory[] nalUnits) + /// The decoded bounded NAL units. + public HevcNalUnitArray(byte nalUnitType, bool isComplete, HevcNalUnit[] nalUnits) { this.NalUnitType = nalUnitType; this.IsComplete = isComplete; @@ -269,7 +268,7 @@ internal sealed class HevcNalUnitArray public bool IsComplete { get; } /// - /// Gets the complete NAL units, including their two-byte HEVC headers. + /// Gets the decoded NAL units. /// - public IReadOnlyList> NalUnits { get; } + public IReadOnlyList NalUnits { get; } } diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnit.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnit.cs new file mode 100644 index 000000000..8f5d64ba3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnit.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Contains one decoded HEVC network abstraction layer unit. +/// +internal sealed class HevcNalUnit +{ + /// + /// Initializes a new instance of the class. + /// + /// The complete NAL unit, including its two-byte header. + /// The NAL header or encoded payload is malformed. + public HevcNalUnit(ReadOnlySpan data) + { + this.Header = HevcNalUnitHeader.Parse(data); + + // Container and configuration NAL units carry EBSP bytes. Decode them once at the boundary so every + // parameter-set and slice parser observes the same validated RBSP representation. + ReadOnlySpan encodedPayload = data[2..]; + byte[] rbspBuffer = new byte[encodedPayload.Length]; + int rbspLength = HevcRbspDecoder.Decode(encodedPayload, rbspBuffer); + this.Rbsp = rbspBuffer.AsMemory(0, rbspLength); + } + + /// + /// Gets the decoded two-byte NAL-unit header. + /// + public HevcNalUnitHeader Header { get; } + + /// + /// Gets the raw byte sequence payload after removal of emulation-prevention bytes. + /// + public ReadOnlyMemory Rbsp { get; } +} + +/// +/// Removes HEVC emulation-prevention bytes from an encoded raw byte sequence payload. +/// +internal static class HevcRbspDecoder +{ + /// + /// Decodes an encoded byte sequence payload into a raw byte sequence payload. + /// + /// The NAL payload following the two-byte header. + /// A buffer at least as long as . + /// The number of decoded bytes written to . + /// + /// The payload contains a forbidden start-code-like byte sequence or an invalid emulation-prevention byte. + /// + public static int Decode(ReadOnlySpan encodedPayload, Span destination) + { + DebugGuard.MustBeGreaterThanOrEqualTo(destination.Length, encodedPayload.Length, nameof(destination)); + + int destinationOffset = 0; + int consecutiveZeroBytes = 0; + for (int sourceOffset = 0; sourceOffset < encodedPayload.Length; sourceOffset++) + { + byte value = encodedPayload[sourceOffset]; + + // HEVC section 7.3.1.1 forbids 00 00 00 through 00 00 02 in EBSP form. A 03 after two zeros is an + // emulation-prevention byte only when another byte in the range 00 through 03 follows it. + if (consecutiveZeroBytes == 2) + { + if (value < 3) + { + throw new InvalidImageContentException("The HEVC NAL unit contains a forbidden start-code-like byte sequence."); + } + + if (value == 3) + { + sourceOffset++; + if (sourceOffset == encodedPayload.Length || encodedPayload[sourceOffset] > 3) + { + throw new InvalidImageContentException("The HEVC NAL unit contains an invalid emulation-prevention byte."); + } + + // Removal depends on the preceding two decoded bytes, so this deliberately remains a single + // scalar pass rather than introducing a second SIMD behavior model for a non-hot syntax path. + value = encodedPayload[sourceOffset]; + consecutiveZeroBytes = 0; + } + } + + destination[destinationOffset++] = value; + consecutiveZeroBytes = value == 0 ? consecutiveZeroBytes + 1 : 0; + } + + return destinationOffset; + } +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnitHeader.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnitHeader.cs new file mode 100644 index 000000000..897e88d08 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnitHeader.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Contains the type, layer, and temporal identifier encoded by an HEVC NAL-unit header. +/// +internal readonly struct HevcNalUnitHeader +{ + /// + /// Initializes a new instance of the struct. + /// + /// The six-bit NAL-unit type. + /// The six-bit layer identifier. + /// The zero-based temporal identifier. + private HevcNalUnitHeader(byte nalUnitType, byte layerId, byte temporalId) + { + this.NalUnitType = nalUnitType; + this.LayerId = layerId; + this.TemporalId = temporalId; + } + + /// + /// Gets the six-bit NAL-unit type. + /// + public byte NalUnitType { get; } + + /// + /// Gets the six-bit layer identifier. + /// + public byte LayerId { get; } + + /// + /// Gets the zero-based temporal identifier. + /// + public byte TemporalId { get; } + + /// + /// Reads and validates an HEVC NAL-unit header. + /// + /// The complete NAL unit beginning with its two-byte header. + /// The decoded header. + /// + /// The header is truncated, its forbidden bit is set, or its temporal identifier is reserved. + /// + public static HevcNalUnitHeader Parse(ReadOnlySpan data) + { + if (data.Length < 2) + { + throw new InvalidImageContentException("The HEVC NAL-unit header is truncated."); + } + + // Use the same bounded MSB-first reader as the RBSP parsers so header truncation and field ordering have + // one behavior model instead of a second set of shifts and masks. + HevcBitReader reader = new(data[..2]); + bool forbiddenZeroBit = reader.ReadFlag(); + byte nalUnitType = (byte)reader.ReadBits(6); + byte layerId = (byte)reader.ReadBits(6); + byte temporalIdPlusOne = (byte)reader.ReadBits(3); + if (forbiddenZeroBit || temporalIdPlusOne == 0) + { + throw new InvalidImageContentException("The HEVC NAL-unit header is invalid."); + } + + return new HevcNalUnitHeader(nalUnitType, layerId, (byte)(temporalIdPlusOne - 1)); + } +}