Files
video-v1/vav1/Vav1Player.Tests/Container/Av1BitstreamParserTests.cs
2025-09-17 04:16:34 +09:00

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();
}
}