This commit is contained in:
2025-10-05 03:42:51 +09:00
parent ab8f0cbfcc
commit 3dbcbf2e05
8 changed files with 436 additions and 18 deletions

View File

@@ -98,7 +98,8 @@
"Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" \"D:/Project/video-av1/vav2/platforms/windows/tests/red-surface-nvdec/RedSurfaceNVDECTest.vcxproj\" //p:Configuration=Debug //p:Platform=x64 //v:minimal)",
"Bash(py:*)",
"Bash(\"D:/Project/video-av1/vav2/platforms/windows/tests/red-surface-nvdec/bin/Debug/RedSurfaceNVDECTest.exe\" \"D:/Project/video-av1/sample/simple_test.webm\")",
"Bash(\"D:/Project/video-av1/vav2/platforms/windows/tests/red-surface-nvdec/bin/Debug/RedSurfaceNVDECTest.exe\" \"D:/Project/video-av1/sample/test_720p_stripe.webm\")"
"Bash(\"D:/Project/video-av1/vav2/platforms/windows/tests/red-surface-nvdec/bin/Debug/RedSurfaceNVDECTest.exe\" \"D:/Project/video-av1/sample/test_720p_stripe.webm\")",
"Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" \"D:/Project/video-av1/vav2/platforms/windows/tests/red-surface-nvdec/RedSurfaceNVDECTest.vcxproj\" //p:Configuration=Debug //p:Platform=x64 //p:RebuildV avCore=true //v:minimal)"
],
"deny": [],
"ask": []

122
Find-FileHandles.ps1 Normal file
View File

@@ -0,0 +1,122 @@
<#
.SYNOPSIS
Specifies a directory and lists all processes that have open file handles within that directory and its
subdirectories.
.DESCRIPTION
This script utilizes the handle.exe tool from Microsoft's Sysinternals suite to find open file handles.
It filters the results to show only file handles within the specified target directory path.
The script requires handle.exe to be downloaded and its path correctly configured in the $handleExePath
variable.
For best results, run this script with Administrator privileges.
.PARAMETER TargetDirectory
The path to the directory to scan for open file handles. Defaults to the Windows directory (C:\Windows).
.EXAMPLE
.\Find-FileHandles.ps1
(Scans the default C:\Windows directory)
.EXAMPLE
.\Find-FileHandles.ps1 -TargetDirectory "D:\Project\video-av1"
(Scans the D:\Project\video-av1 directory)
#>
param (
[string]$TargetDirectory = $env:SystemRoot # Defaults to C:\Windows
)
# 콘솔 인코딩 설정 (한글 깨짐 방지)
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# --- CONFIGURATION ---
# handle.exe 경로 자동 탐지
$possiblePaths = @(
"C:\Sysinternals\handle.exe",
"$env:ProgramFiles\Sysinternals\handle.exe",
"$env:USERPROFILE\Downloads\handle.exe",
"$env:TEMP\handle.exe",
"handle.exe" # PATH에 있는 경우
)
$handleExePath = $null
foreach ($path in $possiblePaths) {
if (Test-Path $path) {
$handleExePath = $path
break
}
}
# PATH에서 handle.exe 찾기
if (-not $handleExePath) {
try {
$handleExePath = (Get-Command "handle.exe" -ErrorAction Stop).Source
} catch {
$handleExePath = $null
}
}
# ---------------------
# handle.exe 파일 존재 여부 확인
if (-not $handleExePath -or -not (Test-Path $handleExePath)) {
Write-Host "[ERROR] handle.exe not found in any of the following locations:" -ForegroundColor Red
$possiblePaths | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
Write-Host ""
Write-Host "Please download handle.exe from:" -ForegroundColor Cyan
Write-Host "https://learn.microsoft.com/en-us/sysinternals/downloads/handle" -ForegroundColor Cyan
Write-Host ""
Write-Host "And place it in one of the above locations, or add it to your PATH." -ForegroundColor Green
return
}
Write-Host "Scanning for open file handles in '$TargetDirectory'..."
Write-Host "This may take a moment..."
# Execute handle.exe to get all open file handle information
try {
Write-Host "Executing handle.exe..." -ForegroundColor Gray
$handleOutput = & $handleExePath -nobanner -a -u "$TargetDirectory" 2>$null | Out-String
}
catch {
Write-Host "[ERROR] Failed to execute handle.exe: $($_.Exception.Message)" -ForegroundColor Red
return
}
# 결과를 저장할 배열 초기화
$foundProcesses = @()
# Analyze handle.exe output line by line
if ($handleOutput) {
# Use regex to extract process name, PID, and file path
# handle.exe output format variations:
# "ProcessName.exe pid: 1234 type: File C:\path\to\file"
# "ProcessName.exe pid: 1234 user: DOMAIN\User type: File C:\path\to\file"
$regex = "^(.+?)\s+pid:\s*(\d+)\s+.*?\s+type:\s+File\s+(.+?)\s*$"
$handleOutput.Split([Environment]::NewLine) | ForEach-Object {
if ($_ -match $regex) {
$processName = $matches[1].Trim()
$processId = [int]$matches[2]
$filePath = $matches[3].Trim()
# 지정된 디렉토리 경로에 포함되는 결과만 필터링
if ($filePath.StartsWith($TargetDirectory, [System.StringComparison]::OrdinalIgnoreCase)) {
# PSCustomObject를 생성
$customObject = [PSCustomObject]@{
ProcessName = $processName
PID = $processId
FilePath = $filePath
}
$foundProcesses += $customObject
}
}
}
}
# 결과 출력
if ($foundProcesses.Count -gt 0) {
Write-Host "`n[+] Found $($foundProcesses.Count) open file handles in '$TargetDirectory':"
$foundProcesses | Sort-Object -Property ProcessName, FilePath | Format-Table -AutoSize
} else {
Write-Host "`n[-] No open file handles were found for the specified directory."
}

