Browse Source

Parse HEVC NAL unit syntax

pull/2633/head
James Jackson-South 1 week ago
parent
commit
81fabe40bf
  1. 1
      HEIF_IMPLEMENTATION_PLAN.md
  2. 40
      THIRD-PARTY-NOTICES.TXT
  3. 180
      src/ImageSharp/Formats/Heif/Hevc/HevcBitReader.cs
  4. 23
      src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs
  5. 93
      src/ImageSharp/Formats/Heif/Hevc/HevcNalUnit.cs
  6. 68
      src/ImageSharp/Formats/Heif/Hevc/HevcNalUnitHeader.cs

1
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. |

40
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
-----

180
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;
/// <summary>
/// Reads fixed-width and Exp-Golomb HEVC syntax from a most-significant-bit-first byte span.
/// </summary>
internal ref struct HevcBitReader
{
/// <summary>
/// The complete raw byte sequence buffer.
/// </summary>
private readonly ReadOnlySpan<byte> data;
/// <summary>
/// The zero-based position of the next bit to read.
/// </summary>
private int bitPosition;
/// <summary>
/// Initializes a new instance of the <see cref="HevcBitReader"/> struct.
/// </summary>
/// <param name="data">The bounded HEVC syntax bytes.</param>
public HevcBitReader(ReadOnlySpan<byte> data)
{
this.data = data;
this.bitPosition = 0;
}
/// <summary>
/// Gets the zero-based position of the next bit to read.
/// </summary>
public readonly int BitPosition => this.bitPosition;
/// <summary>
/// Gets the number of unread bits in the bounded byte span.
/// </summary>
public readonly int BitsRemaining => (this.data.Length * 8) - this.bitPosition;
/// <summary>
/// Gets a value indicating whether the next bit begins a byte.
/// </summary>
public readonly bool IsByteAligned => (this.bitPosition & 7) == 0;
/// <summary>
/// Reads an unsigned fixed-width value in most-significant-bit-first order.
/// </summary>
/// <param name="bitCount">The number of bits to read.</param>
/// <returns>The decoded unsigned value.</returns>
/// <exception cref="InvalidImageContentException">
/// The requested value extends beyond the bounded HEVC syntax.
/// </exception>
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;
}
/// <summary>
/// Reads a one-bit HEVC flag.
/// </summary>
/// <returns><see langword="true"/> when the coded flag is one; otherwise, <see langword="false"/>.</returns>
/// <exception cref="InvalidImageContentException">The flag extends beyond the bounded HEVC syntax.</exception>
public bool ReadFlag() => this.ReadBits(1) != 0;
/// <summary>
/// Reads an unsigned exponential-Golomb value.
/// </summary>
/// <returns>The decoded unsigned value.</returns>
/// <exception cref="InvalidImageContentException">
/// The code is truncated or exceeds the range of a 32-bit unsigned integer.
/// </exception>
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;
}
/// <summary>
/// Reads a signed exponential-Golomb value.
/// </summary>
/// <returns>The decoded signed value.</returns>
/// <exception cref="InvalidImageContentException">
/// The code is truncated or exceeds the range of a 32-bit signed integer.
/// </exception>
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;
}
/// <summary>
/// Reads and validates the stop bit and zero alignment bits that terminate an HEVC raw byte sequence payload.
/// </summary>
/// <exception cref="InvalidImageContentException">
/// The trailing-bit pattern is truncated, malformed, or followed by additional data.
/// </exception>
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.");
}
}
}

