mirror of https://github.com/SixLabors/ImageSharp
6 changed files with 393 additions and 12 deletions
@ -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."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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…
Reference in new issue