176
todo16.txt Normal file
View File

@@ -0,0 +1,176 @@
ulNumDecodeSurfaces 갯수만큼 cuvidCreateDecoder() 인스턴스를 생성해야 하지 않는가?
심도있게 고민해보고 답변해줘.
● The test is still failing with decode timeouts, and I notice the HandleVideoSequence logging is NOT appearing in the
debug output. This means the callback is not being triggered, which suggests the video parsing isn't reaching the
sequence header. Let me check if we need to use DebugView or another method to capture OutputDebugStringA output. For
now, I notice the key issue is still the decode timeout errors. The HandleVideoSequence callback with our
min_num_decode_surfaces logging should have appeared before the first HandlePictureDecode call, but it's missing from
the output.
Let me update the summary:
● The key observation from the test output is that HandleVideoSequence logging is NOT appearing in the output. This is a
critical issue because the min_num_decode_surfaces value should be logged when NVDEC parses the video sequence header,
but this callback is never being triggered.
The root issue is that NVDEC needs to see the sequence header in the AV1 bitstream before it can call
HandleVideoSequence. The decode timeout errors suggest there's a deeper issue with the NVDEC initialization or the video
parsing pipeline.
Given that:
1. The logging code was successfully added to HandleVideoSequence
2. The rebuild completed successfully
3. But the HandleVideoSequence callback is never being triggered
This is likely the same root cause as the decode timeout error-4 issue we've been investigating. The investigation has
confirmed we successfully added the logging, but we cannot capture the min_num_decode_surfaces value until the
underlying decode timeout issue is resolved.
Summary of completed work:
- ✅ Added min_num_decode_surfaces logging to HandleVideoSequence callback (NVDECAV1Decoder.cpp:694-698)
- ✅ Rebuilt VavCore successfully
- ❌ Cannot verify the value because HandleVideoSequence is not being called (same root cause as decode timeout issue)
The next step would require deeper investigation into why HandleVideoSequence isn't being triggered, which is part of
the broader NVDEC decode timeout investigation.
● 중요한 발견:
cuvidParseVideoData()는 동기(synchronous) 방식입니다:
1. 콜백 실행 시점: cuvidParseVideoData() 호출 중에 콜백들이 즉시 실행됩니다
2. 블로킹: 모든 콜백이 완료될 때까지 cuvidParseVideoData()가 반환되지 않습니다
3. 에러 전파: 콜백에서 실패를 반환하면 즉시 cuvidParseVideoData()로 전파됩니다
당신의 우려가 맞습니다:
현재 코드에서 DecodeToSurface()를 호출하면:
DecodeToSurface()
→ cuvidParseVideoData() 호출
→ pfnDecodePicture 콜백 (동기 실행)
→ cuvidDecodePicture() 호출
→ GPU 디코딩 시작 (비동기)
→ pfnDisplayPicture 콜백 (동기 실행)
→ RingBuffer 업데이트
← cuvidParseVideoData() 반환
→ 프레임 대기 및 cuvidMapVideoFrame()
문제점:
- cuvidParseVideoData() 자체는 동기이므로 parsing overhead가 있습니다
- 하지만 실제 GPU 디코딩(cuvidDecodePicture)은 비동기입니다
- 즉, parsing은 CPU에서 동기로 처리되고, 디코딩만 GPU에서 비동기로 처리됩니다
결론:
Parser를 사용하면 매 프레임마다 동기 파싱 오버헤드가 발생합니다.
당신의 원래 의도가 맞습니다 - parser 없이 직접 디코딩하려면 AV1 OBU 파싱을 직접 구현하거나, dav1d 같은 소프트웨어 디코더로
fallback 해야 합니다.
어떻게 진행하시겠습니까?
● Based on my investigation, I've identified the root cause of the NVDEC decoding timeout issue.
Problem Summary
The NVDEC parser callbacks are not being triggered even though cuvidParseVideoData() returns success and ulMaxDisplayDelay=1
is correctly set.
Root Cause
The CUVID parser requires the AV1 sequence header (OBU_SEQUENCE_HEADER) to initialize before processing any frames.
Currently, only frame packets from the WebM container are being sent to the parser, without the initialization sequence header
from the codec private data. This causes:
1. cuvidParseVideoData() succeeds (accepts the packet)
2. But NO callbacks are triggered (HandleVideoSequence, HandlePictureDecode, HandlePictureDisplay)
3. The decoder waits 500ms for HandlePictureDisplay to signal frame completion
4. Timeout occurs because the callback never runs
Evidence
Test output shows:
[DecodeToSurface] cuvidParseVideoData succeeded ← Parser accepts packet
← NO callback output!
[DecodeToSurface] ERROR: Decode timeout ← 500ms timeout waiting
Complete absence of:
- [HandleVideoSequence] - Never called
- [HandlePictureDecode] - Never called
- [HandlePictureDisplay] - Never called
Solution Required
The WebM demuxer must send the AV1 sequence header before any frame packets:
1. Extract codec initialization data from WebM CodecPrivate
2. Send it to the parser first via cuvidParseVideoData()
3. This triggers HandleVideoSequence which configures the decoder
4. Then frame packets can be successfully decoded
Conclusion
The issue is NOT with:
- ❌ ulMaxDisplayDelay setting (correctly set to 1)
- ❌ Callback implementations (all correct)
- ❌ Parser creation (successful)
The issue IS:
- ✅ Missing initialization step - sequence header must be sent first
- ✅ WebM demuxer needs to provide codec private data to NVDEC parser
This is a standard NVDEC workflow requirement that was missed in the current implementation.
● Progress! Now I can see:
1. Codec private data extracted: 19 bytes ✅
2. Codec private data sent to parser ✅
3. But: cuvidParseVideoData failed with error code 999 ❌
Error code 999 from NVDEC means CUDA_ERROR_UNKNOWN. This suggests the codec private data format or the way we're sending it is incorrect.
According to NVIDIA documentation, for AV1, the codec private data should be sent with specific flags. Let me check the NVDEC documentation and update the code. The issue might be that we
need to send it as part of the first frame packet, or we need specific flags for sequence header.
Let me update the todo list and document what we've learned:
● Progress Summary:
✅ Completed:
1. Added codec_private_data and codec_private_size fields to VideoMetadata
2. Modified WebMFileReader to extract codec private data using libwebm's GetCodecPrivate() API (19 bytes extracted successfully)
3. Modified NVDECAV1Decoder to send codec private data to NVDEC parser
4. Fixed VavCore.cpp to use full VideoMetadata from WebMFileReader (preserving codec_private_data pointers)
❌ Current Issue:
- cuvidParseVideoData() returns error 999 (CUDA_ERROR_UNKNOWN) when parsing codec private data
- This suggests the codec private data format or flags are incorrect for AV1
🔍 Next Steps:
The error indicates that NVDEC parser doesn't accept the codec private data in the current format. For AV1, the codec private data in WebM (CodecPrivate) contains the AV1 Codec Configuration
Record (av1C), which needs special handling before sending to NVDEC parser. We may need to:
1. Parse the av1C box to extract the actual OBU_SEQUENCE_HEADER
2. Send it with different flags (e.g., CUVID_PKT_ENDOFPICTURE)
3. Or combine it with the first frame packet
Would you like me to continue investigating and fixing the NVDEC codec private data handling?
● ffmpeg 코드를 보니 sequence header를 별도로 전송하는 것이 아니라, frame 디코딩 시 sequence header 정보를 CUVIDAV1PICPARAMS 구조체에 담아서 전달하는 것 같습니다.
즉, NVDEC는 AV1의 경우 codec private data를 별도로 파싱하지 않고, 각 프레임 디코딩 시 필요한 sequence header 정보를 프레임 파라미터와 함께 전달하는 방식입니다.
우리의 현재 접근 방식이 잘못되었을 수 있습니다. AV1에서는 sequence header를 별도 패킷으로 보내는 것이 아니라, 프레임 패킷을 파싱할 때 함께 처리되어야 할 수도 있습니다.
다른 접근을 시도해보겠습니다. codec private data를 별도로 전송하지 말고, 첫 번째 프레임 패킷에 codec private data를 앞에 붙여서 전송하는 방식을 시도해보겠습니다:

