191 lines
7.0 KiB
C#
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})"
|
|
};
|
|
}
|
|
}
|
|
} |