23
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<byte>[] nalUnits = new ReadOnlyMemory<byte>[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<byte> 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
/// </summary>
/// <param name="nalUnitType">The six-bit HEVC NAL-unit type.</param>
/// <param name="isComplete">A value indicating whether the array contains every NAL unit of this type.</param>
/// <param name="nalUnits">The complete bounded NAL units, including their two-byte headers.</param>
public HevcNalUnitArray(byte nalUnitType, bool isComplete, ReadOnlyMemory<byte>[] nalUnits)
/// <param name="nalUnits">The decoded bounded NAL units.</param>
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; }
/// <summary>
/// Gets the complete NAL units, including their two-byte HEVC headers.
/// Gets the decoded NAL units.
/// </summary>
public IReadOnlyList<ReadOnlyMemory<byte>> NalUnits { get; }
public IReadOnlyList<HevcNalUnit> NalUnits { get; }
}

93
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;
/// <summary>
/// Contains one decoded HEVC network abstraction layer unit.
/// </summary>
internal sealed class HevcNalUnit
{
/// <summary>
/// Initializes a new instance of the <see cref="HevcNalUnit"/> class.
/// </summary>
/// <param name="data">The complete NAL unit, including its two-byte header.</param>
/// <exception cref="InvalidImageContentException">The NAL header or encoded payload is malformed.</exception>
public HevcNalUnit(ReadOnlySpan<byte> 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<byte> encodedPayload = data[2..];
byte[] rbspBuffer = new byte[encodedPayload.Length];
int rbspLength = HevcRbspDecoder.Decode(encodedPayload, rbspBuffer);
this.Rbsp = rbspBuffer.AsMemory(0, rbspLength);
}
/// <summary>
/// Gets the decoded two-byte NAL-unit header.
/// </summary>
public HevcNalUnitHeader Header { get; }
/// <summary>
/// Gets the raw byte sequence payload after removal of emulation-prevention bytes.
/// </summary>
public ReadOnlyMemory<byte> Rbsp { get; }
}
/// <summary>
/// Removes HEVC emulation-prevention bytes from an encoded raw byte sequence payload.
/// </summary>
internal static class HevcRbspDecoder
{
/// <summary>
/// Decodes an encoded byte sequence payload into a raw byte sequence payload.
/// </summary>
/// <param name="encodedPayload">The NAL payload following the two-byte header.</param>
/// <param name="destination">A buffer at least as long as <paramref name="encodedPayload"/>.</param>
/// <returns>The number of decoded bytes written to <paramref name="destination"/>.</returns>
/// <exception cref="InvalidImageContentException">
/// The payload contains a forbidden start-code-like byte sequence or an invalid emulation-prevention byte.
/// </exception>
public static int Decode(ReadOnlySpan<byte> encodedPayload, Span<byte> 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;
}
}

68
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;
/// <summary>
/// Contains the type, layer, and temporal identifier encoded by an HEVC NAL-unit header.
/// </summary>
internal readonly struct HevcNalUnitHeader
{
/// <summary>
/// Initializes a new instance of the <see cref="HevcNalUnitHeader"/> struct.
/// </summary>
/// <param name="nalUnitType">The six-bit NAL-unit type.</param>
/// <param name="layerId">The six-bit layer identifier.</param>
/// <param name="temporalId">The zero-based temporal identifier.</param>
private HevcNalUnitHeader(byte nalUnitType, byte layerId, byte temporalId)
{
this.NalUnitType = nalUnitType;
this.LayerId = layerId;
this.TemporalId = temporalId;
}
/// <summary>
/// Gets the six-bit NAL-unit type.
/// </summary>
public byte NalUnitType { get; }
/// <summary>
/// Gets the six-bit layer identifier.
/// </summary>
public byte LayerId { get; }
/// <summary>
/// Gets the zero-based temporal identifier.
/// </summary>
public byte TemporalId { get; }
/// <summary>
/// Reads and validates an HEVC NAL-unit header.
/// </summary>
/// <param name="data">The complete NAL unit beginning with its two-byte header.</param>
/// <returns>The decoded header.</returns>
/// <exception cref="InvalidImageContentException">
/// The header is truncated, its forbidden bit is set, or its temporal identifier is reserved.
/// </exception>
public static HevcNalUnitHeader Parse(ReadOnlySpan<byte> 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));
}
}
Loading…
Cancel
Save