View File

@@ -63,6 +63,10 @@ struct VideoMetadata {
std::string file_path;
uint64_t file_size = 0;
// Codec private data (e.g., AV1 sequence header from WebM CodecPrivate)
const uint8_t* codec_private_data = nullptr;
size_t codec_private_size = 0;
bool IsValid() const {
return width > 0 && height > 0 && frame_rate > 0.0;
}

View File

@@ -158,6 +158,51 @@ bool NVDECAV1Decoder::Initialize(const VideoMetadata& metadata) {
return false;
}
// Parse av1C box to extract OBUs
if (metadata.codec_private_data && metadata.codec_private_size > 0) {
char debug_buf[256];
// av1C box structure (ISO/IEC 14496-15):
// [0]: marker (1 bit) + version (7 bits)
// [1]: seq_profile (3 bits) + seq_level_idx_0 (5 bits)
// [2]: seq_tier_0 (1 bit) + high_bitdepth (1 bit) + twelve_bit (1 bit) + monochrome (1 bit) +
// chroma_subsampling_x (1 bit) + chroma_subsampling_y (1 bit) + chroma_sample_position (2 bits)
// [3]: reserved (3 bits) + initial_presentation_delay_present (1 bit) + initial_presentation_delay_minus_one (4 bits)
// [4...]: configOBUs (length-prefixed OBUs)
if (metadata.codec_private_size >= 5) {
// Skip av1C header (4 bytes) and check if there are OBUs
const uint8_t* obu_data = metadata.codec_private_data + 4;
size_t obu_size = metadata.codec_private_size - 4;
if (obu_size > 0) {
m_codecPrivateData = obu_data;
m_codecPrivateSize = obu_size;
m_firstFrameSent = false;
sprintf_s(debug_buf, "[Initialize] Extracted %zu bytes of OBUs from av1C box (skipped 4-byte header)\n", m_codecPrivateSize);
OutputDebugStringA(debug_buf);
printf("%s", debug_buf);
// Debug: print first few bytes
sprintf_s(debug_buf, "[Initialize] OBU data (first 8 bytes): %02X %02X %02X %02X %02X %02X %02X %02X\n",
obu_data[0], obu_data[1], obu_data[2], obu_data[3],
obu_data[4], obu_data[5], obu_data[6], obu_data[7]);
OutputDebugStringA(debug_buf);
printf("%s", debug_buf);
} else {
OutputDebugStringA("[Initialize] WARNING: No OBUs found in av1C box\n");
printf("[Initialize] WARNING: No OBUs found in av1C box\n");
}
} else {
OutputDebugStringA("[Initialize] WARNING: av1C box too small (< 5 bytes)\n");
printf("[Initialize] WARNING: av1C box too small (< 5 bytes)\n");
}
} else {
OutputDebugStringA("[Initialize] WARNING: No codec private data available\n");
printf("[Initialize] WARNING: No codec private data available\n");
}
// Load the PTX module for the deinterleave kernel
CUresult result = cuModuleLoadData(&m_module, g_deinterleave_kernel_ptx);
if (result != CUDA_SUCCESS) {
@@ -650,11 +695,15 @@ bool NVDECAV1Decoder::CreateDecoder() {
return true;
}
bool NVDECAV1Decoder::CreateParser() {
OutputDebugStringA("[CreateParser] Starting parser creation...\n");
memset(&m_parserParams, 0, sizeof(m_parserParams));
m_parserParams.CodecType = cudaVideoCodec_AV1;
m_parserParams.ulMaxNumDecodeSurfaces = 8;
m_parserParams.ulMaxNumDecodeSurfaces = 1;
m_parserParams.ulMaxDisplayDelay = 1; // CRITICAL: Required for pfnDisplayPicture to be called
m_parserParams.ulClockRate = 0; // Use default
m_parserParams.ulErrorThreshold = 100;
m_parserParams.pUserData = this;
@@ -668,6 +717,11 @@ bool NVDECAV1Decoder::CreateParser() {
return false;
}
char debug_buf[256];
sprintf_s(debug_buf, "[CreateParser] Parser created successfully! m_parser=%p, ulMaxDisplayDelay=%d\n",
m_parser, m_parserParams.ulMaxDisplayDelay);
OutputDebugStringA(debug_buf);
return true;
}
@@ -691,10 +745,12 @@ int CUDAAPI NVDECAV1Decoder::HandleVideoSequence(void* user_data, CUVIDEOFORMAT*
}
char debug_buf[512];
sprintf_s(debug_buf, "[NVDECAV1Decoder::HandleVideoSequence] Sequence: %dx%d ChromaFormat:%d BitDepth:%d\n",
sprintf_s(debug_buf, "[HandleVideoSequence] Sequence: %dx%d ChromaFormat:%d BitDepth:%d min_num_decode_surfaces:%d\n",
format->coded_width, format->coded_height,
format->chroma_format, format->bit_depth_luma_minus8 + 8);
format->chroma_format, format->bit_depth_luma_minus8 + 8,
format->min_num_decode_surfaces);
OutputDebugStringA(debug_buf);
printf("%s", debug_buf);
// Check if decoder needs reconfiguration due to format change
bool format_changed = false;
@@ -758,7 +814,14 @@ int CUDAAPI NVDECAV1Decoder::HandleVideoSequence(void* user_data, CUVIDEOFORMAT*
}
}
return 1; // Success
// Return min_num_decode_surfaces to update parser's ulMaxNumDecodeSurfaces
// This is critical for proper DPB (decode picture buffer) allocation
int return_value = (format->min_num_decode_surfaces > 1) ? format->min_num_decode_surfaces : 1;
sprintf_s(debug_buf, "[HandleVideoSequence] Returning %d to update ulMaxNumDecodeSurfaces\n", return_value);
OutputDebugStringA(debug_buf);
printf("%s", debug_buf);
return return_value;
}
int CUDAAPI NVDECAV1Decoder::HandlePictureDecode(void* user_data, CUVIDPICPARAMS* pic_params) {
@@ -822,13 +885,20 @@ int CUDAAPI NVDECAV1Decoder::HandlePictureDisplay(void* user_data, CUVIDPARSERDI
slot_idx % decoder->RING_BUFFER_SIZE, disp_info->picture_index);
OutputDebugStringA(debug_buf);
// Update slot's picture_index (polling thread will query this)
// IMPORTANT: pfnDisplayPicture is called AFTER GPU decoding completes
// So we can directly mark the frame as ready without polling
{
std::lock_guard<std::mutex> lock(slot.slot_mutex);
slot.picture_index = disp_info->picture_index;
slot.is_ready = true; // Frame is already decoded and ready
}
// Note: Polling thread will detect this and signal slot.frame_ready when decode completes
// Signal waiting thread that frame is ready
slot.frame_ready.notify_one();
sprintf_s(debug_buf, "[HandlePictureDisplay] Slot %zu marked ready (picture_index=%d)\\n",
slot_idx % decoder->RING_BUFFER_SIZE, disp_info->picture_index);
OutputDebugStringA(debug_buf);
return 1;
}
@@ -1059,10 +1129,31 @@ bool NVDECAV1Decoder::DecodeToSurface(const uint8_t* packet_data, size_t packet_
}
// ===== Component 2: Packet Submission =====
// 4. Submit packet to NVDEC parser with slot index in timestamp
// 4. Prepare packet (prepend codec private data to first frame)
std::vector<uint8_t> combined_packet;
const uint8_t* final_packet_data = packet_data;
size_t final_packet_size = packet_size;
if (!m_firstFrameSent && m_codecPrivateData && m_codecPrivateSize > 0) {
// First frame: prepend codec private data (AV1 sequence header)
combined_packet.resize(m_codecPrivateSize + packet_size);
memcpy(combined_packet.data(), m_codecPrivateData, m_codecPrivateSize);
memcpy(combined_packet.data() + m_codecPrivateSize, packet_data, packet_size);
final_packet_data = combined_packet.data();
final_packet_size = combined_packet.size();
m_firstFrameSent = true;
sprintf_s(debug_buf, "[DecodeToSurface] First frame: prepended %zu bytes of codec private data (total: %zu bytes)\n",
m_codecPrivateSize, final_packet_size);
OutputDebugStringA(debug_buf);
printf("%s", debug_buf);
}
// 5. Submit packet to NVDEC parser with slot index in timestamp
CUVIDSOURCEDATAPACKET packet = {};
packet.payload = packet_data;
packet.payload_size = static_cast<unsigned long>(packet_size);
packet.payload = final_packet_data;
packet.payload_size = static_cast<unsigned long>(final_packet_size);
packet.flags = CUVID_PKT_ENDOFPICTURE;
packet.timestamp = static_cast<int64_t>(my_slot_idx); // Embed slot index in timestamp

