Files
video-v1/vav1/Vav1Player.Tests/WebM/WebMDebugTests.cs
2025-09-18 01:00:04 +09:00

191 lines
7.0 KiB
C#

using FluentAssertions;
using System;
using System.IO;
using System.Linq;
using Vav1Player.Container;
using Xunit;
using Xunit.Abstractions;
namespace Vav1Player.Tests.WebM
{
public class WebMDebugTests
{
private readonly ITestOutputHelper _output;
private readonly string _webmFilePath;
public WebMDebugTests(ITestOutputHelper output)
{
_output = output;
var baseDir = AppContext.BaseDirectory;
var projectRoot = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", ".."));
_webmFilePath = Path.Combine(projectRoot, "sample", "output.webm");
}
[Fact]
public void WebM_ShouldParseMatroskaStructure_Correctly()
{
// Arrange
File.Exists(_webmFilePath).Should().BeTrue();
// Read first 50MB of WebM file to analyze structure
var fileData = File.ReadAllBytes(_webmFilePath);
var testData = fileData.Take(Math.Min(50 * 1024 * 1024, fileData.Length)).ToArray();
_output.WriteLine($"WebM file size: {fileData.Length} bytes, analyzing first {testData.Length} bytes");
// Act
var parser = new MatroskaParser(testData);
var tracks = parser.Parse();
// Assert
tracks.Should().NotBeEmpty("Should find at least one track");
var av1Track = tracks.FirstOrDefault(t => t.CodecId == "V_AV1");
av1Track.Should().NotBeNull("Should find AV1 video track");
_output.WriteLine($"Found {tracks.Count} tracks");
_output.WriteLine($"AV1 Track: {av1Track!.PixelWidth}x{av1Track.PixelHeight}");
_output.WriteLine($"AV1 Track blocks: {av1Track.Blocks.Count}");
// Analyze first few blocks in detail
for (int i = 0; i < Math.Min(5, av1Track.Blocks.Count); i++)
{
var block = av1Track.Blocks[i];
_output.WriteLine($"Block {i}: Size={block.Size}, Timestamp={block.Timestamp}, KeyFrame={block.IsKeyFrame}");
if (block.Data != null && block.Data.Length > 0)
{
var hexData = string.Join(" ", block.Data.Take(32).Select(b => b.ToString("X2")));
_output.WriteLine($" Data: [{hexData}]");
// Analyze OBU structure
AnalyzeOBUStructure(block.Data, $"Block {i}");
}
}
}
[Fact]
public void WebM_FirstKeyFrame_ShouldContainSequenceHeader()
{
// Arrange
File.Exists(_webmFilePath).Should().BeTrue();
var fileData = File.ReadAllBytes(_webmFilePath);
var testData = fileData.Take(Math.Min(50 * 1024 * 1024, fileData.Length)).ToArray();
// Act
var parser = new MatroskaParser(testData);
var tracks = parser.Parse();
var av1Track = tracks.FirstOrDefault(t => t.CodecId == "V_AV1");
av1Track.Should().NotBeNull();
av1Track!.Blocks.Should().NotBeEmpty();
// Find first keyframe
var firstKeyFrame = av1Track.Blocks.FirstOrDefault(b => b.IsKeyFrame);
firstKeyFrame.IsKeyFrame.Should().BeTrue("Should have at least one keyframe");
_output.WriteLine($"First keyframe: Size={firstKeyFrame.Size}, Timestamp={firstKeyFrame.Timestamp}");
if (firstKeyFrame.Data != null && firstKeyFrame.Data.Length > 0)
{
var hexData = string.Join(" ", firstKeyFrame.Data.Take(64).Select(b => b.ToString("X2")));
_output.WriteLine($"First keyframe data: [{hexData}]");
// Analyze for sequence header
bool hasSequenceHeader = AnalyzeForSequenceHeader(firstKeyFrame.Data);
_output.WriteLine($"Contains sequence header: {hasSequenceHeader}");
// Try to parse as OBUs
AnalyzeOBUStructure(firstKeyFrame.Data, "First KeyFrame");
}
}
private void AnalyzeOBUStructure(byte[] data, string label)
{
_output.WriteLine($"{label} OBU Analysis:");
if (data == null || data.Length == 0)
{
_output.WriteLine(" No data to analyze");
return;
}
try
{
// Try to parse as direct OBU stream
var obuList = Av1BitstreamParser.ParseMp4Sample(data);
_output.WriteLine($" Parsed as MP4 sample: {obuList.Count} OBUs found");
for (int i = 0; i < Math.Min(3, obuList.Count); i++)
{
var obu = obuList[i];
if (obu.Length > 0)
{
byte header = obu[0];
int obuType = (header >> 3) & 0xF;
bool hasSizeField = (header & 0x02) != 0;
string obuTypeName = GetOBUTypeName(obuType);
_output.WriteLine($" OBU {i}: Type={obuTypeName}, Size={obu.Length}, HasSizeField={hasSizeField}");
}
}
}
catch (Exception ex)
{
_output.WriteLine($" Failed to parse as MP4 sample: {ex.Message}");
}
// Analyze raw bytes
if (data.Length > 0)
{
byte firstByte = data[0];
int obuType = (firstByte >> 3) & 0xF;
bool extensionFlag = (firstByte & 0x4) != 0;
bool hasSizeField = (firstByte & 0x2) != 0;
string obuTypeName = GetOBUTypeName(obuType);
_output.WriteLine($" Raw analysis: First byte=0x{firstByte:X2}");
_output.WriteLine($" OBU Type: {obuType} ({obuTypeName})");
_output.WriteLine($" Extension Flag: {extensionFlag}");
_output.WriteLine($" Has Size Field: {hasSizeField}");
}
}
private bool AnalyzeForSequenceHeader(byte[] data)
{
if (data == null || data.Length < 2) return false;
// Look for sequence header OBU (type 1)
for (int i = 0; i < data.Length - 1; i++)
{
byte header = data[i];
int obuType = (header >> 3) & 0xF;
if (obuType == 1) // Sequence Header OBU
{
return true;
}
}
return false;
}
private string GetOBUTypeName(int obuType)
{
return obuType switch
{
0 => "Reserved",
1 => "Sequence Header",
2 => "Temporal Delimiter",
3 => "Frame Header",
4 => "Tile Group",
5 => "Metadata",
6 => "Frame",
7 => "Redundant Frame Header",
8 => "Tile List",
15 => "Padding",
_ => $"Unknown({obuType})"
};
}
}
}