using FluentAssertions; using Vav1Player.Container; namespace Vav1Player.Tests.Container; public class Av1BitstreamParserTests { [Fact] public void ParseMp4Sample_WithEmptyData_ShouldReturnEmptyList() { // Arrange var emptyData = Array.Empty(); // 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(); // 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 { 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 { 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(); 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(); // Act & Assert var action = () => Av1BitstreamParser.LogOBUInfo(emptyObu); action.Should().NotThrow(); } }