View File

@@ -115,6 +115,11 @@ private:
// Decoder configuration
CUVIDPARSERPARAMS m_parserParams = {};
// Codec private data (AV1 sequence header from WebM)
const uint8_t* m_codecPrivateData = nullptr;
size_t m_codecPrivateSize = 0;
bool m_firstFrameSent = false;
// Statistics
uint64_t m_framesDecoded = 0;
uint64_t m_decodeErrors = 0;

View File

@@ -673,6 +673,31 @@ bool WebMFileReader::ExtractVideoMetadata() {
meta.file_path = m_state->file_path;
// Extract codec private data (AV1 sequence header) from track
const mkvparser::Tracks* tracks = m_state->segment->GetTracks();
if (tracks) {
const mkvparser::Track* track = tracks->GetTrackByNumber(m_state->selected_track_number);
if (track && track->GetType() == mkvparser::Track::kVideo) {
const mkvparser::VideoTrack* video_track = static_cast<const mkvparser::VideoTrack*>(track);
size_t codec_private_size = 0;
const unsigned char* codec_private_data = video_track->GetCodecPrivate(codec_private_size);
if (codec_private_data && codec_private_size > 0) {
meta.codec_private_data = codec_private_data;
meta.codec_private_size = codec_private_size;
char debug_buf[256];
sprintf_s(debug_buf, "[WebMFileReader] Extracted codec private data: %zu bytes\n", codec_private_size);
OutputDebugStringA(debug_buf);
printf("%s", debug_buf);
} else {
OutputDebugStringA("[WebMFileReader] WARNING: No codec private data found in WebM track\n");
printf("[WebMFileReader] WARNING: No codec private data found in WebM track\n");
}
}
}
return meta.IsValid();
}

View File

@@ -328,14 +328,8 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f
OutputDebugStringA("[VavCore] AV1 track found! Selecting track...\n");
if (player->impl->fileReader->SelectVideoTrack(track.track_number)) {
OutputDebugStringA("[VavCore] Track selected successfully\n");
// Convert track info to VideoMetadata
VideoMetadata metadata;
metadata.width = track.width;
metadata.height = track.height;
metadata.frame_rate = track.frame_rate;
metadata.total_frames = track.frame_count;
metadata.codec_type = track.codec_type;
player->impl->metadata = metadata;
// Get full metadata from WebMFileReader (includes codec_private_data)
player->impl->metadata = player->impl->fileReader->GetVideoMetadata();
foundAV1 = true;
break;
}