418 lines
14 KiB
C#
418 lines
14 KiB
C#
using FluentAssertions;
|
|
using Vav1Player.Container;
|
|
|
|
namespace Vav1Player.Tests.Container;
|
|
|
|
public class Av1BitstreamParserTests
|
|
{
|
|
[Fact]
|
|
public void ParseMp4Sample_WithEmptyData_ShouldReturnEmptyList()
|
|
{
|
|
// Arrange
|
|
var emptyData = Array.Empty<byte>();
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(emptyData);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithInvalidLEB128_ShouldReturnEmptyList()
|
|
{
|
|
// Arrange - LEB128 with continuation bit but no following byte
|
|
var invalidData = new byte[] { 0x80 }; // Continuation bit set but no following data
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(invalidData);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithValidSingleOBU_ShouldReturnOneOBU()
|
|
{
|
|
// Arrange - Simple OBU: length=4, then 4 bytes of data
|
|
var sampleData = new byte[]
|
|
{
|
|
0x04, // LEB128: length = 4
|
|
0x12, 0x00, 0x0A, 0x0A // 4 bytes of OBU data
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result[0].Should().HaveCount(4);
|
|
result[0].Should().BeEquivalentTo(new byte[] { 0x12, 0x00, 0x0A, 0x0A });
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithMultipleOBUs_ShouldReturnAllOBUs()
|
|
{
|
|
// Arrange - Two OBUs: first with length=2, second with length=3
|
|
var sampleData = new byte[]
|
|
{
|
|
0x02, // LEB128: length = 2
|
|
0xAA, 0xBB, // First OBU data
|
|
0x03, // LEB128: length = 3
|
|
0xCC, 0xDD, 0xEE // Second OBU data
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(2);
|
|
result[0].Should().BeEquivalentTo(new byte[] { 0xAA, 0xBB });
|
|
result[1].Should().BeEquivalentTo(new byte[] { 0xCC, 0xDD, 0xEE });
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithLargeLEB128_ShouldHandleMultiByteLength()
|
|
{
|
|
// Arrange - LEB128 encoded length of 200 (0xC8 = 200)
|
|
// 200 = 11001000 binary, encoded as 0xC8, 0x01 in LEB128
|
|
var dataSize = 200;
|
|
var obuData = new byte[dataSize];
|
|
for (int i = 0; i < dataSize; i++)
|
|
{
|
|
obuData[i] = (byte)(i % 256);
|
|
}
|
|
|
|
var sampleData = new byte[2 + dataSize];
|
|
sampleData[0] = 0xC8; // 200 & 0x7F | 0x80 = 0xC8
|
|
sampleData[1] = 0x01; // (200 >> 7) & 0x7F = 0x01
|
|
Array.Copy(obuData, 0, sampleData, 2, dataSize);
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result[0].Should().HaveCount(dataSize);
|
|
result[0].Should().BeEquivalentTo(obuData);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithOversizedOBU_ShouldStopParsing()
|
|
{
|
|
// Arrange - OBU claims to be 20MB (larger than 10MB limit)
|
|
var sampleData = new byte[]
|
|
{
|
|
0x80, 0x80, 0x80, 0x0A, // LEB128: ~20MB
|
|
0x12, 0x34 // Some data (but not 20MB worth)
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty(); // Should reject oversized OBU
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithIncompleteOBU_ShouldStopParsing()
|
|
{
|
|
// Arrange - OBU claims length of 10 but only 5 bytes available
|
|
var sampleData = new byte[]
|
|
{
|
|
0x0A, // LEB128: length = 10
|
|
0x12, 0x34, 0x56, 0x78, 0x9A // Only 5 bytes available
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty(); // Should stop when data is insufficient
|
|
}
|
|
|
|
[Fact]
|
|
public void CombineOBUs_WithEmptyList_ShouldReturnEmptyArray()
|
|
{
|
|
// Arrange
|
|
var emptyList = new List<byte[]>();
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.CombineOBUs(emptyList);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void CombineOBUs_WithSingleOBU_ShouldEnsureSizeField()
|
|
{
|
|
// Arrange - OBU without size field (bit 1 = 0)
|
|
var obu = new byte[] { 0x10, 0x34, 0x56 }; // Header 0x10 = 00010000 (no size field)
|
|
var obuList = new List<byte[]> { obu };
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.CombineOBUs(obuList);
|
|
|
|
// Assert - Should add size field
|
|
result[0].Should().Be(0x10 | 0x02); // Header with size field bit set (0x12)
|
|
result.Length.Should().BeGreaterThan(obu.Length); // Should be larger due to added size field
|
|
}
|
|
|
|
[Fact]
|
|
public void CombineOBUs_WithMultipleOBUs_ShouldConcatenateWithSizeFields()
|
|
{
|
|
// Arrange - OBUs without size fields
|
|
var obu1 = new byte[] { 0x10, 0x34 }; // Header 0x10, payload 0x34
|
|
var obu2 = new byte[] { 0x54, 0x78, 0x9A }; // Header 0x54, payload 0x78, 0x9A
|
|
var obu3 = new byte[] { 0xB8 }; // Header 0xB8, no payload
|
|
var obuList = new List<byte[]> { obu1, obu2, obu3 };
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.CombineOBUs(obuList);
|
|
|
|
// Assert - Should be larger due to added size fields and modified headers
|
|
result.Length.Should().BeGreaterThan(6); // Original total was 6, should be larger
|
|
|
|
// Verify first OBU has size field bit set
|
|
result[0].Should().Be((byte)(0x10 | 0x02)); // Header with size field bit (0x12)
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithRealVideoSample_ShouldExtractOBUs()
|
|
{
|
|
// This test requires the sample/output.mp4 file to be present
|
|
var sampleFilePath = Path.Combine("..", "..", "..", "..", "sample", "output.mp4");
|
|
|
|
// Skip test if sample file doesn't exist
|
|
if (!File.Exists(sampleFilePath))
|
|
{
|
|
// Use Skip.If when available, otherwise just return
|
|
return;
|
|
}
|
|
|
|
// Arrange - Read first few bytes from the actual MP4 file
|
|
// Note: This is a simplified test - real MP4 parsing would need proper MP4 container parsing
|
|
var fileBytes = File.ReadAllBytes(sampleFilePath);
|
|
|
|
// Act & Assert - Just ensure parsing doesn't crash
|
|
var action = () =>
|
|
{
|
|
// Try parsing some data from the file (this might not be valid AV1 data without proper MP4 parsing)
|
|
if (fileBytes.Length > 1000)
|
|
{
|
|
var sampleData = fileBytes.Skip(100).Take(1000).ToArray();
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
// Don't assert specific results since this isn't properly parsed MP4 data
|
|
// Just ensure it doesn't crash
|
|
}
|
|
};
|
|
|
|
action.Should().NotThrow();
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithZeroLengthOBU_ShouldStopParsing()
|
|
{
|
|
// Arrange - OBU with zero length (should be treated as end marker)
|
|
var sampleData = new byte[]
|
|
{
|
|
0x03, // LEB128: length = 3
|
|
0x12, 0x34, 0x56, // First valid OBU
|
|
0x00, // LEB128: length = 0 (should stop here)
|
|
0x99, 0x88 // Additional data that shouldn't be parsed
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result[0].Should().BeEquivalentTo(new byte[] { 0x12, 0x34, 0x56 });
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithTrailingPaddingBytes_ShouldStopGracefully()
|
|
{
|
|
// Arrange - Valid OBU followed by insufficient data for another OBU
|
|
var sampleData = new byte[]
|
|
{
|
|
0x02, // LEB128: length = 2
|
|
0xAA, 0xBB, // Valid OBU data
|
|
0xFF // Only 1 byte remaining (insufficient for next OBU)
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result[0].Should().BeEquivalentTo(new byte[] { 0xAA, 0xBB });
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithCorruptedLEB128Sequence_ShouldHandleGracefully()
|
|
{
|
|
// Arrange - LEB128 that would overflow uint32
|
|
var sampleData = new byte[]
|
|
{
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01, // 7-byte LEB128 (should be rejected)
|
|
0x12, 0x34
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty(); // Should reject due to overflow protection
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithMultiByteZeroLEB128_ShouldRejectAsInvalid()
|
|
{
|
|
// Arrange - Multi-byte encoding of zero (invalid LEB128)
|
|
var sampleData = new byte[]
|
|
{
|
|
0x80, 0x00, // Invalid: multi-byte encoding of zero
|
|
0x12, 0x34
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty(); // Should reject invalid multi-byte zero
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithExactBoundaryConditions_ShouldParseCorrectly()
|
|
{
|
|
// Arrange - Sample where OBU exactly fills remaining space
|
|
var sampleData = new byte[]
|
|
{
|
|
0x05, // LEB128: length = 5
|
|
0x11, 0x22, 0x33, 0x44, 0x55 // Exactly 5 bytes (perfect fit)
|
|
};
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result[0].Should().HaveCount(5);
|
|
result[0].Should().BeEquivalentTo(new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55 });
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseMp4Sample_WithValidThreeByteLength_ShouldParseCorrectly()
|
|
{
|
|
// Arrange - Test 3-byte LEB128 with known working values
|
|
var dataSize = 1000; // Reasonable size
|
|
var obuData = new byte[dataSize];
|
|
// Fill with pattern for verification
|
|
for (int i = 0; i < dataSize; i++)
|
|
{
|
|
obuData[i] = (byte)(i & 0xFF);
|
|
}
|
|
|
|
// Encode 1000 as LEB128: 1000 = 0x3E8
|
|
// 1000 = 11101000000, LEB128: 11101000 01111101 00000111 → 0xE8 0x07 (2 bytes, not 3)
|
|
// Let's use a value that needs 3 bytes: 16384 = 0x4000
|
|
var targetSize = 16384;
|
|
var largeObuData = new byte[targetSize];
|
|
for (int i = 0; i < targetSize; i++)
|
|
{
|
|
largeObuData[i] = (byte)(i & 0xFF);
|
|
}
|
|
|
|
// 16384 = 0x4000 → LEB128: 0x80 0x80 0x01 (3 bytes)
|
|
var sampleData = new byte[3 + targetSize];
|
|
sampleData[0] = 0x80; // (16384 & 0x7F) | 0x80 = 0x80
|
|
sampleData[1] = 0x80; // ((16384 >> 7) & 0x7F) | 0x80 = 0x80
|
|
sampleData[2] = 0x01; // (16384 >> 14) & 0x7F = 0x01
|
|
Array.Copy(largeObuData, 0, sampleData, 3, targetSize);
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(sampleData);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result[0].Should().HaveCount(targetSize);
|
|
// Verify pattern in first and last few bytes
|
|
result[0][0].Should().Be(0);
|
|
result[0][255].Should().Be(255);
|
|
result[0][targetSize - 1].Should().Be((byte)((targetSize - 1) & 0xFF));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(new byte[] { })] // Empty sample
|
|
[InlineData(new byte[] { 0x01 })] // Only length, no data
|
|
[InlineData(new byte[] { 0x80, 0x80 })] // Incomplete LEB128
|
|
[InlineData(new byte[] { 0x05, 0x11, 0x22 })] // Length=5 but only 2 bytes
|
|
public void ParseMp4Sample_WithVariousInvalidInputs_ShouldReturnEmptyList(byte[] invalidData)
|
|
{
|
|
// Act
|
|
var result = Av1BitstreamParser.ParseMp4Sample(invalidData);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void CombineOBUs_WithLargeNumberOfOBUs_ShouldPerformEfficiently()
|
|
{
|
|
// Arrange - Create 1000 small OBUs (headers without size field bit)
|
|
var obuList = new List<byte[]>();
|
|
|
|
for (int i = 0; i < 1000; i++)
|
|
{
|
|
var obu = new byte[10];
|
|
obu[0] = (byte)(i & 0xFC); // Ensure bit 1 (size field) is 0
|
|
for (int j = 1; j < 10; j++)
|
|
{
|
|
obu[j] = (byte)(i + j);
|
|
}
|
|
obuList.Add(obu);
|
|
}
|
|
|
|
// Act
|
|
var result = Av1BitstreamParser.CombineOBUs(obuList);
|
|
|
|
// Assert - Should be larger due to added size fields
|
|
result.Length.Should().BeGreaterThan(10000); // Original was 10000, should be larger
|
|
|
|
// Verify first OBU structure: modified header + size + payload
|
|
result[0].Should().Be((byte)(obuList[0][0] | 0x02)); // Header with size field bit set
|
|
result[1].Should().Be(0x09); // Size field (LEB128 of 9 bytes payload)
|
|
result[2].Should().Be(obuList[0][1]); // First payload byte
|
|
}
|
|
|
|
[Fact]
|
|
public void LogOBUInfo_WithValidOBUData_ShouldNotThrow()
|
|
{
|
|
// Arrange - Create sample OBU data with known header
|
|
var obuData = new byte[]
|
|
{
|
|
0x12, // OBU header: type=2 (temporal delimiter), no extension, has size
|
|
0x34, 0x56, 0x78 // OBU payload
|
|
};
|
|
|
|
// Act & Assert
|
|
var action = () => Av1BitstreamParser.LogOBUInfo(obuData, "[TEST] ");
|
|
action.Should().NotThrow();
|
|
}
|
|
|
|
[Fact]
|
|
public void LogOBUInfo_WithEmptyOBU_ShouldNotThrow()
|
|
{
|
|
// Arrange
|
|
var emptyObu = Array.Empty<byte>();
|
|
|
|
// Act & Assert
|
|
var action = () => Av1BitstreamParser.LogOBUInfo(emptyObu);
|
|
action.Should().NotThrow();
|
|
}
|
|
} |