This commit is contained in:
2025-10-03 19:00:15 +09:00
parent ef7fd02a8a
commit 7968c7e0be
29 changed files with 455 additions and 4617 deletions

View File

@@ -2,6 +2,7 @@
#include "App.xaml.h"
#include "MainWindow.xaml.h"
#include "VavCore/VavCore.h"
#include "src/Logger/SimpleLogger.h"
using namespace winrt;
using namespace winrt::Microsoft::UI::Xaml;
@@ -16,28 +17,26 @@ namespace winrt::Vav2Player::implementation
if (result == VAVCORE_SUCCESS)
{
const char* version = vavcore_get_version_string();
OutputDebugStringA("VavCore initialized successfully, version: ");
OutputDebugStringA(version);
OutputDebugStringA("\n");
LOGF_INFO("[App] VavCore initialized successfully, version: %s", version);
// Test creating a player
VavCorePlayer* player = vavcore_create_player();
if (player)
{
OutputDebugStringA("VavCore player created successfully\n");
LOGF_INFO("[App] VavCore player created successfully");
vavcore_destroy_player(player);
}
// C++ wrapper test disabled for now (only using C API)
// VavCore::VideoPlayer cppPlayer;
// OutputDebugStringA("VavCore C++ player created successfully\n");
// LOGF_INFO("[App] VavCore C++ player created successfully");
vavcore_cleanup();
OutputDebugStringA("VavCore cleanup completed\n");
LOGF_INFO("[App] VavCore cleanup completed");
}
else
{
OutputDebugStringA("Failed to initialize VavCore\n");
LOGF_ERROR("[App] Failed to initialize VavCore");
}
}

View File

@@ -234,6 +234,7 @@
</ClCompile>
<ClCompile Include="src\Logger\LogManager.cpp" />
<ClCompile Include="src\Logger\LogOutputs.cpp" />
<ClCompile Include="src\Logger\SimpleLogger.cpp" />
<!-- <ClCompile Include="src\Decoder\VideoDecoderFactory.cpp" /> -->
<!-- <ClCompile Include="src\Decoder\AV1Decoder.cpp" /> -->
<!-- <ClCompile Include="src\Decoder\AdaptiveAV1Decoder.cpp" /> -->

View File

@@ -298,7 +298,18 @@ namespace winrt::Vav2Player::implementation
// 2. Load the video file. VavCore will now see the pending D3D device
// and initialize the decoder in the correct GPU-accelerated mode.
std::wstring filePathStr(filePath.c_str());
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"About to call LoadVideo with path: " + std::wstring(filePath));
// Safety check: Verify m_playbackController is valid
if (!m_playbackController) {
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"CRITICAL: m_playbackController is nullptr!");
UpdateStatus(L"Load failed - internal error");
return;
}
bool success = m_playbackController->LoadVideo(filePathStr);
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2",
success ? L"LoadVideo returned TRUE" : L"LoadVideo returned FALSE");
if (success) {
// 3. Get video dimensions (which are now available).
@@ -550,7 +561,8 @@ namespace winrt::Vav2Player::implementation
return;
}
processor->ProcessFrame(player, [weakThis = get_weak(), &consecutiveErrors](bool success) {
processor->ProcessFrame(player, [weakThis = get_weak()](bool success) {
// Static variable is already in function scope, no need to capture
if (auto strongThis = weakThis.get()) {
if (!success) {
consecutiveErrors++;

View File

@@ -1,277 +0,0 @@
#include "pch.h"
#include "AV1Decoder.h"
#include <iostream>
#include <cstring>
namespace Vav2Player {
// Dummy free callback for zero-copy decoding
static void DummyFreeCallback(const uint8_t* data, void* user_data) {
// Do nothing - packet data is managed externally
}
AV1Decoder::AV1Decoder()
: m_dav1d_context(nullptr)
, m_initialized(false) {
// Initialize default AV1 settings
m_av1_settings.max_frame_delay = 1;
m_av1_settings.num_threads = std::min(4, (int)std::thread::hardware_concurrency());
m_av1_settings.apply_grain = true;
m_av1_settings.all_layers = false;
}
AV1Decoder::~AV1Decoder() {
Cleanup();
}
bool AV1Decoder::Initialize(const VideoMetadata& metadata) {
if (m_initialized) {
LogError("Decoder already initialized");
return false;
}
if (metadata.codec_type != VideoCodecType::AV1) {
LogError("Invalid codec type for AV1 decoder");
return false;
}
// Initialize dav1d settings
Dav1dSettings settings;
dav1d_default_settings(&settings);
settings.max_frame_delay = m_av1_settings.max_frame_delay;
settings.n_threads = m_av1_settings.num_threads;
settings.apply_grain = m_av1_settings.apply_grain ? 1 : 0;
settings.all_layers = m_av1_settings.all_layers ? 1 : 0;
// Open dav1d decoder
int ret = dav1d_open(&m_dav1d_context, &settings);
if (ret != 0 || !m_dav1d_context) {
LogError("Failed to open dav1d context: " + std::to_string(ret));
return false;
}
m_initialized = true;
std::cout << "[AV1Decoder_Headless] Initialized successfully" << std::endl;
std::cout << " Resolution: " << metadata.width << "x" << metadata.height << std::endl;
std::cout << " Frame rate: " << metadata.frame_rate << " fps" << std::endl;
std::cout << " Threads: " << settings.n_threads << std::endl;
return true;
}
void AV1Decoder::Cleanup() {
if (m_dav1d_context) {
dav1d_close(&m_dav1d_context);
m_dav1d_context = nullptr;
}
m_initialized = false;
}
bool AV1Decoder::IsInitialized() const {
return m_initialized;
}
bool AV1Decoder::DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) {
if (!input_packet.IsValid()) {
LogError("Invalid input packet");
return false;
}
return DecodeFrame(input_packet.data.get(), input_packet.size, output_frame);
}
bool AV1Decoder::DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) {
if (!m_initialized || !packet_data || packet_size == 0) {
LogError("Invalid parameters or decoder not initialized");
return false;
}
auto decode_start = std::chrono::high_resolution_clock::now();
// Create dav1d data structure with memory copy
Dav1dData data;
uint8_t* buffer = dav1d_data_create(&data, packet_size);
if (!buffer) {
LogError("Failed to create dav1d data buffer");
return false;
}
std::memcpy(buffer, packet_data, packet_size);
// Send data to decoder
int ret = dav1d_send_data(m_dav1d_context, &data);
if (ret != 0) {
LogError("Failed to send data to dav1d: " + std::to_string(ret));
return false;
}
// Get decoded picture
Dav1dPicture picture = {};
ret = dav1d_get_picture(m_dav1d_context, &picture);
if (ret != 0) {
LogError("Failed to get decoded picture: " + std::to_string(ret));
return false;
}
// Convert to our VideoFrame format
bool convert_success = ConvertDav1dPicture(picture, output_frame);
dav1d_picture_unref(&picture);
if (!convert_success) {
LogError("Failed to convert dav1d picture");
m_stats.decode_errors++;
return false;
}
// Update statistics
auto decode_end = std::chrono::high_resolution_clock::now();
auto decode_duration = std::chrono::duration<double, std::milli>(decode_end - decode_start);
UpdateDecodingStats(decode_duration.count(), packet_size);
output_frame.is_valid = true;
return true;
}
bool AV1Decoder::DecodeFrameZeroCopy(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) {
if (!m_initialized || !packet_data || packet_size == 0) {
LogError("Invalid parameters or decoder not initialized");
return false;
}
auto decode_start = std::chrono::high_resolution_clock::now();
// Create dav1d data structure with zero-copy (wrap existing data)
Dav1dData data;
int ret = dav1d_data_wrap(&data, const_cast<uint8_t*>(packet_data), packet_size, DummyFreeCallback, nullptr);
if (ret != 0) {
LogError("Failed to wrap packet data: " + std::to_string(ret));
return false;
}
// Send data to decoder
ret = dav1d_send_data(m_dav1d_context, &data);
if (ret != 0) {
LogError("Failed to send data to dav1d: " + std::to_string(ret));
return false;
}
// Get decoded picture
Dav1dPicture picture = {};
ret = dav1d_get_picture(m_dav1d_context, &picture);
if (ret != 0) {
LogError("Failed to get decoded picture: " + std::to_string(ret));
return false;
}
// Convert to our VideoFrame format
bool convert_success = ConvertDav1dPicture(picture, output_frame);
dav1d_picture_unref(&picture);
if (!convert_success) {
LogError("Failed to convert dav1d picture");
m_stats.decode_errors++;
return false;
}
// Update statistics
auto decode_end = std::chrono::high_resolution_clock::now();
auto decode_duration = std::chrono::duration<double, std::milli>(decode_end - decode_start);
UpdateDecodingStats(decode_duration.count(), packet_size);
output_frame.is_valid = true;
return true;
}
bool AV1Decoder::Reset() {
if (!m_initialized) {
return false;
}
dav1d_flush(m_dav1d_context);
return true;
}
bool AV1Decoder::Flush() {
if (!m_initialized) {
return false;
}
dav1d_flush(m_dav1d_context);
return true;
}
bool AV1Decoder::ConvertDav1dPicture(const Dav1dPicture& dav1d_picture, VideoFrame& output_frame) {
// Allocate YUV420P frame
if (!output_frame.AllocateYUV420P(dav1d_picture.p.w, dav1d_picture.p.h)) {
LogError("Failed to allocate VideoFrame");
return false;
}
// Copy Y plane
const uint8_t* src_y = static_cast<const uint8_t*>(dav1d_picture.data[0]);
uint8_t* dst_y = output_frame.y_plane.get();
for (uint32_t y = 0; y < output_frame.height; y++) {
std::memcpy(dst_y + y * output_frame.y_stride,
src_y + y * dav1d_picture.stride[0],
output_frame.width);
}
// Copy U plane
const uint8_t* src_u = static_cast<const uint8_t*>(dav1d_picture.data[1]);
uint8_t* dst_u = output_frame.u_plane.get();
for (uint32_t y = 0; y < output_frame.height / 2; y++) {
std::memcpy(dst_u + y * output_frame.u_stride,
src_u + y * dav1d_picture.stride[1],
output_frame.width / 2);
}
// Copy V plane
const uint8_t* src_v = static_cast<const uint8_t*>(dav1d_picture.data[2]);
uint8_t* dst_v = output_frame.v_plane.get();
for (uint32_t y = 0; y < output_frame.height / 2; y++) {
std::memcpy(dst_v + y * output_frame.v_stride,
src_v + y * dav1d_picture.stride[1],
output_frame.width / 2);
}
output_frame.color_space = ColorSpace::YUV420P;
return true;
}
void AV1Decoder::UpdateDecodingStats(double decode_time_ms, size_t packet_size) {
m_stats.total_frames_decoded++;
m_stats.total_bytes_processed += packet_size;
m_stats.total_decode_time_ms += decode_time_ms;
}
void AV1Decoder::LogError(const std::string& message) {
std::cout << "[AV1Decoder_Headless ERROR] " << message << std::endl;
}
void AV1Decoder::ApplyOptimalSettingsForResolution(uint32_t width, uint32_t height) {
// Apply resolution-based optimizations
AV1Settings settings = m_av1_settings;
if (width >= 3840 && height >= 2160) {
// 4K or higher - use more threads
settings.num_threads = 8;
settings.max_frame_delay = 2;
} else if (width >= 1920 && height >= 1080) {
// 1080p - balanced settings
settings.num_threads = 6;
settings.max_frame_delay = 1;
} else {
// Lower resolutions - fewer threads
settings.num_threads = 4;
settings.max_frame_delay = 1;
}
SetAV1Settings(settings);
std::cout << "[AV1Decoder_Headless] Applied optimal settings for " << width << "x" << height
<< " (threads=" << settings.num_threads << ", delay=" << settings.max_frame_delay << ")" << std::endl;
}
} // namespace Vav2Player

View File

@@ -1,101 +0,0 @@
#pragma once
#include "IVideoDecoder.h"
#include <dav1d.h>
#include <memory>
#include <chrono>
namespace Vav2Player {
// AV1 decoder using dav1d library
class AV1Decoder : public IVideoDecoder {
public:
AV1Decoder();
~AV1Decoder() override;
// Prevent copying
AV1Decoder(const AV1Decoder&) = delete;
AV1Decoder& operator=(const AV1Decoder&) = delete;
// IVideoDecoder interface implementation
bool Initialize(const VideoMetadata& metadata) override;
void Cleanup() override;
bool IsInitialized() const override;
bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) override;
bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override;
// Zero-copy decoding methods
bool DecodeFrameZeroCopy(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame);
bool Reset() override;
bool Flush() override;
// IVideoDecoder interface - additional methods
std::string GetCodecName() const override { return "AV1 (dav1d)"; }
VideoCodecType GetCodecType() const override { return VideoCodecType::AV1; }
std::string GetVersion() const override { return "dav1d headless"; }
DecoderStats GetStats() const override {
DecoderStats stats;
stats.frames_decoded = m_stats.total_frames_decoded;
stats.decode_errors = m_stats.decode_errors;
stats.avg_decode_time_ms = m_stats.GetAverageDecodeTime();
stats.bytes_processed = m_stats.total_bytes_processed;
return stats;
}
void ResetStats() override {
m_stats = DecodingStatistics{};
}
// Statistics and performance monitoring
struct DecodingStatistics {
uint64_t total_frames_decoded = 0;
uint64_t total_bytes_processed = 0;
double total_decode_time_ms = 0.0;
uint64_t decode_errors = 0;
double GetAverageDecodeTime() const {
return (total_frames_decoded > 0) ? (total_decode_time_ms / total_frames_decoded) : 0.0;
}
double GetAverageThroughput() const {
return (total_decode_time_ms > 0) ? ((total_bytes_processed / 1024.0 / 1024.0) / (total_decode_time_ms / 1000.0)) : 0.0;
}
};
const DecodingStatistics& GetStatistics() const { return m_stats; }
// AV1-specific settings
struct AV1Settings {
int max_frame_delay = 1;
int num_threads = 4;
bool apply_grain = true;
bool all_layers = false;
};
void SetAV1Settings(const AV1Settings& settings) { m_av1_settings = settings; }
const AV1Settings& GetAV1Settings() const { return m_av1_settings; }
// Compatibility method for GUI integration
void ApplyOptimalSettingsForResolution(uint32_t width, uint32_t height);
// Compatibility methods for pipeline integration
bool IsWaitingForMoreData() const { return false; } // Simple always ready
private:
// dav1d context and state
Dav1dContext* m_dav1d_context;
bool m_initialized;
// Statistics
DecodingStatistics m_stats;
AV1Settings m_av1_settings;
// Helper methods
bool ConvertDav1dPicture(const Dav1dPicture& dav1d_picture, VideoFrame& output_frame);
void UpdateDecodingStats(double decode_time_ms, size_t packet_size);
void LogError(const std::string& message);
};
} // namespace Vav2Player

View File

@@ -1,317 +0,0 @@
#include "pch.h"
#include "AdaptiveAV1Decoder.h"
#include "AdaptiveNVDECDecoder.h" // For shared types and utilities
#include <algorithm>
#include <cmath>
namespace Vav2Player {
AdaptiveAV1Decoder::AdaptiveAV1Decoder() : AV1Decoder() {
// Initialize with balanced configuration
m_config = AdaptiveUtils::GetBalancedConfig();
m_metrics.last_update = std::chrono::steady_clock::now();
}
AdaptiveAV1Decoder::~AdaptiveAV1Decoder() = default;
bool AdaptiveAV1Decoder::Initialize(const VideoMetadata& metadata, const AdaptiveConfig& config) {
m_config = config;
return Initialize(metadata);
}
bool AdaptiveAV1Decoder::Initialize(const VideoMetadata& metadata) {
// Store original dimensions for scaling calculations
m_originalWidth = metadata.width;
m_originalHeight = metadata.height;
m_targetScaledWidth = metadata.width;
m_targetScaledHeight = metadata.height;
// Initialize the base dav1d decoder
bool result = AV1Decoder::Initialize(metadata);
if (result) {
OutputDebugStringA("[AdaptiveAV1Decoder] Initialized with adaptive quality control (post-decode scaling)\n");
}
return result;
}
bool AdaptiveAV1Decoder::DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) {
return DecodeFrame(input_packet.data.get(), input_packet.size, output_frame);
}
bool AdaptiveAV1Decoder::DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) {
auto decode_start = std::chrono::high_resolution_clock::now();
// Step 1: Decode frame using dav1d at full resolution
VideoFrame full_resolution_frame;
bool decode_success = AV1Decoder::DecodeFrame(packet_data, packet_size, full_resolution_frame);
auto decode_end = std::chrono::high_resolution_clock::now();
double decode_time = std::chrono::duration<double, std::milli>(decode_end - decode_start).count();
if (!decode_success) {
return false;
}
// Step 2: Apply post-decode scaling if quality level requires it
if (m_currentQuality != QualityLevel::ULTRA) {
auto scale_start = std::chrono::high_resolution_clock::now();
bool scale_success = ScaleDecodedFrame(full_resolution_frame, output_frame);
auto scale_end = std::chrono::high_resolution_clock::now();
double scale_time = std::chrono::duration<double, std::milli>(scale_end - scale_start).count();
// Add scaling time to decode time for performance analysis
decode_time += scale_time;
if (!scale_success) {
// Fallback: use full resolution frame
output_frame = std::move(full_resolution_frame);
OutputDebugStringA("[AdaptiveAV1Decoder] Scaling failed, using full resolution\n");
}
} else {
// ULTRA quality: use full resolution frame directly
output_frame = std::move(full_resolution_frame);
}
// Step 3: Update performance metrics and potentially adjust quality
if (m_adaptiveEnabled) {
UpdatePerformanceMetrics(decode_time, 0.0); // Render time updated separately
AnalyzePerformanceAndAdjust();
}
return true;
}
void AdaptiveAV1Decoder::SetQualityLevel(QualityLevel level) {
if (level == m_currentQuality) return;
m_targetQuality = level;
CalculateScaledDimensions(level, m_targetScaledWidth, m_targetScaledHeight);
m_currentQuality = level; // dav1d doesn't need decoder reconfiguration
OutputDebugStringA(("[AdaptiveAV1Decoder] Quality changed to " +
QualityLevelToString(level) + "\n").c_str());
}
void AdaptiveAV1Decoder::UpdatePerformanceMetrics(double decode_time, double render_time) {
std::lock_guard<std::mutex> lock(m_metricsMutex);
// Update moving averages
if (decode_time > 0) {
UpdateMovingAverage(m_recentDecodeTimes, decode_time);
m_metrics.avg_decode_time_ms = CalculateMovingAverage(m_recentDecodeTimes);
}
if (render_time > 0) {
UpdateMovingAverage(m_recentRenderTimes, render_time);
m_metrics.avg_render_time_ms = CalculateMovingAverage(m_recentRenderTimes);
}
m_metrics.last_update = std::chrono::steady_clock::now();
}
void AdaptiveAV1Decoder::AnalyzePerformanceAndAdjust() {
std::lock_guard<std::mutex> lock(m_metricsMutex);
double totalFrameTime = m_metrics.avg_decode_time_ms + m_metrics.avg_render_time_ms;
if (ShouldAdjustQuality(m_metrics.avg_decode_time_ms, m_metrics.avg_render_time_ms)) {
QualityLevel optimalQuality = DetermineOptimalQuality(totalFrameTime);
if (optimalQuality != m_currentQuality) {
m_stableFrameCount = 0; // Reset stability counter
SetQualityLevel(optimalQuality);
} else {
m_stableFrameCount++;
}
} else {
m_stableFrameCount++;
}
}
bool AdaptiveAV1Decoder::ShouldAdjustQuality(double avgDecodeTime, double avgRenderTime) {
double totalTime = avgDecodeTime + avgRenderTime;
double targetTime = m_config.target_frame_time_ms;
// Require minimum frames for stability
if (m_stableFrameCount < m_config.stable_frames_required) {
return false;
}
// Check if we're outside acceptable performance range
bool shouldScaleDown = totalTime > (targetTime * m_config.quality_down_threshold);
bool shouldScaleUp = totalTime < (targetTime * m_config.quality_up_threshold) &&
m_currentQuality > QualityLevel::ULTRA;
return shouldScaleDown || shouldScaleUp;
}
QualityLevel AdaptiveAV1Decoder::DetermineOptimalQuality(double totalFrameTime) {
double targetTime = m_config.target_frame_time_ms;
double ratio = totalFrameTime / targetTime;
// Determine quality based on performance ratio
if (ratio > 2.0) return QualityLevel::MINIMUM; // Extremely slow
if (ratio > 1.5) return QualityLevel::LOW; // Very slow
if (ratio > 1.2) return QualityLevel::MEDIUM; // Slow
if (ratio > 1.0) return QualityLevel::HIGH; // Slightly slow
return QualityLevel::ULTRA; // Fast enough for full quality
}
bool AdaptiveAV1Decoder::ScaleDecodedFrame(const VideoFrame& input_frame, VideoFrame& output_frame) {
// Ensure we have target dimensions calculated
if (m_targetScaledWidth == 0 || m_targetScaledHeight == 0) {
return false;
}
// Create output frame with scaled dimensions
output_frame.width = m_targetScaledWidth;
output_frame.height = m_targetScaledHeight;
output_frame.color_space = input_frame.color_space;
output_frame.timestamp_seconds = input_frame.timestamp_seconds;
// Allocate scaled frame buffer
if (!output_frame.AllocateYUV420P(m_targetScaledWidth, m_targetScaledHeight)) {
return false;
}
// Perform CPU-based YUV420P scaling
return ScaleYUV420P_CPU(input_frame, output_frame);
}
void AdaptiveAV1Decoder::CalculateScaledDimensions(QualityLevel quality, uint32_t& width, uint32_t& height) {
double scaleFactor = GetQualityScaleFactor(quality);
width = static_cast<uint32_t>(m_originalWidth * scaleFactor);
height = static_cast<uint32_t>(m_originalHeight * scaleFactor);
// Ensure dimensions are even (required for YUV420P)
width = (width + 1) & ~1;
height = (height + 1) & ~1;
// Ensure minimum dimensions
width = std::max(width, 64u);
height = std::max(height, 64u);
}
bool AdaptiveAV1Decoder::EnsureScalingBuffer(size_t required_size) {
if (m_scalingBufferSize < required_size) {
m_scalingBuffer = std::make_unique<uint8_t[]>(required_size);
m_scalingBufferSize = required_size;
}
return m_scalingBuffer != nullptr;
}
bool AdaptiveAV1Decoder::ScaleYUV420P_CPU(const VideoFrame& input, VideoFrame& output) {
// Scale Y plane
if (!ScaleYUVPlane_CPU(
input.y_plane.get(), input.y_stride, input.width, input.height,
output.y_plane.get(), output.y_stride, output.width, output.height)) {
return false;
}
// Scale U plane (half resolution)
uint32_t input_uv_width = input.width / 2;
uint32_t input_uv_height = input.height / 2;
uint32_t output_uv_width = output.width / 2;
uint32_t output_uv_height = output.height / 2;
if (!ScaleYUVPlane_CPU(
input.u_plane.get(), input.u_stride, input_uv_width, input_uv_height,
output.u_plane.get(), output.u_stride, output_uv_width, output_uv_height)) {
return false;
}
// Scale V plane (half resolution)
if (!ScaleYUVPlane_CPU(
input.v_plane.get(), input.v_stride, input_uv_width, input_uv_height,
output.v_plane.get(), output.v_stride, output_uv_width, output_uv_height)) {
return false;
}
return true;
}
bool AdaptiveAV1Decoder::ScaleYUVPlane_CPU(const uint8_t* src_plane, int src_stride, int src_width, int src_height,
uint8_t* dst_plane, int dst_stride, int dst_width, int dst_height) {
// Simple bilinear scaling implementation
for (int dst_y = 0; dst_y < dst_height; dst_y++) {
for (int dst_x = 0; dst_x < dst_width; dst_x++) {
// Calculate source coordinates
float src_x_f = (static_cast<float>(dst_x) * src_width) / dst_width;
float src_y_f = (static_cast<float>(dst_y) * src_height) / dst_height;
int src_x = static_cast<int>(src_x_f);
int src_y = static_cast<int>(src_y_f);
// Bounds checking
src_x = std::min(src_x, src_width - 1);
src_y = std::min(src_y, src_height - 1);
// Simple nearest neighbor for now (can be upgraded to bilinear later)
uint8_t pixel_value = src_plane[src_y * src_stride + src_x];
dst_plane[dst_y * dst_stride + dst_x] = pixel_value;
}
}
return true;
}
void AdaptiveAV1Decoder::UpdateMovingAverage(std::queue<double>& queue, double newValue, size_t maxSize) {
queue.push(newValue);
while (queue.size() > maxSize) {
queue.pop();
}
}
double AdaptiveAV1Decoder::CalculateMovingAverage(const std::queue<double>& queue) const {
if (queue.empty()) return 0.0;
double sum = 0.0;
std::queue<double> temp = queue;
while (!temp.empty()) {
sum += temp.front();
temp.pop();
}
return sum / queue.size();
}
double AdaptiveAV1Decoder::GetQualityScaleFactor(QualityLevel level) {
switch (level) {
case QualityLevel::ULTRA: return 1.0; // 100% resolution
case QualityLevel::HIGH: return 0.75; // 75% resolution
case QualityLevel::MEDIUM: return 0.5; // 50% resolution
case QualityLevel::LOW: return 0.35; // 35% resolution
case QualityLevel::MINIMUM: return 0.25; // 25% resolution
default: return 1.0;
}
}
std::string AdaptiveAV1Decoder::QualityLevelToString(QualityLevel level) {
switch (level) {
case QualityLevel::ULTRA: return "ULTRA";
case QualityLevel::HIGH: return "HIGH";
case QualityLevel::MEDIUM: return "MEDIUM";
case QualityLevel::LOW: return "LOW";
case QualityLevel::MINIMUM: return "MINIMUM";
default: return "UNKNOWN";
}
}
PerformanceMetrics AdaptiveAV1Decoder::GetPerformanceMetrics() const {
std::lock_guard<std::mutex> lock(m_metricsMutex);
return m_metrics;
}
void AdaptiveAV1Decoder::SetTargetFrameRate(double fps) {
m_config.target_frame_time_ms = 1000.0 / fps;
m_config.critical_frame_time_ms = 1000.0 / (fps * 0.6); // 60% of target FPS
}
void AdaptiveAV1Decoder::ForceQualityAdjustment() {
m_stableFrameCount = m_config.stable_frames_required; // Force next analysis
}
} // namespace Vav2Player

View File

@@ -1,96 +0,0 @@
#pragma once
#include "AV1Decoder.h"
#include "AdaptiveNVDECDecoder.h" // Include full definitions of shared types
#include <queue>
#include <mutex>
#include <atomic>
namespace Vav2Player {
// Enhanced AV1 decoder with adaptive quality adjustment using post-decode scaling
class AdaptiveAV1Decoder : public AV1Decoder {
public:
AdaptiveAV1Decoder();
~AdaptiveAV1Decoder() override;
// Enhanced initialization with adaptive features
bool Initialize(const VideoMetadata& metadata) override;
bool Initialize(const VideoMetadata& metadata, const AdaptiveConfig& config);
// Override decode with adaptive logic and post-decode scaling
bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) override;
bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override;
// Adaptive quality control
void SetQualityLevel(QualityLevel level);
QualityLevel GetCurrentQualityLevel() const { return m_currentQuality; }
// Performance monitoring
PerformanceMetrics GetPerformanceMetrics() const;
void UpdatePerformanceMetrics(double decode_time, double render_time);
// Manual override controls
void EnableAdaptiveMode(bool enable) { m_adaptiveEnabled = enable; }
void SetTargetFrameRate(double fps);
void ForceQualityAdjustment(); // Immediate adjustment trigger
// Configuration management
void UpdateConfig(const AdaptiveConfig& config) { m_config = config; }
AdaptiveConfig GetConfig() const { return m_config; }
// Override codec name to indicate adaptive capability
std::string GetCodecName() const override { return "AV1 (dav1d adaptive)"; }
private:
// Adaptive control state
QualityLevel m_currentQuality = QualityLevel::ULTRA;
QualityLevel m_targetQuality = QualityLevel::ULTRA;
AdaptiveConfig m_config;
// Performance monitoring
mutable std::mutex m_metricsMutex;
PerformanceMetrics m_metrics;
std::queue<double> m_recentDecodeTimes;
std::queue<double> m_recentRenderTimes;
// Adaptive decision making
std::atomic<bool> m_adaptiveEnabled{true};
uint32_t m_stableFrameCount = 0;
// Original video properties for scaling calculations
uint32_t m_originalWidth = 0;
uint32_t m_originalHeight = 0;
// Current scaled properties (for post-decode scaling)
uint32_t m_targetScaledWidth = 0;
uint32_t m_targetScaledHeight = 0;
// Post-decode scaling buffer
std::unique_ptr<uint8_t[]> m_scalingBuffer;
size_t m_scalingBufferSize = 0;
// Adaptive logic methods
void AnalyzePerformanceAndAdjust();
bool ShouldAdjustQuality(double avgDecodeTime, double avgRenderTime);
QualityLevel DetermineOptimalQuality(double totalFrameTime);
// Post-decode scaling methods (dav1d specific approach)
bool ScaleDecodedFrame(const VideoFrame& input_frame, VideoFrame& output_frame);
void CalculateScaledDimensions(QualityLevel quality, uint32_t& width, uint32_t& height);
bool EnsureScalingBuffer(size_t required_size);
// CPU-based scaling implementation
bool ScaleYUV420P_CPU(const VideoFrame& input, VideoFrame& output);
bool ScaleYUVPlane_CPU(const uint8_t* src_plane, int src_stride, int src_width, int src_height,
uint8_t* dst_plane, int dst_stride, int dst_width, int dst_height);
// Performance calculation helpers
void UpdateMovingAverage(std::queue<double>& queue, double newValue, size_t maxSize = 30);
double CalculateMovingAverage(const std::queue<double>& queue) const;
// Quality level utilities
static double GetQualityScaleFactor(QualityLevel level);
static std::string QualityLevelToString(QualityLevel level);
};
} // namespace Vav2Player

View File

@@ -1,159 +0,0 @@
// Example usage and integration guide for AdaptiveNVDECDecoder
#include "pch.h"
#include "AdaptiveNVDECDecoder.h"
namespace Vav2Player {
// Example: Basic adaptive decoding setup
class AdaptiveVideoPlayer {
public:
bool InitializeAdaptiveDecoding(const VideoMetadata& metadata, double targetFPS = 30.0) {
// Create adaptive configuration based on target performance
AdaptiveConfig config = AdaptiveUtils::CreateConfigForTarget(targetFPS);
// Customize based on system capabilities
if (IsLowEndSystem()) {
config = AdaptiveUtils::GetConservativeConfig();
} else if (IsHighEndSystem()) {
config = AdaptiveUtils::GetQualityConfig();
}
// Initialize adaptive decoder
m_adaptiveDecoder = std::make_unique<AdaptiveNVDECDecoder>();
bool result = m_adaptiveDecoder->Initialize(metadata, config);
if (result) {
OutputDebugStringA("[AdaptiveVideoPlayer] Adaptive decoding initialized successfully\n");
// Start performance monitoring thread
StartPerformanceMonitoring();
}
return result;
}
void ProcessFrameWithAdaptiveControl(const VideoPacket& packet) {
auto render_start = std::chrono::high_resolution_clock::now();
// Decode frame with adaptive quality
VideoFrame frame;
bool decode_success = m_adaptiveDecoder->DecodeFrame(packet, frame);
if (decode_success) {
// Render frame (this timing is important for adaptive control)
RenderFrame(frame);
auto render_end = std::chrono::high_resolution_clock::now();
double render_time = std::chrono::duration<double, std::milli>(render_end - render_start).count();
// Update render timing for adaptive algorithm
m_adaptiveDecoder->UpdatePerformanceMetrics(0.0, render_time);
// Log quality changes
LogQualityStatus();
}
}
// Example: Manual quality override for user preferences
void SetUserQualityPreference(const std::string& qualityName) {
QualityLevel level = QualityLevel::ULTRA;
if (qualityName == "high") level = QualityLevel::HIGH;
else if (qualityName == "medium") level = QualityLevel::MEDIUM;
else if (qualityName == "low") level = QualityLevel::LOW;
else if (qualityName == "minimum") level = QualityLevel::MINIMUM;
// Temporarily disable adaptive mode for manual control
m_adaptiveDecoder->EnableAdaptiveMode(false);
m_adaptiveDecoder->SetQualityLevel(level);
OutputDebugStringA(("[AdaptiveVideoPlayer] Manual quality set to " + qualityName + "\n").c_str());
}
// Example: Re-enable adaptive mode after manual override
void EnableAutoQuality() {
m_adaptiveDecoder->EnableAdaptiveMode(true);
m_adaptiveDecoder->ForceQualityAdjustment(); // Immediate analysis
OutputDebugStringA("[AdaptiveVideoPlayer] Auto quality control re-enabled\n");
}
// Example: Performance-based target adjustment
void AdjustForSystemLoad() {
PerformanceMetrics metrics = m_adaptiveDecoder->GetPerformanceMetrics();
// If system is heavily loaded, reduce target FPS
if (metrics.cpu_usage_percent > 85.0 || metrics.gpu_usage_percent > 90.0) {
m_adaptiveDecoder->SetTargetFrameRate(24.0); // Reduce to 24 FPS
OutputDebugStringA("[AdaptiveVideoPlayer] Reduced target FPS due to high system load\n");
}
// If system has headroom, try to increase FPS
else if (metrics.cpu_usage_percent < 50.0 && metrics.gpu_usage_percent < 60.0) {
m_adaptiveDecoder->SetTargetFrameRate(60.0); // Increase to 60 FPS
OutputDebugStringA("[AdaptiveVideoPlayer] Increased target FPS due to available resources\n");
}
}
private:
std::unique_ptr<AdaptiveNVDECDecoder> m_adaptiveDecoder;
QualityLevel m_lastLoggedQuality = QualityLevel::ULTRA;
void StartPerformanceMonitoring() {
// Background thread for periodic performance adjustment
std::thread([this]() {
while (m_adaptiveDecoder && m_adaptiveDecoder->IsInitialized()) {
std::this_thread::sleep_for(std::chrono::seconds(2));
AdjustForSystemLoad();
}
}).detach();
}
void LogQualityStatus() {
QualityLevel currentQuality = m_adaptiveDecoder->GetCurrentQualityLevel();
if (currentQuality != m_lastLoggedQuality) {
PerformanceMetrics metrics = m_adaptiveDecoder->GetPerformanceMetrics();
OutputDebugStringA(("[AdaptiveVideoPlayer] Quality: " +
AdaptiveNVDECDecoder::QualityLevelToString(currentQuality) +
", Decode: " + std::to_string(metrics.avg_decode_time_ms) + "ms" +
", Render: " + std::to_string(metrics.avg_render_time_ms) + "ms\n").c_str());
m_lastLoggedQuality = currentQuality;
}
}
bool IsLowEndSystem() {
// Example: Detect system capabilities
// Check GPU memory, compute capability, etc.
return false; // Placeholder
}
bool IsHighEndSystem() {
// Example: High-end system with plenty of resources
return true; // Placeholder
}
void RenderFrame(const VideoFrame& frame) {
// Placeholder for actual rendering
std::this_thread::sleep_for(std::chrono::milliseconds(5)); // Simulate render time
}
};
// Example: Integration with VideoDecoderFactory
class AdaptiveDecoderFactory {
public:
static std::unique_ptr<IVideoDecoder> CreateAdaptiveDecoder(
VideoCodecType codecType,
const AdaptiveConfig& config = AdaptiveUtils::GetBalancedConfig()) {
if (codecType == VideoCodecType::AV1) {
auto decoder = std::make_unique<AdaptiveNVDECDecoder>();
// Configuration will be applied during Initialize()
return std::move(decoder);
}
// Fallback to regular decoders for non-adaptive cases
return VideoDecoderFactory::CreateDecoder(codecType, VideoDecoderFactory::DecoderType::AUTO);
}
};
} // namespace Vav2Player

View File

@@ -1,296 +0,0 @@
#include "pch.h"
#include "AdaptiveNVDECDecoder.h"
#include <algorithm>
#include <cmath>
namespace Vav2Player {
AdaptiveNVDECDecoder::AdaptiveNVDECDecoder() : NVDECAV1Decoder() {
// Initialize with balanced configuration
m_config = AdaptiveUtils::GetBalancedConfig();
m_metrics.last_update = std::chrono::steady_clock::now();
}
AdaptiveNVDECDecoder::~AdaptiveNVDECDecoder() = default;
bool AdaptiveNVDECDecoder::Initialize(const VideoMetadata& metadata, const AdaptiveConfig& config) {
m_config = config;
return Initialize(metadata);
}
bool AdaptiveNVDECDecoder::Initialize(const VideoMetadata& metadata) {
// Store original dimensions for scaling calculations
m_originalWidth = metadata.width;
m_originalHeight = metadata.height;
m_currentScaledWidth = metadata.width;
m_currentScaledHeight = metadata.height;
// Initialize with maximum resolution support for dynamic scaling
VideoMetadata adaptiveMetadata = metadata;
// Ensure maxWidth/maxHeight can support dynamic resolution changes
m_maxWidth = std::max(metadata.width, 4096u);
m_maxHeight = std::max(metadata.height, 4096u);
bool result = NVDECAV1Decoder::Initialize(adaptiveMetadata);
if (result) {
OutputDebugStringA("[AdaptiveNVDECDecoder] Initialized with adaptive quality control\n");
}
return result;
}
bool AdaptiveNVDECDecoder::DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) {
auto decode_start = std::chrono::high_resolution_clock::now();
// Check if we need to reconfigure the decoder
if (m_needsReconfiguration) {
if (ReconfigureDecoder(m_currentScaledWidth, m_currentScaledHeight)) {
m_needsReconfiguration = false;
OutputDebugStringA(("[AdaptiveNVDECDecoder] Reconfigured to " +
std::to_string(m_currentScaledWidth) + "x" +
std::to_string(m_currentScaledHeight) + "\n").c_str());
}
}
// Perform the actual decode
bool decode_success = NVDECAV1Decoder::DecodeFrame(packet_data, packet_size, output_frame);
auto decode_end = std::chrono::high_resolution_clock::now();
double decode_time = std::chrono::duration<double, std::milli>(decode_end - decode_start).count();
// Update performance metrics and potentially adjust quality
if (m_adaptiveEnabled) {
UpdatePerformanceMetrics(decode_time, 0.0); // Render time updated separately
AnalyzePerformanceAndAdjust();
}
return decode_success;
}
void AdaptiveNVDECDecoder::SetQualityLevel(QualityLevel level) {
if (level == m_currentQuality) return;
m_targetQuality = level;
CalculateScaledDimensions(level, m_currentScaledWidth, m_currentScaledHeight);
m_needsReconfiguration = true;
OutputDebugStringA(("[AdaptiveNVDECDecoder] Quality changed to " +
QualityLevelToString(level) + "\n").c_str());
}
void AdaptiveNVDECDecoder::UpdatePerformanceMetrics(double decode_time, double render_time) {
std::lock_guard<std::mutex> lock(m_metricsMutex);
// Update moving averages
if (decode_time > 0) {
UpdateMovingAverage(m_recentDecodeTimes, decode_time);
m_metrics.avg_decode_time_ms = CalculateMovingAverage(m_recentDecodeTimes);
}
if (render_time > 0) {
UpdateMovingAverage(m_recentRenderTimes, render_time);
m_metrics.avg_render_time_ms = CalculateMovingAverage(m_recentRenderTimes);
}
m_metrics.last_update = std::chrono::steady_clock::now();
}
void AdaptiveNVDECDecoder::AnalyzePerformanceAndAdjust() {
std::lock_guard<std::mutex> lock(m_metricsMutex);
double totalFrameTime = m_metrics.avg_decode_time_ms + m_metrics.avg_render_time_ms;
if (ShouldAdjustQuality(m_metrics.avg_decode_time_ms, m_metrics.avg_render_time_ms)) {
QualityLevel optimalQuality = DetermineOptimalQuality(totalFrameTime);
if (optimalQuality != m_currentQuality) {
m_stableFrameCount = 0; // Reset stability counter
SetQualityLevel(optimalQuality);
} else {
m_stableFrameCount++;
}
} else {
m_stableFrameCount++;
}
}
bool AdaptiveNVDECDecoder::ShouldAdjustQuality(double avgDecodeTime, double avgRenderTime) {
double totalTime = avgDecodeTime + avgRenderTime;
double targetTime = m_config.target_frame_time_ms;
// Require minimum frames for stability
if (m_stableFrameCount < m_config.stable_frames_required) {
return false;
}
// Check if we're outside acceptable performance range
bool shouldScaleDown = totalTime > (targetTime * m_config.quality_down_threshold);
bool shouldScaleUp = totalTime < (targetTime * m_config.quality_up_threshold) &&
m_currentQuality > QualityLevel::ULTRA;
return shouldScaleDown || shouldScaleUp;
}
QualityLevel AdaptiveNVDECDecoder::DetermineOptimalQuality(double totalFrameTime) {
double targetTime = m_config.target_frame_time_ms;
double ratio = totalFrameTime / targetTime;
// Determine quality based on performance ratio
if (ratio > 2.0) return QualityLevel::MINIMUM; // Extremely slow
if (ratio > 1.5) return QualityLevel::LOW; // Very slow
if (ratio > 1.2) return QualityLevel::MEDIUM; // Slow
if (ratio > 1.0) return QualityLevel::HIGH; // Slightly slow
return QualityLevel::ULTRA; // Fast enough for full quality
}
bool AdaptiveNVDECDecoder::ReconfigureDecoder(uint32_t newWidth, uint32_t newHeight) {
if (!m_decoder) return false;
// Use cuvidReconfigureDecoder for dynamic resolution changes
CUVIDRECONFIGUREDECODERINFO reconfigInfo = {};
reconfigInfo.ulWidth = newWidth;
reconfigInfo.ulHeight = newHeight;
reconfigInfo.ulTargetWidth = newWidth;
reconfigInfo.ulTargetHeight = newHeight;
reconfigInfo.ulNumDecodeSurfaces = m_createInfo.ulNumDecodeSurfaces;
CUresult result = cuvidReconfigureDecoder(m_decoder, &reconfigInfo);
if (result == CUDA_SUCCESS) {
// Update our create info for future reference
m_createInfo.ulWidth = newWidth;
m_createInfo.ulHeight = newHeight;
m_createInfo.ulTargetWidth = newWidth;
m_createInfo.ulTargetHeight = newHeight;
m_width = newWidth;
m_height = newHeight;
m_currentQuality = m_targetQuality;
return true;
} else {
LogCUDAError(result, "cuvidReconfigureDecoder");
return false;
}
}
void AdaptiveNVDECDecoder::CalculateScaledDimensions(QualityLevel quality, uint32_t& width, uint32_t& height) {
double scaleFactor = GetQualityScaleFactor(quality);
width = static_cast<uint32_t>(m_originalWidth * scaleFactor);
height = static_cast<uint32_t>(m_originalHeight * scaleFactor);
// Ensure dimensions are even (required for most video formats)
width = (width + 1) & ~1;
height = (height + 1) & ~1;
// Ensure minimum dimensions
width = std::max(width, 64u);
height = std::max(height, 64u);
}
void AdaptiveNVDECDecoder::UpdateMovingAverage(std::queue<double>& queue, double newValue, size_t maxSize) {
queue.push(newValue);
while (queue.size() > maxSize) {
queue.pop();
}
}
double AdaptiveNVDECDecoder::CalculateMovingAverage(const std::queue<double>& queue) const {
if (queue.empty()) return 0.0;
double sum = 0.0;
std::queue<double> temp = queue;
while (!temp.empty()) {
sum += temp.front();
temp.pop();
}
return sum / queue.size();
}
double AdaptiveNVDECDecoder::GetQualityScaleFactor(QualityLevel level) {
switch (level) {
case QualityLevel::ULTRA: return 1.0; // 100% resolution
case QualityLevel::HIGH: return 0.75; // 75% resolution
case QualityLevel::MEDIUM: return 0.5; // 50% resolution
case QualityLevel::LOW: return 0.35; // 35% resolution
case QualityLevel::MINIMUM: return 0.25; // 25% resolution
default: return 1.0;
}
}
std::string AdaptiveNVDECDecoder::QualityLevelToString(QualityLevel level) {
switch (level) {
case QualityLevel::ULTRA: return "ULTRA";
case QualityLevel::HIGH: return "HIGH";
case QualityLevel::MEDIUM: return "MEDIUM";
case QualityLevel::LOW: return "LOW";
case QualityLevel::MINIMUM: return "MINIMUM";
default: return "UNKNOWN";
}
}
PerformanceMetrics AdaptiveNVDECDecoder::GetPerformanceMetrics() const {
std::lock_guard<std::mutex> lock(m_metricsMutex);
return m_metrics;
}
void AdaptiveNVDECDecoder::SetTargetFrameRate(double fps) {
m_config.target_frame_time_ms = 1000.0 / fps;
m_config.critical_frame_time_ms = 1000.0 / (fps * 0.6); // 60% of target FPS
}
void AdaptiveNVDECDecoder::ForceQualityAdjustment() {
m_stableFrameCount = m_config.stable_frames_required; // Force next analysis
}
// AdaptiveUtils implementation
namespace AdaptiveUtils {
AdaptiveConfig CreateConfigForTarget(double targetFPS) {
AdaptiveConfig config;
config.target_frame_time_ms = 1000.0 / targetFPS;
config.critical_frame_time_ms = 1000.0 / (targetFPS * 0.6);
return config;
}
AdaptiveConfig GetConservativeConfig() {
AdaptiveConfig config;
config.quality_down_threshold = 1.1; // Aggressive scaling down
config.quality_up_threshold = 0.7; // Conservative scaling up
config.stable_frames_required = 15; // Faster adjustments
return config;
}
AdaptiveConfig GetBalancedConfig() {
AdaptiveConfig config;
// Use default values - already balanced
return config;
}
AdaptiveConfig GetQualityConfig() {
AdaptiveConfig config;
config.quality_down_threshold = 1.4; // Tolerate more lag before reducing quality
config.quality_up_threshold = 0.9; // Quick to restore quality
config.stable_frames_required = 45; // Slower adjustments for stability
return config;
}
double EstimateOptimalFrameRate(const PerformanceMetrics& metrics) {
double totalFrameTime = metrics.avg_decode_time_ms + metrics.avg_render_time_ms;
if (totalFrameTime <= 0) return 30.0; // Default
return 1000.0 / totalFrameTime; // Convert ms to FPS
}
QualityLevel RecommendQualityForTargetFPS(double targetFPS, double currentFPS) {
double ratio = currentFPS / targetFPS;
if (ratio >= 0.95) return QualityLevel::ULTRA;
if (ratio >= 0.80) return QualityLevel::HIGH;
if (ratio >= 0.60) return QualityLevel::MEDIUM;
if (ratio >= 0.40) return QualityLevel::LOW;
return QualityLevel::MINIMUM;
}
} // namespace AdaptiveUtils
} // namespace Vav2Player

View File

@@ -1,143 +0,0 @@
#pragma once
#include "NVDECAV1Decoder.h"
#include <queue>
#include <mutex>
#include <atomic>
namespace Vav2Player {
// Performance monitoring data
struct PerformanceMetrics {
double avg_decode_time_ms = 0.0;
double avg_render_time_ms = 0.0;
double cpu_usage_percent = 0.0;
double gpu_usage_percent = 0.0;
uint64_t dropped_frames = 0;
std::chrono::steady_clock::time_point last_update;
};
// Quality levels for adaptive adjustment
enum class QualityLevel {
ULTRA = 0, // Original resolution, full quality
HIGH = 1, // 75% resolution, high quality
MEDIUM = 2, // 50% resolution, medium quality
LOW = 3, // 25% resolution, low quality
MINIMUM = 4 // 12.5% resolution, minimal quality
};
// Adaptive decoder configuration
struct AdaptiveConfig {
// Performance thresholds (milliseconds)
double target_frame_time_ms = 33.33; // 30 FPS target
double critical_frame_time_ms = 50.0; // 20 FPS critical
// Quality adjustment thresholds
double quality_up_threshold = 0.8; // Scale up when < 80% of target time
double quality_down_threshold = 1.2; // Scale down when > 120% of target time
// Hysteresis to prevent oscillation
uint32_t stable_frames_required = 30; // Frames to wait before adjustment
// Memory constraints
uint32_t max_decode_surfaces = 16;
uint32_t min_decode_surfaces = 4;
// Enable/disable features
bool enable_dynamic_resolution = true;
bool enable_dynamic_surfaces = true;
bool enable_skip_non_reference = true;
};
// Enhanced NVDEC decoder with adaptive quality adjustment
class AdaptiveNVDECDecoder : public NVDECAV1Decoder {
public:
AdaptiveNVDECDecoder();
~AdaptiveNVDECDecoder() override;
// Enhanced initialization with adaptive features
bool Initialize(const VideoMetadata& metadata) override;
bool Initialize(const VideoMetadata& metadata, const AdaptiveConfig& config);
// Override decode with adaptive logic
bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override;
// Adaptive quality control
void SetQualityLevel(QualityLevel level);
QualityLevel GetCurrentQualityLevel() const { return m_currentQuality; }
// Performance monitoring
PerformanceMetrics GetPerformanceMetrics() const;
void UpdatePerformanceMetrics(double decode_time, double render_time);
// Manual override controls
void EnableAdaptiveMode(bool enable) { m_adaptiveEnabled = enable; }
void SetTargetFrameRate(double fps);
void ForceQualityAdjustment(); // Immediate adjustment trigger
// Configuration management
void UpdateConfig(const AdaptiveConfig& config) { m_config = config; }
AdaptiveConfig GetConfig() const { return m_config; }
private:
// Adaptive control state
QualityLevel m_currentQuality = QualityLevel::ULTRA;
QualityLevel m_targetQuality = QualityLevel::ULTRA;
AdaptiveConfig m_config;
// Performance monitoring
mutable std::mutex m_metricsMutex;
PerformanceMetrics m_metrics;
std::queue<double> m_recentDecodeTimes;
std::queue<double> m_recentRenderTimes;
// Adaptive decision making
std::atomic<bool> m_adaptiveEnabled{true};
uint32_t m_stableFrameCount = 0;
bool m_needsReconfiguration = false;
// Original video properties for scaling calculations
uint32_t m_originalWidth = 0;
uint32_t m_originalHeight = 0;
// Current scaled properties
uint32_t m_currentScaledWidth = 0;
uint32_t m_currentScaledHeight = 0;
// Adaptive logic methods
void AnalyzePerformanceAndAdjust();
bool ShouldAdjustQuality(double avgDecodeTime, double avgRenderTime);
QualityLevel DetermineOptimalQuality(double totalFrameTime);
// NVDEC reconfiguration methods
bool ReconfigureDecoder(uint32_t newWidth, uint32_t newHeight);
bool UpdateDecodeSurfaces(uint32_t newCount);
void CalculateScaledDimensions(QualityLevel quality, uint32_t& width, uint32_t& height);
// Performance calculation helpers
void UpdateMovingAverage(std::queue<double>& queue, double newValue, size_t maxSize = 30);
double CalculateMovingAverage(const std::queue<double>& queue) const;
// Quality level utilities
static double GetQualityScaleFactor(QualityLevel level);
static std::string QualityLevelToString(QualityLevel level);
};
// Utility functions for adaptive decoding
namespace AdaptiveUtils {
// Performance estimation
double EstimateOptimalFrameRate(const PerformanceMetrics& metrics);
QualityLevel RecommendQualityForTargetFPS(double targetFPS, double currentFPS);
// System resource monitoring
double GetCurrentCPUUsage();
double GetCurrentGPUUsage();
size_t GetAvailableGPUMemory();
// Configuration presets
AdaptiveConfig CreateConfigForTarget(double targetFPS);
AdaptiveConfig GetConservativeConfig(); // Aggressive quality reduction
AdaptiveConfig GetBalancedConfig(); // Balanced performance/quality
AdaptiveConfig GetQualityConfig(); // Prioritize quality over performance
}
} // namespace Vav2Player

View File

@@ -1,69 +0,0 @@
#pragma once
#include "../Common/VideoTypes.h"
#include <string>
namespace Vav2Player {
// 비디오 디코더 인터페이스
// 다양한 코덱(AV1, VP9, H.264 등)에 대한 공통 인터페이스 제공
class IVideoDecoder {
public:
virtual ~IVideoDecoder() = default;
// 초기화 및 해제
virtual bool Initialize(const VideoMetadata& metadata) = 0;
virtual void Cleanup() = 0;
virtual bool IsInitialized() const = 0;
// 디코딩 핵심 기능
virtual bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) = 0;
// 추가 디코딩 옵션 (일부 디코더에서 사용)
virtual bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) = 0;
// 디코더 상태 관리
virtual bool Reset() = 0; // 디코더 상태 초기화
virtual bool Flush() = 0; // 남은 프레임들 출력
// 디코더 정보
virtual std::string GetCodecName() const = 0;
virtual VideoCodecType GetCodecType() const = 0;
virtual std::string GetVersion() const = 0;
// 성능 및 통계 정보
struct DecoderStats {
uint64_t frames_decoded = 0;
uint64_t frames_dropped = 0;
uint64_t decode_errors = 0;
double avg_decode_time_ms = 0.0;
uint64_t bytes_processed = 0;
};
virtual DecoderStats GetStats() const = 0;
virtual void ResetStats() = 0;
// 디코더별 특화 설정 (옵션)
virtual bool SetOption(const std::string& key, const std::string& value) {
return false; // 기본 구현: 설정 미지원
}
virtual std::string GetOption(const std::string& key) const {
return ""; // 기본 구현: 설정 미지원
}
protected:
// 파생 클래스에서 통계 업데이트용
mutable DecoderStats m_stats{};
void UpdateDecodeTime(double decode_time_ms) {
m_stats.avg_decode_time_ms =
(m_stats.avg_decode_time_ms * m_stats.frames_decoded + decode_time_ms) /
(m_stats.frames_decoded + 1);
}
void IncrementFramesDecoded() { ++m_stats.frames_decoded; }
void IncrementFramesDropped() { ++m_stats.frames_dropped; }
void IncrementDecodeErrors() { ++m_stats.decode_errors; }
void AddBytesProcessed(size_t bytes) { m_stats.bytes_processed += bytes; }
};
} // namespace Vav2Player

View File

@@ -1,651 +0,0 @@
#include "pch.h"
#include "MediaFoundationAV1Decoder.h"
#include <sstream>
#include <iomanip>
#include <thread>
namespace Vav2Player {
MediaFoundationAV1Decoder::MediaFoundationAV1Decoder()
: m_initialized(false)
, m_hardwareAccelerated(false)
, m_hardwareType("Software")
, m_mftConfigured(false) {
LogInfo("MediaFoundationAV1Decoder created");
}
MediaFoundationAV1Decoder::~MediaFoundationAV1Decoder() {
Cleanup();
LogInfo("MediaFoundationAV1Decoder destroyed");
}
bool MediaFoundationAV1Decoder::Initialize(const VideoMetadata& metadata) {
LogInfo("Initializing MediaFoundationAV1Decoder...");
if (m_initialized) {
LogInfo("Decoder already initialized");
return true;
}
m_metadata = metadata;
if (!InitializeMediaFoundation()) {
LogError("Failed to initialize Media Foundation");
return false;
}
ComPtr<IMFTransform> testMFT;
if (!FindAV1DecoderMFT(&testMFT)) {
LogError("No AV1 decoder MFT found on this system");
Cleanup();
return false;
}
testMFT.Reset(); // Release the test MFT
if (m_mfSettings.enable_hardware_acceleration && !CreateD3D11Device()) {
LogError("Failed to create D3D11 device, falling back to software");
m_mfSettings.enable_hardware_acceleration = false;
}
if (m_mfSettings.enable_dxva && m_mfSettings.enable_hardware_acceleration) {
if (!SetupDXVAAcceleration()) {
LogError("Failed to setup DXVA acceleration");
m_mfSettings.enable_dxva = false;
}
}
if (!CreateSourceReader()) {
LogError("Failed to create AV1 decoder MFT");
Cleanup();
return false;
}
if (!ConfigureVideoDecoder()) {
LogError("Failed to configure video decoder");
Cleanup();
return false;
}
DetectHardwareAcceleration();
m_initialized = true;
ResetStats();
LogInfo("MediaFoundationAV1Decoder initialized successfully");
LogInfo("Hardware acceleration: " + std::string(m_hardwareAccelerated ? "Enabled" : "Disabled"));
if (m_hardwareAccelerated) {
LogInfo("Hardware type: " + m_hardwareType);
}
return true;
}
void MediaFoundationAV1Decoder::Cleanup() {
if (!m_initialized) {
return;
}
LogInfo("Cleaning up MediaFoundationAV1Decoder...");
{
std::lock_guard<std::mutex> lock(m_mftMutex);
m_mftConfigured = false;
}
CleanupMediaFoundation();
m_initialized = false;
m_hardwareAccelerated = false;
m_hardwareType = "Software";
LogInfo("MediaFoundationAV1Decoder cleanup completed");
}
bool MediaFoundationAV1Decoder::IsInitialized() const {
return m_initialized;
}
bool MediaFoundationAV1Decoder::DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) {
if (!input_packet.IsValid()) {
LogError("Invalid input packet");
return false;
}
return DecodeFrame(input_packet.data.get(), input_packet.size, output_frame);
}
bool MediaFoundationAV1Decoder::DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) {
if (!m_initialized) {
LogError("Decoder not initialized");
return false;
}
if (!packet_data || packet_size == 0) {
LogError("Invalid packet data");
return false;
}
try {
if (!m_mftConfigured) {
std::lock_guard<std::mutex> lock(m_mftMutex);
if (!m_mftConfigured && !ConfigureMFTMediaTypes()) {
LogError("Failed to configure MFT media types in DecodeFrame");
return false;
}
m_mftConfigured = true;
}
auto start_time = std::chrono::high_resolution_clock::now();
// Always feed input data to the MFT
if (!ProcessMFTInput(packet_data, packet_size)) {
return false; // Error is logged inside
}
// Try to get output, but "need more input" is normal for MediaFoundation
bool outputAvailable = ProcessMFTOutput(output_frame);
if (!outputAvailable) {
// Could be "need more input" (normal) or actual error
// For now, we'll assume it's normal and wait for more input
LogInfo("No output available yet - buffering input data");
return false; // No output frame available yet
}
// Update timing and statistics only when frame is successfully output
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
UpdateDecodingStats(duration.count() / 1000.0, packet_size);
output_frame.is_valid = true;
output_frame.timestamp_seconds = 0.0;
return true;
}
catch (const std::exception& e) {
LogError("Exception in DecodeFrame: " + std::string(e.what()));
return false;
}
}
bool MediaFoundationAV1Decoder::Reset() {
if (!m_initialized) {
return false;
}
LogInfo("Resetting MediaFoundationAV1Decoder...");
try {
if (m_decoderMFT) {
HRESULT hr = m_decoderMFT->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
if (FAILED(hr)) {
LogError("Failed to flush MFT", hr);
return false;
}
hr = m_decoderMFT->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
if (FAILED(hr)) {
LogError("Failed to send BEGIN_STREAMING message after flush", hr);
return false;
}
}
LogInfo("MediaFoundationAV1Decoder reset completed");
return true;
}
catch (const std::exception& e) {
LogError("Exception in Reset: " + std::string(e.what()));
return false;
}
}
bool MediaFoundationAV1Decoder::Flush() {
return Reset();
}
std::string MediaFoundationAV1Decoder::GetCodecName() const {
return "Media Foundation AV1 Decoder";
}
VideoCodecType MediaFoundationAV1Decoder::GetCodecType() const {
return VideoCodecType::AV1;
}
std::string MediaFoundationAV1Decoder::GetVersion() const {
return "1.0.0 (Media Foundation)";
}
IVideoDecoder::DecoderStats MediaFoundationAV1Decoder::GetStats() const {
std::lock_guard<std::mutex> lock(m_statsMutex);
return m_stats;
}
void MediaFoundationAV1Decoder::ResetStats() {
std::lock_guard<std::mutex> lock(m_statsMutex);
m_stats = DecoderStats{};
}
bool MediaFoundationAV1Decoder::SetOption(const std::string& key, const std::string& value) { return false; }
std::string MediaFoundationAV1Decoder::GetOption(const std::string& key) const { return ""; }
void MediaFoundationAV1Decoder::SetMFSettings(const MFSettings& settings) { m_mfSettings = settings; }
MediaFoundationAV1Decoder::MFSettings MediaFoundationAV1Decoder::GetMFSettings() const { return m_mfSettings; }
bool MediaFoundationAV1Decoder::IsHardwareAccelerated() const { return m_hardwareAccelerated; }
std::string MediaFoundationAV1Decoder::GetHardwareAccelerationType() const { return m_hardwareType; }
bool MediaFoundationAV1Decoder::InitializeMediaFoundation() {
HRESULT hr = MFStartup(MF_VERSION);
if (FAILED(hr)) {
LogError("MFStartup failed", hr);
return false;
}
LogInfo("Media Foundation initialized successfully");
return true;
}
void MediaFoundationAV1Decoder::CleanupMediaFoundation() {
m_decoderMFT.Reset();
m_deviceManager.Reset();
m_d3d11Context.Reset();
m_d3d11Device.Reset();
m_inputMediaType.Reset();
m_outputMediaType.Reset();
MFShutdown();
LogInfo("Media Foundation cleaned up");
}
bool MediaFoundationAV1Decoder::CreateD3D11Device() {
D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0 };
UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL featureLevel;
HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &m_d3d11Device, &featureLevel, &m_d3d11Context);
if (FAILED(hr)) {
LogError("D3D11CreateDevice failed", hr);
return false;
}
LogInfo("D3D11 device created successfully");
return true;
}
bool MediaFoundationAV1Decoder::SetupDXVAAcceleration() {
if (!m_d3d11Device) {
LogError("D3D11 device not available for DXVA");
return false;
}
UINT resetToken = 0;
HRESULT hr = MFCreateDXGIDeviceManager(&resetToken, &m_deviceManager);
if (FAILED(hr)) {
LogError("MFCreateDXGIDeviceManager failed", hr);
return false;
}
hr = m_deviceManager->ResetDevice(m_d3d11Device.Get(), resetToken);
if (FAILED(hr)) {
LogError("ResetDevice failed", hr);
return false;
}
LogInfo("DXVA acceleration setup successfully");
return true;
}
bool MediaFoundationAV1Decoder::CreateSourceReader() {
LogInfo("Creating AV1 decoder MFT directly...");
ComPtr<IMFTransform> decoder;
if (!FindAV1DecoderMFT(&decoder)) {
LogError("Failed to find AV1 decoder MFT");
return false;
}
m_decoderMFT = decoder;
if (m_deviceManager && m_mfSettings.enable_dxva) {
if (!SetupMFTForDXVA()) {
LogError("Failed to setup MFT for DXVA, continuing without hardware acceleration");
m_hardwareAccelerated = false;
}
}
LogInfo("AV1 decoder MFT created successfully");
return true;
}
bool MediaFoundationAV1Decoder::ConfigureVideoDecoder() {
if (!m_decoderMFT) {
LogError("Decoder MFT not available");
return false;
}
if (!ConfigureMFTMediaTypes()) {
LogError("Failed to configure MFT media types");
return false;
}
HRESULT hr = m_decoderMFT->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
if (FAILED(hr)) {
LogError("Failed to send BEGIN_STREAMING message to MFT", hr);
return false;
}
m_mftConfigured = true;
LogInfo("Video decoder configured successfully");
return true;
}
bool MediaFoundationAV1Decoder::ConvertMFSampleToVideoFrame(IMFSample* sample, VideoFrame& output_frame) {
if (!sample) return false;
ComPtr<IMFMediaBuffer> buffer;
HRESULT hr = sample->ConvertToContiguousBuffer(&buffer);
if (FAILED(hr)) return false;
if (!m_outputMediaType) return false;
UINT32 width = 0, height = 0;
hr = MFGetAttributeSize(m_outputMediaType.Get(), MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) return false;
GUID subtype = {};
hr = m_outputMediaType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) return false;
output_frame.width = width;
output_frame.height = height;
output_frame.color_space = GetColorSpaceFromMFFormat(subtype);
return CopyVideoFrameData(buffer.Get(), output_frame);
}
bool MediaFoundationAV1Decoder::CopyVideoFrameData(IMFMediaBuffer* buffer, VideoFrame& frame) {
BYTE* data = nullptr;
DWORD currentLength = 0;
HRESULT hr = buffer->Lock(&data, nullptr, &currentLength);
if (FAILED(hr)) return false;
bool success = false;
try {
if (frame.color_space == ColorSpace::YUV420P) {
size_t y_size = frame.width * frame.height;
size_t uv_size = y_size / 2;
frame.y_plane = std::make_unique<uint8_t[]>(y_size);
frame.y_stride = frame.width;
frame.y_size = static_cast<uint32_t>(y_size);
memcpy(frame.y_plane.get(), data, y_size);
frame.u_plane = std::make_unique<uint8_t[]>(uv_size / 2);
frame.v_plane = std::make_unique<uint8_t[]>(uv_size / 2);
frame.u_stride = frame.width / 2;
frame.v_stride = frame.width / 2;
frame.u_size = static_cast<uint32_t>(uv_size / 2);
frame.v_size = static_cast<uint32_t>(uv_size / 2);
const uint8_t* uv_data = data + y_size;
for (uint32_t i = 0; i < frame.u_size; i++) {
frame.u_plane[i] = uv_data[i * 2];
frame.v_plane[i] = uv_data[i * 2 + 1];
}
success = true;
}
}
catch (...) { success = false; }
buffer->Unlock();
return success;
}
ColorSpace MediaFoundationAV1Decoder::GetColorSpaceFromMFFormat(const GUID& subtype) {
if (subtype == MFVideoFormat_NV12 || subtype == MFVideoFormat_YV12) return ColorSpace::YUV420P;
if (subtype == MFVideoFormat_YUY2) return ColorSpace::YUV422P;
if (subtype == MFVideoFormat_AYUV) return ColorSpace::YUV444P;
return ColorSpace::YUV420P;
}
bool MediaFoundationAV1Decoder::DetectHardwareAcceleration() {
if (!m_decoderMFT) return false;
m_hardwareAccelerated = (m_d3d11Device != nullptr && m_deviceManager != nullptr);
if (m_hardwareAccelerated) {
m_hardwareType = GetGPUDescription();
}
return true;
}
std::string MediaFoundationAV1Decoder::GetGPUDescription() {
if (!m_d3d11Device) return "Software";
ComPtr<IDXGIDevice> dxgiDevice;
HRESULT hr = m_d3d11Device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(dxgiDevice.GetAddressOf()));
if (FAILED(hr)) return "Unknown Hardware";
ComPtr<IDXGIAdapter> adapter;
hr = dxgiDevice->GetAdapter(&adapter);
if (FAILED(hr)) return "Unknown Hardware";
return MFUtils::GetDXGIAdapterDescription(adapter.Get());
}
void MediaFoundationAV1Decoder::UpdateDecodingStats(double decode_time_ms, size_t input_bytes) {
std::lock_guard<std::mutex> lock(m_statsMutex);
m_stats.bytes_processed += input_bytes;
m_stats.frames_decoded++;
// Simple moving average filter
if (m_stats.avg_decode_time_ms == 0) {
m_stats.avg_decode_time_ms = decode_time_ms;
} else {
m_stats.avg_decode_time_ms = m_stats.avg_decode_time_ms * 0.95 + decode_time_ms * 0.05;
}
}
std::string MediaFoundationAV1Decoder::GetMFErrorString(HRESULT hr) {
switch (hr) {
case MF_E_INVALIDMEDIATYPE: return "Invalid media type";
case MF_E_INVALIDREQUEST: return "Invalid request";
case MF_E_INVALIDSTREAMNUMBER: return "Invalid stream number";
case MF_E_NOTACCEPTING: return "Not accepting data";
case MF_E_NOT_INITIALIZED: return "Not initialized";
case MF_E_UNSUPPORTED_REPRESENTATION: return "Unsupported representation";
default: {
std::ostringstream oss;
oss << "HRESULT 0x" << std::hex << hr;
return oss.str();
}
}
}
void MediaFoundationAV1Decoder::LogError(const std::string& message, HRESULT hr) {
std::string fullMessage = "[MediaFoundationAV1Decoder] ERROR: " + message;
if (hr != S_OK) {
fullMessage += " (" + GetMFErrorString(hr) + ")";
}
OutputDebugStringA((fullMessage + "\n").c_str());
}
void MediaFoundationAV1Decoder::LogInfo(const std::string& message) {
std::string fullMessage = "[MediaFoundationAV1Decoder] INFO: " + message;
OutputDebugStringA((fullMessage + "\n").c_str());
}
namespace MFUtils {
bool InitializeMediaFoundation() { return SUCCEEDED(MFStartup(MF_VERSION)); }
void ShutdownMediaFoundation() { MFShutdown(); }
std::string GetDXGIAdapterDescription(IDXGIAdapter* adapter) {
if (!adapter) return "Unknown Adapter";
DXGI_ADAPTER_DESC desc;
if (FAILED(adapter->GetDesc(&desc))) return "Unknown Adapter";
char buffer[256];
size_t convertedChars = 0;
wcstombs_s(&convertedChars, buffer, sizeof(buffer), desc.Description, _TRUNCATE);
return std::string(buffer);
}
}
bool MediaFoundationAV1Decoder::FindAV1DecoderMFT(IMFTransform** decoder) {
*decoder = nullptr;
MFT_REGISTER_TYPE_INFO inputType = { MFMediaType_Video, MFVideoFormat_AV1 };
IMFActivate** activateArray = nullptr;
UINT32 numActivate = 0;
HRESULT hr = MFTEnumEx(MFT_CATEGORY_VIDEO_DECODER, MFT_ENUM_FLAG_ALL, &inputType, nullptr, &activateArray, &numActivate);
if (FAILED(hr) || numActivate == 0) {
LogError("No AV1 decoder MFT found", hr);
return false;
}
hr = activateArray[0]->ActivateObject(IID_PPV_ARGS(decoder));
for (UINT32 i = 0; i < numActivate; i++) { activateArray[i]->Release(); }
CoTaskMemFree(activateArray);
if (FAILED(hr)) {
LogError("Failed to activate AV1 decoder MFT", hr);
return false;
}
LogInfo("AV1 decoder MFT found and activated");
return true;
}
bool MediaFoundationAV1Decoder::SetupMFTForDXVA() {
if (!m_decoderMFT || !m_deviceManager) {
LogError("SetupMFTForDXVA: MFT or Device Manager not available.");
return false;
}
HRESULT hr = m_decoderMFT->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, reinterpret_cast<ULONG_PTR>(m_deviceManager.Get()));
if (FAILED(hr)) {
LogError("Failed to set D3D manager on MFT", hr);
return false;
}
LogInfo("DXVA setup for MFT completed");
return true;
}
bool MediaFoundationAV1Decoder::ConfigureMFTMediaTypes() {
if (!m_decoderMFT) { return false; }
HRESULT hr;
hr = MFCreateMediaType(&m_inputMediaType);
if (FAILED(hr)) return false;
hr = m_inputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
if (FAILED(hr)) return false;
hr = m_inputMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_AV1);
if (FAILED(hr)) return false;
hr = m_decoderMFT->SetInputType(0, m_inputMediaType.Get(), 0);
if (FAILED(hr)) {
LogError("Failed to set input type on MFT", hr);
return false;
}
for (DWORD i = 0; ; i++) {
ComPtr<IMFMediaType> availableType;
hr = m_decoderMFT->GetOutputAvailableType(0, i, &availableType);
if (FAILED(hr)) {
if (hr == MF_E_NO_MORE_TYPES) { LogError("MFT does not support a suitable output type (NV12)."); }
break;
}
GUID subtype = {};
availableType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (subtype == MFVideoFormat_NV12) {
m_outputMediaType = availableType;
break;
}
}
if (!m_outputMediaType) {
LogError("Failed to find a suitable output media type.");
return false;
}
if (m_metadata.width > 0 && m_metadata.height > 0) {
hr = MFSetAttributeSize(m_outputMediaType.Get(), MF_MT_FRAME_SIZE, m_metadata.width, m_metadata.height);
if (FAILED(hr)) {
LogError("Failed to set frame size on output type", hr);
return false;
}
}
hr = m_decoderMFT->SetOutputType(0, m_outputMediaType.Get(), 0);
if (FAILED(hr)) {
LogError("Failed to set output type on MFT after negotiation", hr);
return false;
}
LogInfo("MFT media types configured successfully after negotiation");
return true;
}
bool MediaFoundationAV1Decoder::ProcessMFTInput(const uint8_t* packet_data, size_t packet_size) {
HRESULT hr;
ComPtr<IMFSample> inputSample;
ComPtr<IMFMediaBuffer> inputBuffer;
hr = MFCreateSample(&inputSample);
if (FAILED(hr)) return false;
hr = MFCreateMemoryBuffer(static_cast<DWORD>(packet_size), &inputBuffer);
if (FAILED(hr)) return false;
hr = inputSample->AddBuffer(inputBuffer.Get());
if (FAILED(hr)) return false;
BYTE* bufferData = nullptr;
hr = inputBuffer->Lock(&bufferData, nullptr, nullptr);
if (FAILED(hr)) return false;
memcpy(bufferData, packet_data, packet_size);
inputBuffer->Unlock();
hr = inputBuffer->SetCurrentLength(static_cast<DWORD>(packet_size));
if (FAILED(hr)) return false;
hr = inputSample->SetUINT32(MFSampleExtension_CleanPoint, TRUE);
if (FAILED(hr)) {
LogError("Failed to set CleanPoint attribute", hr);
return false;
}
hr = inputSample->SetSampleTime(0);
if (FAILED(hr)) {
LogError("Failed to set sample time", hr);
return false;
}
hr = m_decoderMFT->ProcessInput(0, inputSample.Get(), 0);
if (FAILED(hr)) {
LogError("ProcessInput failed", hr);
return false;
}
return true;
}
bool MediaFoundationAV1Decoder::ProcessMFTOutput(VideoFrame& output_frame) {
if (!m_decoderMFT) {
LogError("ProcessMFTOutput: m_decoderMFT is null");
return false;
}
// Check MFT output stream info
DWORD outputStreamId = 0;
MFT_OUTPUT_STREAM_INFO outputStreamInfo = {};
HRESULT hr = m_decoderMFT->GetOutputStreamInfo(outputStreamId, &outputStreamInfo);
if (FAILED(hr)) {
LogError("GetOutputStreamInfo failed", hr);
return false;
}
LogInfo("MFT Output Stream Info - Flags: " + std::to_string(outputStreamInfo.dwFlags) +
", Size: " + std::to_string(outputStreamInfo.cbSize) +
", Alignment: " + std::to_string(outputStreamInfo.cbAlignment));
MFT_OUTPUT_DATA_BUFFER outputDataBuffer = {};
outputDataBuffer.dwStreamID = 0;
outputDataBuffer.pEvents = nullptr;
// Check if we need to provide the output sample
if (outputStreamInfo.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) {
LogInfo("MFT provides output samples");
outputDataBuffer.pSample = nullptr;
} else {
LogInfo("Client must provide output sample");
// Create output sample
ComPtr<IMFSample> outputSample;
hr = MFCreateSample(&outputSample);
if (FAILED(hr)) {
LogError("Failed to create output sample", hr);
return false;
}
// Create output media buffer
UINT32 bufferSize = outputStreamInfo.cbSize > 0 ? outputStreamInfo.cbSize : (3840 * 2160 * 4);
ComPtr<IMFMediaBuffer> outputBuffer;
hr = MFCreateMemoryBuffer(bufferSize, &outputBuffer);
if (FAILED(hr)) {
LogError("Failed to create output buffer", hr);
return false;
}
hr = outputSample->AddBuffer(outputBuffer.Get());
if (FAILED(hr)) {
LogError("Failed to add buffer to sample", hr);
return false;
}
outputDataBuffer.pSample = outputSample.Get();
}
DWORD status = 0;
hr = m_decoderMFT->ProcessOutput(0, 1, &outputDataBuffer, &status);
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
LogInfo("ProcessOutput: Need more input (normal for MediaFoundation)");
return false; // Normal case - need more input packets
}
if (FAILED(hr)) {
LogError("ProcessOutput failed", hr);
return false;
}
if (!outputDataBuffer.pSample) {
LogError("ProcessOutput: No output sample returned");
return false;
}
bool result = ConvertMFSampleToVideoFrame(outputDataBuffer.pSample, output_frame);
// Clean up
if (outputDataBuffer.pEvents) {
outputDataBuffer.pEvents->Release();
}
return result;
}
} // namespace Vav2Player

View File

@@ -1,166 +0,0 @@
#pragma once
#include "IVideoDecoder.h"
// Simplified architecture - removed FramePool
#include <memory>
#include <chrono>
#include <mutex>
// Media Foundation headers
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mftransform.h>
#include <mfobjects.h>
#include <mferror.h>
#include <mfcaptureengine.h>
#include <dxgi.h>
#include <d3d11.h>
#include <wrl/client.h>
// Additional headers for Media Foundation Transform
#include <wmcodecdsp.h>
// AV1 format GUID declaration (supported in Windows 10 2004+)
// AV01: {31304156-0000-0010-8000-00AA00389B71}
extern const GUID MFVideoFormat_AV01;
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mf.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
using Microsoft::WRL::ComPtr;
namespace Vav2Player {
// Media Foundation-based AV1 hardware accelerated decoder
// Performs hardware accelerated decoding using Source Reader
class MediaFoundationAV1Decoder : public IVideoDecoder {
public:
MediaFoundationAV1Decoder();
~MediaFoundationAV1Decoder() override;
// Prevent copying
MediaFoundationAV1Decoder(const MediaFoundationAV1Decoder&) = delete;
MediaFoundationAV1Decoder& operator=(const MediaFoundationAV1Decoder&) = delete;
// IVideoDecoder interface implementation
bool Initialize(const VideoMetadata& metadata) override;
void Cleanup() override;
bool IsInitialized() const override;
bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) override;
bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override;
bool Reset() override;
bool Flush() override;
std::string GetCodecName() const override;
VideoCodecType GetCodecType() const override;
std::string GetVersion() const override;
DecoderStats GetStats() const override;
void ResetStats() override;
// Media Foundation specific options
bool SetOption(const std::string& key, const std::string& value) override;
std::string GetOption(const std::string& key) const override;
// Media Foundation exclusive methods
struct MFSettings {
bool enable_hardware_acceleration = true; // Enable hardware acceleration
bool enable_dxva = true; // Use DXVA 2.0/D3D11VA
bool enable_low_latency = true; // Low latency mode
uint32_t max_buffer_size = 16; // Maximum buffer size (number of frames)
};
void SetMFSettings(const MFSettings& settings);
MFSettings GetMFSettings() const;
// Check hardware acceleration status
bool IsHardwareAccelerated() const;
std::string GetHardwareAccelerationType() const;
private:
// Media Foundation related members
ComPtr<IMFTransform> m_decoderMFT;
ComPtr<IMFDXGIDeviceManager> m_deviceManager;
ComPtr<ID3D11Device> m_d3d11Device;
ComPtr<ID3D11DeviceContext> m_d3d11Context;
ComPtr<IMFMediaType> m_inputMediaType;
ComPtr<IMFMediaType> m_outputMediaType;
// Settings and state
MFSettings m_mfSettings;
bool m_initialized;
bool m_hardwareAccelerated;
std::string m_hardwareType;
VideoMetadata m_metadata;
// MFT processing
std::mutex m_mftMutex;
bool m_mftConfigured = false;
// Members for performance measurement
std::chrono::high_resolution_clock::time_point m_decode_start_time;
mutable std::mutex m_statsMutex;
// Internal helper methods
bool InitializeMediaFoundation();
void CleanupMediaFoundation();
bool CreateD3D11Device();
bool SetupDXVAAcceleration();
bool CreateSourceReader();
bool ConfigureVideoDecoder();
// MFT related methods
bool FindAV1DecoderMFT(IMFTransform** decoder);
bool SetupMFTForDXVA();
bool ConfigureMFTMediaTypes();
bool ProcessMFTInput(const uint8_t* packet_data, size_t packet_size);
bool ProcessMFTOutput(VideoFrame& output_frame);
// Frame conversion
bool ConvertMFSampleToVideoFrame(IMFSample* sample, VideoFrame& output_frame);
bool CopyVideoFrameData(IMFMediaBuffer* buffer, VideoFrame& frame);
ColorSpace GetColorSpaceFromMFFormat(const GUID& subtype);
// Hardware acceleration detection
bool DetectHardwareAcceleration();
std::string GetGPUDescription();
// Statistics update
void UpdateDecodingStats(double decode_time_ms, size_t input_bytes);
// Error handling
std::string GetMFErrorString(HRESULT hr);
void LogError(const std::string& message, HRESULT hr = S_OK);
void LogInfo(const std::string& message);
};
// Media Foundation related utility functions
namespace MFUtils {
// Media Foundation initialization/cleanup
bool InitializeMediaFoundation();
void ShutdownMediaFoundation();
// GUID conversion utility
std::string GUIDToString(const GUID& guid);
bool StringToGUID(const std::string& str, GUID& guid);
// Media type utility
std::string GetMediaTypeDescription(IMFMediaType* mediaType);
bool IsAV1MediaType(IMFMediaType* mediaType);
// Hardware detection
std::vector<std::string> EnumerateVideoDecoders();
bool IsHardwareDecoderAvailable(const std::string& codecName);
// D3D11 utility
std::string GetD3D11FeatureLevel(ID3D11Device* device);
std::string GetDXGIAdapterDescription(IDXGIAdapter* adapter);
}
} // namespace Vav2Player

View File

@@ -1,366 +0,0 @@
#include "pch.h"
// Include NVDEC decoder header with TIMECODE protection
#include "NVDECAV1Decoder.h"
#include <iostream>
#include <cstring>
#include <algorithm>
namespace Vav2Player {
NVDECAV1Decoder::NVDECAV1Decoder()
: m_initialized(false) {
}
NVDECAV1Decoder::~NVDECAV1Decoder() {
Cleanup();
}
bool NVDECAV1Decoder::Initialize(const VideoMetadata& metadata) {
if (m_initialized) {
LogError("Decoder already initialized");
return false;
}
if (metadata.codec_type != VideoCodecType::AV1) {
LogError("Invalid codec type for NVDEC AV1 decoder");
return false;
}
// Check NVDEC availability
if (!IsNVDECAvailable()) {
LogError("NVDEC not available on this system");
return false;
}
// Initialize CUDA context
if (!InitializeCUDA()) {
LogError("Failed to initialize CUDA");
return false;
}
// Store video properties
m_width = metadata.width;
m_height = metadata.height;
m_maxWidth = std::max(m_width, 4096u);
m_maxHeight = std::max(m_height, 4096u);
// Create decoder
if (!CreateDecoder()) {
LogError("Failed to create NVDEC decoder");
Cleanup();
return false;
}
// Create parser
if (!CreateParser()) {
LogError("Failed to create NVDEC parser");
Cleanup();
return false;
}
m_initialized = true;
std::cout << "[NVDECAV1Decoder] Initialized successfully" << std::endl;
std::cout << " Resolution: " << m_width << "x" << m_height << std::endl;
std::cout << " Max Resolution: " << m_maxWidth << "x" << m_maxHeight << std::endl;
return true;
}
void NVDECAV1Decoder::Cleanup() {
if (m_parser) {
cuvidDestroyVideoParser(m_parser);
m_parser = nullptr;
}
if (m_decoder) {
cuvidDestroyDecoder(m_decoder);
m_decoder = nullptr;
}
CleanupCUDA();
m_initialized = false;
}
bool NVDECAV1Decoder::IsInitialized() const {
return m_initialized;
}
bool NVDECAV1Decoder::DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) {
if (!input_packet.IsValid()) {
LogError("Invalid input packet");
return false;
}
return DecodeFrame(input_packet.data.get(), input_packet.size, output_frame);
}
bool NVDECAV1Decoder::DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) {
if (!m_initialized || !packet_data || packet_size == 0) {
LogError("Invalid parameters or decoder not initialized");
return false;
}
auto decode_start = std::chrono::high_resolution_clock::now();
// Prepare packet for parser
CUVIDSOURCEDATAPACKET packet = {};
packet.payload_size = static_cast<unsigned long>(packet_size);
packet.payload = packet_data;
packet.flags = 0;
// Parse packet
CUresult result = cuvidParseVideoData(m_parser, &packet);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuvidParseVideoData");
m_decodeErrors++;
return false;
}
// For GUI mode, we can copy pixel data to the VideoFrame
// TODO: Implement actual frame data copying when needed
output_frame.width = m_width;
output_frame.height = m_height;
output_frame.format = PixelFormat::YUV420P;
// Update statistics
auto decode_end = std::chrono::high_resolution_clock::now();
double decode_time = std::chrono::duration<double, std::milli>(decode_end - decode_start).count();
m_framesDecoded++;
m_bytesProcessed += packet_size;
// Update average decode time
m_avgDecodeTime = (m_avgDecodeTime * (m_framesDecoded - 1) + decode_time) / m_framesDecoded;
return true;
}
bool NVDECAV1Decoder::Reset() {
if (!m_initialized) {
return false;
}
// Reset statistics
ResetStats();
return true;
}
bool NVDECAV1Decoder::Flush() {
if (!m_initialized) {
return false;
}
// Send end-of-stream packet to flush any remaining frames
CUVIDSOURCEDATAPACKET packet = {};
packet.flags = CUVID_PKT_ENDOFSTREAM;
CUresult result = cuvidParseVideoData(m_parser, &packet);
return (result == CUDA_SUCCESS);
}
std::string NVDECAV1Decoder::GetVersion() const {
int driver_version = 0;
cuDriverGetVersion(&driver_version);
return "NVDEC AV1 (CUDA Driver: " + std::to_string(driver_version) + ")";
}
bool NVDECAV1Decoder::IsNVDECAvailable() const {
// Check if CUDA driver is available
if (cuInit(0) != CUDA_SUCCESS) {
return false;
}
// Check device count
int device_count = 0;
if (cuDeviceGetCount(&device_count) != CUDA_SUCCESS || device_count == 0) {
return false;
}
// Check decode capabilities for AV1
CUdevice device;
if (cuDeviceGet(&device, 0) != CUDA_SUCCESS) {
return false;
}
CUVIDDECODECAPS decode_caps = {};
decode_caps.eCodecType = cudaVideoCodec_AV1;
decode_caps.eChromaFormat = cudaVideoChromaFormat_420;
decode_caps.nBitDepthMinus8 = 0;
if (cuvidGetDecoderCaps(&decode_caps) != CUDA_SUCCESS) {
return false;
}
return decode_caps.bIsSupported != 0;
}
bool NVDECAV1Decoder::InitializeCUDA() {
// Initialize CUDA driver
CUresult result = cuInit(0);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuInit");
return false;
}
// Get device
CUdevice device;
result = cuDeviceGet(&device, 0);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuDeviceGet");
return false;
}
// Create context - use correct API signature for CUDA 13.0
CUctxCreateParams createParams = {};
createParams.execAffinityParams = nullptr;
result = cuCtxCreate_v4(&m_cuContext, &createParams, 0, device);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuCtxCreate");
return false;
}
// Create stream
result = cuStreamCreate(&m_stream, CU_STREAM_DEFAULT);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuStreamCreate");
return false;
}
return CheckCUDACapability();
}
bool NVDECAV1Decoder::CheckCUDACapability() {
// Get device properties
int major, minor;
CUresult result = cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, 0);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuDeviceGetAttribute");
return false;
}
result = cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, 0);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuDeviceGetAttribute");
return false;
}
std::cout << "[NVDECAV1Decoder] CUDA Compute Capability: " << major << "." << minor << std::endl;
// NVDEC requires compute capability 3.0 or higher
return (major >= 3);
}
bool NVDECAV1Decoder::CreateDecoder() {
memset(&m_createInfo, 0, sizeof(m_createInfo));
m_createInfo.CodecType = cudaVideoCodec_AV1;
m_createInfo.ChromaFormat = cudaVideoChromaFormat_420;
m_createInfo.OutputFormat = cudaVideoSurfaceFormat_NV12;
m_createInfo.bitDepthMinus8 = 0;
m_createInfo.DeinterlaceMode = cudaVideoDeinterlaceMode_Weave;
m_createInfo.ulNumOutputSurfaces = 8;
m_createInfo.ulCreationFlags = cudaVideoCreate_PreferCUVID;
m_createInfo.ulNumDecodeSurfaces = 8;
m_createInfo.vidLock = nullptr;
m_createInfo.ulWidth = m_width;
m_createInfo.ulHeight = m_height;
m_createInfo.ulMaxWidth = m_maxWidth;
m_createInfo.ulMaxHeight = m_maxHeight;
m_createInfo.ulTargetWidth = m_width;
m_createInfo.ulTargetHeight = m_height;
CUresult result = cuvidCreateDecoder(&m_decoder, &m_createInfo);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuvidCreateDecoder");
return false;
}
return true;
}
bool NVDECAV1Decoder::CreateParser() {
memset(&m_parserParams, 0, sizeof(m_parserParams));
m_parserParams.CodecType = cudaVideoCodec_AV1;
m_parserParams.ulMaxNumDecodeSurfaces = 8;
m_parserParams.ulClockRate = 0; // Use default
m_parserParams.ulErrorThreshold = 100;
m_parserParams.pUserData = this;
m_parserParams.pfnSequenceCallback = HandleVideoSequence;
m_parserParams.pfnDecodePicture = HandlePictureDecode;
m_parserParams.pfnDisplayPicture = HandlePictureDisplay;
CUresult result = cuvidCreateVideoParser(&m_parser, &m_parserParams);
if (result != CUDA_SUCCESS) {
LogCUDAError(result, "cuvidCreateVideoParser");
return false;
}
return true;
}
void NVDECAV1Decoder::CleanupCUDA() {
if (m_stream) {
cuStreamDestroy(m_stream);
m_stream = nullptr;
}
if (m_cuContext) {
cuCtxDestroy(m_cuContext);
m_cuContext = nullptr;
}
}
// NVDEC Callbacks
int CUDAAPI NVDECAV1Decoder::HandleVideoSequence(void* user_data, CUVIDEOFORMAT* format) {
auto* decoder = static_cast<NVDECAV1Decoder*>(user_data);
if (!decoder || !format) {
return 0;
}
std::cout << "[NVDECAV1Decoder] Sequence: " << format->coded_width << "x" << format->coded_height
<< " ChromaFormat:" << format->chroma_format << " BitDepth:" << format->bit_depth_luma_minus8 + 8 << std::endl;
return 1; // Success
}
int CUDAAPI NVDECAV1Decoder::HandlePictureDecode(void* user_data, CUVIDPICPARAMS* pic_params) {
auto* decoder = static_cast<NVDECAV1Decoder*>(user_data);
if (!decoder || !pic_params) {
return 0;
}
CUresult result = cuvidDecodePicture(decoder->m_decoder, pic_params);
if (result != CUDA_SUCCESS) {
decoder->LogCUDAError(result, "cuvidDecodePicture");
return 0;
}
return 1; // Success
}
int CUDAAPI NVDECAV1Decoder::HandlePictureDisplay(void* user_data, CUVIDPARSERDISPINFO* disp_info) {
auto* decoder = static_cast<NVDECAV1Decoder*>(user_data);
if (!decoder || !disp_info) {
return 0;
}
// For GUI mode, we can implement actual frame data copying here
return 1;
}
void NVDECAV1Decoder::LogError(const std::string& message) const {
std::cerr << "[NVDECAV1Decoder] ERROR: " << message << std::endl;
}
void NVDECAV1Decoder::LogCUDAError(CUresult result, const std::string& operation) const {
const char* error_string = nullptr;
cuGetErrorString(result, &error_string);
std::cerr << "[NVDECAV1Decoder] CUDA ERROR in " << operation << ": "
<< (error_string ? error_string : "Unknown error")
<< " (code: " << result << ")" << std::endl;
}
} // namespace Vav2Player

View File

@@ -1,110 +0,0 @@
#pragma once
#include "IVideoDecoder.h"
#include <memory>
#include <chrono>
// Prevent TIMECODE conflicts by defining it before Windows headers
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
// Prevent specific Windows header conflicts
#define TIMECODE TIMECODE_WIN32
#include <cuda.h>
#include <nvcuvid.h>
#include <cuviddec.h>
#undef TIMECODE
namespace Vav2Player {
// NVIDIA NVDEC-based AV1 decoder for hardware acceleration
class NVDECAV1Decoder : public IVideoDecoder {
public:
NVDECAV1Decoder();
~NVDECAV1Decoder() override;
// Prevent copying
NVDECAV1Decoder(const NVDECAV1Decoder&) = delete;
NVDECAV1Decoder& operator=(const NVDECAV1Decoder&) = delete;
// IVideoDecoder interface implementation
bool Initialize(const VideoMetadata& metadata) override;
void Cleanup() override;
bool IsInitialized() const override;
bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) override;
bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override;
bool Reset() override;
bool Flush() override;
// IVideoDecoder interface - additional methods
std::string GetCodecName() const override { return "AV1 (NVDEC)"; }
VideoCodecType GetCodecType() const override { return VideoCodecType::AV1; }
std::string GetVersion() const override;
DecoderStats GetStats() const override {
DecoderStats stats;
stats.frames_decoded = m_framesDecoded;
stats.decode_errors = m_decodeErrors;
stats.avg_decode_time_ms = m_avgDecodeTime;
stats.bytes_processed = m_bytesProcessed;
return stats;
}
void ResetStats() override {
m_framesDecoded = 0;
m_decodeErrors = 0;
m_avgDecodeTime = 0.0;
m_bytesProcessed = 0;
}
// NVDEC-specific methods
bool IsNVDECAvailable() const;
bool InitializeCUDA();
protected:
// Protected members for inheritance (AdaptiveNVDECDecoder)
CUvideodecoder m_decoder = nullptr;
CUVIDDECODECREATEINFO m_createInfo = {};
uint32_t m_width = 0;
uint32_t m_height = 0;
uint32_t m_maxWidth = 4096;
uint32_t m_maxHeight = 4096;
// Protected helper methods
void LogCUDAError(CUresult result, const std::string& operation) const;
private:
// CUDA and NVDEC objects
CUcontext m_cuContext = nullptr;
CUvideoparser m_parser = nullptr;
CUstream m_stream = nullptr;
// Decoder configuration
CUVIDPARSERPARAMS m_parserParams = {};
// Statistics
uint64_t m_framesDecoded = 0;
uint64_t m_decodeErrors = 0;
double m_avgDecodeTime = 0.0;
uint64_t m_bytesProcessed = 0;
// State
bool m_initialized = false;
// Helper methods
bool CheckCUDACapability();
bool CreateDecoder();
bool CreateParser();
void CleanupCUDA();
// NVDEC callbacks
static int CUDAAPI HandleVideoSequence(void* user_data, CUVIDEOFORMAT* format);
static int CUDAAPI HandlePictureDecode(void* user_data, CUVIDPICPARAMS* pic_params);
static int CUDAAPI HandlePictureDisplay(void* user_data, CUVIDPARSERDISPINFO* disp_info);
// Error handling
void LogError(const std::string& message) const;
};
} // namespace Vav2Player

View File

@@ -1,397 +0,0 @@
#include "pch.h"
#include "VideoDecoderFactory.h"
#include "AV1Decoder.h"
#include "AdaptiveAV1Decoder.h"
#include "MediaFoundationAV1Decoder.h"
// #include "VP9Decoder.h" // TODO: activate when VP9 implemented
#include <mfapi.h>
// Include NVDEC header (TIMECODE conflicts handled in NVDECAV1Decoder.h)
#include "NVDECAV1Decoder.h"
#include "AdaptiveNVDECDecoder.h"
namespace Vav2Player {
// Static member initialization
bool VideoDecoderFactory::s_av1_available = false;
bool VideoDecoderFactory::s_vp9_available = false;
bool VideoDecoderFactory::s_media_foundation_available = false;
bool VideoDecoderFactory::s_nvdec_available = false;
bool VideoDecoderFactory::s_factory_initialized = false;
std::unique_ptr<IVideoDecoder> VideoDecoderFactory::CreateDecoder(VideoCodecType codec_type, DecoderType decoder_type) {
if (!s_factory_initialized) {
InitializeFactory();
}
switch (codec_type) {
case VideoCodecType::AV1:
return CreateAV1Decoder(decoder_type);
case VideoCodecType::VP9:
// TODO: activate when VP9 implemented
// if (s_vp9_available) {
// return std::make_unique<VP9Decoder>();
// }
break;
default:
break;
}
return nullptr;
}
std::unique_ptr<IVideoDecoder> VideoDecoderFactory::CreateAV1Decoder(DecoderType decoder_type) {
switch (decoder_type) {
case DecoderType::ADAPTIVE_NVDEC:
if (s_nvdec_available) {
OutputDebugStringA("[VideoDecoderFactory] Creating Adaptive NVDEC AV1 decoder\n");
return std::make_unique<AdaptiveNVDECDecoder>();
}
OutputDebugStringA("[VideoDecoderFactory] NVDEC not available, falling back to regular NVDEC\n");
[[fallthrough]];
case DecoderType::NVDEC:
if (s_nvdec_available) {
OutputDebugStringA("[VideoDecoderFactory] Creating NVDEC AV1 decoder\n");
return std::make_unique<NVDECAV1Decoder>();
}
OutputDebugStringA("[VideoDecoderFactory] NVDEC not available, falling back to Adaptive dav1d\n");
[[fallthrough]];
case DecoderType::ADAPTIVE_DAV1D:
if (s_av1_available) {
OutputDebugStringA("[VideoDecoderFactory] Creating Adaptive dav1d AV1 decoder\n");
return std::make_unique<AdaptiveAV1Decoder>();
}
OutputDebugStringA("[VideoDecoderFactory] dav1d not available, falling back to regular dav1d\n");
[[fallthrough]];
case DecoderType::DAV1D:
if (s_av1_available) {
OutputDebugStringA("[VideoDecoderFactory] Creating dav1d AV1 decoder\n");
return std::make_unique<AV1Decoder>();
}
OutputDebugStringA("[VideoDecoderFactory] dav1d not available, falling back to MediaFoundation\n");
[[fallthrough]];
case DecoderType::MEDIA_FOUNDATION:
if (s_media_foundation_available) {
OutputDebugStringA("[VideoDecoderFactory] Creating MediaFoundation AV1 decoder\n");
return std::make_unique<MediaFoundationAV1Decoder>();
}
break;
case DecoderType::AUTO:
// Try ADAPTIVE_NVDEC first (best user experience), then ADAPTIVE_DAV1D, then NVDEC, then dav1d, finally MediaFoundation
if (s_nvdec_available) {
OutputDebugStringA("[VideoDecoderFactory] Auto mode: trying Adaptive NVDEC AV1 decoder first\n");
auto decoder = std::make_unique<AdaptiveNVDECDecoder>();
if (decoder) {
return decoder;
}
}
if (s_av1_available) {
OutputDebugStringA("[VideoDecoderFactory] Auto mode: trying Adaptive dav1d AV1 decoder\n");
auto decoder = std::make_unique<AdaptiveAV1Decoder>();
if (decoder) {
return decoder;
}
}
if (s_nvdec_available) {
OutputDebugStringA("[VideoDecoderFactory] Auto mode: trying regular NVDEC AV1 decoder\n");
auto decoder = std::make_unique<NVDECAV1Decoder>();
if (decoder) {
return decoder;
}
}
if (s_av1_available) {
OutputDebugStringA("[VideoDecoderFactory] Auto mode: trying regular dav1d AV1 decoder\n");
auto decoder = std::make_unique<AV1Decoder>();
if (decoder) {
return decoder;
}
}
// Fallback to MediaFoundation as last resort
if (s_media_foundation_available) {
OutputDebugStringA("[VideoDecoderFactory] Auto mode: falling back to MediaFoundation AV1 decoder\n");
return std::make_unique<MediaFoundationAV1Decoder>();
}
break;
}
return nullptr;
}
std::unique_ptr<IVideoDecoder> VideoDecoderFactory::CreateDecoderFromCodecId(const std::string& codec_id, DecoderType decoder_type) {
VideoCodecType codec_type = DetectCodecTypeFromId(codec_id);
return CreateDecoder(codec_type, decoder_type);
}
VideoCodecType VideoDecoderFactory::DetectCodecTypeFromId(const std::string& codec_id) {
if (codec_id == DecoderUtils::CodecIds::AV1) return VideoCodecType::AV1;
if (codec_id == DecoderUtils::CodecIds::VP9) return VideoCodecType::VP9;
if (codec_id == DecoderUtils::CodecIds::VP8) return VideoCodecType::VP8;
if (codec_id == DecoderUtils::CodecIds::H264) return VideoCodecType::H264;
if (codec_id == DecoderUtils::CodecIds::H265) return VideoCodecType::H265;
return VideoCodecType::AV1; // Default value
}
std::vector<VideoDecoderFactory::DecoderInfo> VideoDecoderFactory::GetSupportedDecoders() {
if (!s_factory_initialized) {
InitializeFactory();
}
std::vector<DecoderInfo> decoders;
// AV1 dav1d decoder
decoders.push_back({
VideoCodecType::AV1,
DecoderType::DAV1D,
"AV1 (dav1d)",
"AV1 video decoder using dav1d library",
s_av1_available
});
// AV1 MediaFoundation decoder
decoders.push_back({
VideoCodecType::AV1,
DecoderType::MEDIA_FOUNDATION,
"AV1 (MediaFoundation)",
"AV1 decoder using Windows Media Foundation",
s_media_foundation_available
});
// AV1 NVDEC decoder
decoders.push_back({
VideoCodecType::AV1,
DecoderType::NVDEC,
"AV1 (NVDEC)",
"AV1 decoder using NVIDIA NVDEC hardware acceleration",
s_nvdec_available
});
decoders.push_back({
VideoCodecType::VP9,
DecoderType::DAV1D, // TODO: VP9 needs separate decoder type
"VP9",
"VP9 video decoder (TODO: not implemented yet)",
s_vp9_available
});
return decoders;
}
bool VideoDecoderFactory::IsCodecSupported(VideoCodecType codec_type) {
if (!s_factory_initialized) {
InitializeFactory();
}
switch (codec_type) {
case VideoCodecType::AV1: return s_av1_available;
case VideoCodecType::VP9: return s_vp9_available;
default: return false;
}
}
bool VideoDecoderFactory::IsCodecSupported(const std::string& codec_id) {
VideoCodecType codec_type = DetectCodecTypeFromId(codec_id);
return IsCodecSupported(codec_type);
}
void VideoDecoderFactory::InitializeFactory() {
if (s_factory_initialized) return;
OutputDebugStringA("[VideoDecoderFactory] Initializing decoder factory...\n");
// Check availability of each decoder
s_av1_available = CheckAV1DecoderAvailability();
s_vp9_available = CheckVP9DecoderAvailability();
s_media_foundation_available = CheckMediaFoundationAvailability();
s_nvdec_available = CheckNVDECAvailability();
OutputDebugStringA(("[VideoDecoderFactory] AV1 (dav1d): " + std::string(s_av1_available ? "Available" : "Not available") + "\n").c_str());
OutputDebugStringA(("[VideoDecoderFactory] VP9: " + std::string(s_vp9_available ? "Available" : "Not available") + "\n").c_str());
OutputDebugStringA(("[VideoDecoderFactory] Media Foundation: " + std::string(s_media_foundation_available ? "Available" : "Not available") + "\n").c_str());
OutputDebugStringA(("[VideoDecoderFactory] NVDEC: " + std::string(s_nvdec_available ? "Available" : "Not available") + "\n").c_str());
s_factory_initialized = true;
}
void VideoDecoderFactory::CleanupFactory() {
s_factory_initialized = false;
s_av1_available = false;
s_vp9_available = false;
s_media_foundation_available = false;
s_nvdec_available = false;
}
std::string VideoDecoderFactory::GetDecoderVersion(VideoCodecType codec_type) {
switch (codec_type) {
case VideoCodecType::AV1:
return "dav1d 1.0+"; // TODO: get actual version information
case VideoCodecType::VP9:
return "Not implemented"; // TODO: when VP9 is implemented
default:
return "Unknown";
}
}
std::string VideoDecoderFactory::GetDecoderDescription(VideoCodecType codec_type) {
switch (codec_type) {
case VideoCodecType::AV1:
return "High-performance AV1 decoder";
case VideoCodecType::VP9:
return "VP9 decoder (TODO)";
default:
return "Unknown decoder";
}
}
bool VideoDecoderFactory::CheckAV1DecoderAvailability() {
// TODO: Actually check dav1d library loading
// Currently assumes always available
return true;
}
bool VideoDecoderFactory::CheckVP9DecoderAvailability() {
// TODO: activate after VP9 decoder implementation
return false;
}
bool VideoDecoderFactory::CheckMediaFoundationAvailability() {
try {
HRESULT hr = MFStartup(MF_VERSION);
if (FAILED(hr)) {
OutputDebugStringA("[VideoDecoderFactory] Media Foundation startup failed\n");
return false;
}
MFT_REGISTER_TYPE_INFO inputType = { MFMediaType_Video, MFVideoFormat_AV1 };
IMFActivate** activateArray = nullptr;
UINT32 numActivate = 0;
hr = MFTEnumEx(
MFT_CATEGORY_VIDEO_DECODER,
MFT_ENUM_FLAG_ALL, // Search for software and hardware MFTs
&inputType,
nullptr,
&activateArray,
&numActivate
);
bool av1_mft_available = false;
if (SUCCEEDED(hr) && numActivate > 0) {
av1_mft_available = true;
for (UINT32 i = 0; i < numActivate; i++) {
LPWSTR friendlyName = nullptr;
UINT32 nameLength = 0;
if (SUCCEEDED(activateArray[i]->GetAllocatedString(MFT_FRIENDLY_NAME_Attribute, &friendlyName, &nameLength))) {
OutputDebugStringA("[VideoDecoderFactory] Found AV1 MFT: ");
OutputDebugStringW(friendlyName);
OutputDebugStringA("\n");
CoTaskMemFree(friendlyName);
}
activateArray[i]->Release();
}
CoTaskMemFree(activateArray);
}
MFShutdown();
if (av1_mft_available) {
OutputDebugStringA("[VideoDecoderFactory] Media Foundation AV1 support: AVAILABLE\n");
return true;
} else {
OutputDebugStringA("[VideoDecoderFactory] Media Foundation AV1 support: NOT AVAILABLE\n");
return false;
}
}
catch (const std::exception& e) {
OutputDebugStringA(("[VideoDecoderFactory] Media Foundation availability check exception: " + std::string(e.what()) + "\n").c_str());
return false;
}
catch (...) {
OutputDebugStringA("[VideoDecoderFactory] Media Foundation availability check: Unknown exception\n");
return false;
}
}
bool VideoDecoderFactory::CheckNVDECAvailability() {
try {
// Create temporary NVDEC decoder to test availability
auto nvdec_decoder = std::make_unique<NVDECAV1Decoder>();
bool available = nvdec_decoder->IsNVDECAvailable();
if (available) {
OutputDebugStringA("[VideoDecoderFactory] NVDEC AV1 support: AVAILABLE\n");
} else {
OutputDebugStringA("[VideoDecoderFactory] NVDEC AV1 support: NOT AVAILABLE (No NVIDIA GPU or driver)\n");
}
return available;
}
catch (const std::exception& e) {
OutputDebugStringA(("[VideoDecoderFactory] NVDEC availability check exception: " + std::string(e.what()) + "\n").c_str());
return false;
}
catch (...) {
OutputDebugStringA("[VideoDecoderFactory] NVDEC availability check: Unknown exception\n");
return false;
}
}
// DecoderUtils implementation
namespace DecoderUtils {
std::string GetFriendlyCodecName(const std::string& codec_id) {
if (codec_id == CodecIds::AV1) return "AV1";
if (codec_id == CodecIds::VP9) return "VP9";
if (codec_id == CodecIds::VP8) return "VP8";
if (codec_id == CodecIds::H264) return "H.264/AVC";
if (codec_id == CodecIds::H265) return "H.265/HEVC";
return "Unknown (" + codec_id + ")";
}
std::string GetFriendlyCodecName(VideoCodecType codec_type) {
switch (codec_type) {
case VideoCodecType::AV1: return "AV1";
case VideoCodecType::VP9: return "VP9";
case VideoCodecType::VP8: return "VP8";
case VideoCodecType::H264: return "H.264/AVC";
case VideoCodecType::H265: return "H.265/HEVC";
default: return "Unknown";
}
}
std::string CodecTypeToString(VideoCodecType codec_type) {
switch (codec_type) {
case VideoCodecType::AV1: return "AV1";
case VideoCodecType::VP9: return "VP9";
case VideoCodecType::VP8: return "VP8";
case VideoCodecType::H264: return "H264";
case VideoCodecType::H265: return "H265";
default: return "UNKNOWN";
}
}
VideoCodecType StringToCodecType(const std::string& codec_string) {
if (codec_string == "AV1") return VideoCodecType::AV1;
if (codec_string == "VP9") return VideoCodecType::VP9;
if (codec_string == "VP8") return VideoCodecType::VP8;
if (codec_string == "H264") return VideoCodecType::H264;
if (codec_string == "H265") return VideoCodecType::H265;
return VideoCodecType::AV1; // Default value
}
} // namespace DecoderUtils
} // namespace Vav2Player

View File

@@ -1,101 +0,0 @@
#pragma once
#include "IVideoDecoder.h"
#include <memory>
#include <string>
#include <vector>
#include <mutex>
namespace Vav2Player {
// Video decoder factory class
// Creates appropriate decoder instances based on codec type
class VideoDecoderFactory {
public:
// Decoder type enumeration
enum class DecoderType {
DAV1D, // dav1d library based decoder
ADAPTIVE_DAV1D, // Adaptive dav1d with dynamic quality control (post-decode scaling)
MEDIA_FOUNDATION, // Windows Media Foundation based decoder
NVDEC, // NVIDIA NVDEC hardware acceleration decoder
ADAPTIVE_NVDEC, // Adaptive NVDEC with dynamic quality control
AUTO // Auto selection (ADAPTIVE_NVDEC priority, ADAPTIVE_DAV1D, NVDEC, dav1d, finally MediaFoundation)
};
// Supported decoder information
struct DecoderInfo {
VideoCodecType codec_type;
DecoderType decoder_type;
std::string codec_name;
std::string description;
bool is_available; // Whether currently available (library load status, etc.)
};
// Decoder creation (based on codec type)
static std::unique_ptr<IVideoDecoder> CreateDecoder(VideoCodecType codec_type, DecoderType decoder_type = DecoderType::AUTO);
// Decoder creation (based on codec ID string - used in WebM)
static std::unique_ptr<IVideoDecoder> CreateDecoderFromCodecId(const std::string& codec_id, DecoderType decoder_type = DecoderType::AUTO);
// Convert codec ID string to VideoCodecType
static VideoCodecType DetectCodecTypeFromId(const std::string& codec_id);
// Return list of all supported decoders
static std::vector<DecoderInfo> GetSupportedDecoders();
// Check if specific codec is supported
static bool IsCodecSupported(VideoCodecType codec_type);
static bool IsCodecSupported(const std::string& codec_id);
// Check decoder availability (library load status, etc.)
static void InitializeFactory(); // Called at app startup
static void CleanupFactory(); // Called at app shutdown
// Additional information per decoder
static std::string GetDecoderVersion(VideoCodecType codec_type);
static std::string GetDecoderDescription(VideoCodecType codec_type);
private:
// Factory is used as a static class
VideoDecoderFactory() = delete;
~VideoDecoderFactory() = delete;
VideoDecoderFactory(const VideoDecoderFactory&) = delete;
VideoDecoderFactory& operator=(const VideoDecoderFactory&) = delete;
// Internal helper functions
static bool CheckAV1DecoderAvailability();
static bool CheckVP9DecoderAvailability(); // TODO: when VP9 is implemented
static bool CheckMediaFoundationAvailability();
static bool CheckNVDECAvailability();
static std::unique_ptr<IVideoDecoder> CreateAV1Decoder(DecoderType decoder_type);
// Decoder availability status cache
static bool s_av1_available;
static bool s_vp9_available; // TODO: when VP9 is implemented
static bool s_media_foundation_available;
static bool s_nvdec_available;
static bool s_factory_initialized;
};
// Convenience functions
namespace DecoderUtils {
// Convert WebM codec ID to human-readable name
std::string GetFriendlyCodecName(const std::string& codec_id);
std::string GetFriendlyCodecName(VideoCodecType codec_type);
// Convert codec type to string
std::string CodecTypeToString(VideoCodecType codec_type);
VideoCodecType StringToCodecType(const std::string& codec_string);
// Widely used WebM codec IDs
namespace CodecIds {
constexpr const char* AV1 = "V_AV01";
constexpr const char* VP9 = "V_VP9";
constexpr const char* VP8 = "V_VP8";
constexpr const char* H264 = "V_MPEG4/ISO/AVC";
constexpr const char* H265 = "V_MPEGH/ISO/HEVC";
}
}
} // namespace Vav2Player

View File

@@ -1,77 +0,0 @@
#pragma once
#include "../Common/VideoTypes.h"
#include <string>
#include <vector>
namespace Vav2Player {
// Forward declaration for VideoTrackInfo
struct VideoTrackInfo {
uint64_t track_number;
VideoCodecType codec_type;
std::string codec_id;
std::string codec_name;
uint32_t width;
uint32_t height;
double frame_rate;
uint64_t frame_count;
bool is_default;
bool is_enabled = true; // For mock compatibility
};
// Error codes for WebM file operations
enum class WebMErrorCode {
Success,
FileNotFound,
InvalidFormat,
UnsupportedCodec,
NoVideoTrack,
ReadError,
SeekError,
FileNotOpen,
InvalidTrack,
SeekFailed,
Unknown
};
// Interface for WebM/MKV file readers
// Provides common abstraction for parsing WebM containers and extracting AV1 video streams
class IWebMFileReader {
public:
virtual ~IWebMFileReader() = default;
// File operations
virtual bool OpenFile(const std::string& file_path) = 0;
virtual void CloseFile() = 0;
virtual bool IsFileOpen() const = 0;
// File and stream information
virtual const VideoMetadata& GetVideoMetadata() const = 0;
virtual std::string GetFilePath() const = 0;
// Video track management
virtual std::vector<VideoTrackInfo> GetVideoTracks() const = 0;
virtual bool SelectVideoTrack(uint64_t track_number) = 0;
virtual uint64_t GetSelectedTrackNumber() const = 0;
// Packet reading
virtual bool ReadNextPacket(VideoPacket& packet) = 0;
virtual bool SeekToFrame(uint64_t frame_index) = 0;
virtual bool SeekToTime(double timestamp_seconds) = 0;
// Position information
virtual uint64_t GetCurrentFrameIndex() const = 0;
virtual double GetCurrentTimestamp() const = 0;
virtual bool IsEndOfFile() const = 0;
// File navigation and statistics
virtual bool Reset() = 0;
virtual uint64_t GetTotalFrames() const = 0;
virtual double GetDuration() const = 0;
// Error handling
virtual WebMErrorCode GetLastError() const = 0;
virtual std::string GetLastErrorString() const = 0;
};
} // namespace Vav2Player

View File

@@ -1,871 +0,0 @@
#include "pch.h"
#include "WebMFileReader.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cassert>
namespace Vav2Player {
// libwebm IMkvReader implementation class
class WebMFileReader::MkvReader : public mkvparser::IMkvReader {
public:
MkvReader() : m_file(nullptr) {}
~MkvReader() { Close(); }
bool Open(const std::string& file_path) {
Close();
errno_t err = fopen_s(&m_file, file_path.c_str(), "rb");
if (err != 0 || !m_file) {
return false;
}
// Calculate file size
_fseeki64(m_file, 0, SEEK_END);
m_file_size = _ftelli64(m_file);
_fseeki64(m_file, 0, SEEK_SET);
return true;
}
void Close() {
if (m_file) {
fclose(m_file);
m_file = nullptr;
}
m_file_size = 0;
}
// IMkvReader interface implementation
int Read(long long pos, long len, unsigned char* buf) override {
std::lock_guard<std::mutex> lock(m_file_mutex);
if (!m_file || !buf || len < 0) return -1;
if (_fseeki64(m_file, pos, SEEK_SET) != 0) {
return -1;
}
const size_t bytes_read = fread(buf, 1, static_cast<size_t>(len), m_file);
// libwebm IMkvReader contract:
// - Return 0: success (read all requested bytes)
// - Return positive: underflow (insufficient bytes available)
// - Return negative: error
if (bytes_read == static_cast<size_t>(len)) {
return 0; // Success: read all requested bytes
} else if (bytes_read < static_cast<size_t>(len)) {
return static_cast<int>(len - bytes_read); // Underflow: return insufficient byte count
} else {
return -1; // Unexpected situation
}
}
int Length(long long* total, long long* available) override {
std::lock_guard<std::mutex> lock(m_file_mutex);
if (!m_file) return -1;
if (total) *total = m_file_size;
if (available) *available = m_file_size; // Entire file available
return 0;
}
private:
std::FILE* m_file;
long long m_file_size = 0;
mutable std::mutex m_file_mutex; // File access synchronization
};
// WebMFileReader internal state management
struct WebMFileReader::InternalState {
std::unique_ptr<MkvReader> reader;
std::unique_ptr<mkvparser::Segment> segment;
// Current state
std::string file_path;
VideoMetadata metadata;
std::vector<VideoTrackInfo> video_tracks;
uint64_t selected_track_number = 0;
// Current reading position
const mkvparser::Cluster* current_cluster = nullptr;
const mkvparser::BlockEntry* current_block_entry = nullptr;
uint64_t current_frame_index = 0;
double current_timestamp = 0.0;
bool end_of_file = false;
// Error handling
WebMErrorCode last_error = WebMErrorCode::Success;
std::string last_error_message;
InternalState() : reader(std::make_unique<MkvReader>()) {}
};
WebMFileReader::WebMFileReader() : m_state(std::make_unique<InternalState>()) {
}
WebMFileReader::~WebMFileReader() {
CloseFile();
}
bool WebMFileReader::OpenFile(const std::string& file_path) {
CloseFile();
if (file_path.empty()) {
SetLastError(WebMErrorCode::FileNotFound, "File path is empty");
return false;
}
// Open file
if (!m_state->reader->Open(file_path)) {
SetLastError(WebMErrorCode::FileNotFound, "Cannot open file: " + file_path);
return false;
}
m_state->file_path = file_path;
// Initialize libwebm parser
if (!InitializeParser()) {
CloseFile();
return false;
}
// Extract video tracks
if (!ExtractVideoTracks()) {
CloseFile();
SetLastError(WebMErrorCode::NoVideoTrack, "No supported video tracks found");
return false;
}
// Automatically select first supported video track
for (const auto& track : m_state->video_tracks) {
if (IsVideoCodecSupported(track.codec_id)) {
SelectVideoTrack(track.track_number);
break;
}
}
if (m_state->selected_track_number == 0) {
CloseFile();
SetLastError(WebMErrorCode::UnsupportedCodec, "No supported video codecs found");
return false;
}
// Extract metadata
if (!ExtractVideoMetadata()) {
CloseFile();
return false;
}
// Initialize reading position
Reset();
SetLastError(WebMErrorCode::Success);
return true;
}
void WebMFileReader::CloseFile() {
if (m_state) {
m_state->segment.reset();
m_state->reader->Close();
m_state->video_tracks.clear();
m_state->selected_track_number = 0;
m_state->current_cluster = nullptr;
m_state->current_block_entry = nullptr;
m_state->current_frame_index = 0;
m_state->current_timestamp = 0.0;
m_state->end_of_file = false;
m_state->file_path.clear();
}
}
bool WebMFileReader::IsFileOpen() const {
return m_state && m_state->segment && !m_state->file_path.empty();
}
const VideoMetadata& WebMFileReader::GetVideoMetadata() const {
return m_state->metadata;
}
std::string WebMFileReader::GetFilePath() const {
return m_state ? m_state->file_path : "";
}
std::vector<VideoTrackInfo> WebMFileReader::GetVideoTracks() const {
return m_state ? m_state->video_tracks : std::vector<VideoTrackInfo>();
}
bool WebMFileReader::SelectVideoTrack(uint64_t track_number) {
if (!IsFileOpen()) {
SetLastError(WebMErrorCode::ReadError, "File not open");
return false;
}
// Check track existence
auto it = std::find_if(m_state->video_tracks.begin(), m_state->video_tracks.end(),
[track_number](const VideoTrackInfo& info) {
return info.track_number == track_number;
});
if (it == m_state->video_tracks.end()) {
SetLastError(WebMErrorCode::ReadError, "Track not found: " + std::to_string(track_number));
return false;
}
m_state->selected_track_number = track_number;
Reset(); // Initialize reading position
SetLastError(WebMErrorCode::Success);
return true;
}
uint64_t WebMFileReader::GetSelectedTrackNumber() const {
return m_state ? m_state->selected_track_number : 0;
}
bool WebMFileReader::ReadNextPacket(VideoPacket& packet) {
std::lock_guard<std::mutex> lock(m_access_mutex);
// Enhanced null safety check inside mutex
if (!m_state) {
SetLastError(WebMErrorCode::ReadError, "WebMFileReader state is null");
return false;
}
if (!IsFileOpen() || m_state->end_of_file) {
return false;
}
// Fail if no track selected
if (m_state->selected_track_number == 0) {
SetLastError(WebMErrorCode::ReadError, "No video track selected");
return false;
}
// Find next block
if (!AdvanceToNextFrame()) {
m_state->end_of_file = true;
return false;
}
// Read packet from current block
if (!m_state->current_block_entry || !m_state->current_block_entry->GetBlock()) {
SetLastError(WebMErrorCode::ReadError, "Invalid block entry");
return false;
}
const mkvparser::Block* block = m_state->current_block_entry->GetBlock();
if (!ReadPacketFromBlock(block, packet)) {
SetLastError(WebMErrorCode::ReadError, "Failed to read packet from block");
return false;
}
// Update current state
m_state->current_frame_index++;
// Calculate timestamp
const mkvparser::SegmentInfo* info = m_state->segment->GetInfo();
if (info && m_state->current_cluster) {
long long cluster_time = m_state->current_cluster->GetTime();
long long block_time = block->GetTime(m_state->current_cluster);
long long timecode_scale = info->GetTimeCodeScale();
m_state->current_timestamp = WebMUtils::TimecodeToSeconds(
cluster_time + block_time, timecode_scale);
packet.timestamp_seconds = m_state->current_timestamp;
}
packet.frame_index = m_state->current_frame_index - 1;
packet.is_keyframe = block->IsKey();
SetLastError(WebMErrorCode::Success);
return true;
}
bool WebMFileReader::SeekToFrame(uint64_t frame_index) {
if (!IsFileOpen()) {
SetLastError(WebMErrorCode::SeekError, "File not open");
return false;
}
std::lock_guard<std::mutex> lock(m_access_mutex);
if (m_state->selected_track_number == 0) {
SetLastError(WebMErrorCode::SeekError, "No video track selected");
return false;
}
// Frame 0 is equivalent to reset
if (frame_index == 0) {
return Reset();
}
// Convert frame-based seeking to time-based seeking
// Calculate approximate time using frame rate
double estimated_time = 0.0;
if (m_state->metadata.frame_rate > 0) {
estimated_time = static_cast<double>(frame_index) / m_state->metadata.frame_rate;
}
// 시간 기반 탐색 후 정확한 프레임 찾기
if (!SeekToTime(estimated_time)) {
return false;
}
// 정확한 프레임 인덱스까지 순차 탐색
while (m_state->current_frame_index < frame_index && !m_state->end_of_file) {
if (!AdvanceToNextFrame()) {
m_state->end_of_file = true;
SetLastError(WebMErrorCode::SeekError, "Cannot reach target frame");
return false;
}
m_state->current_frame_index++;
}
SetLastError(WebMErrorCode::Success);
return m_state->current_frame_index == frame_index;
}
bool WebMFileReader::SeekToTime(double timestamp_seconds) {
if (!IsFileOpen()) {
SetLastError(WebMErrorCode::SeekError, "File not open");
return false;
}
std::lock_guard<std::mutex> lock(m_access_mutex);
if (m_state->selected_track_number == 0) {
SetLastError(WebMErrorCode::SeekError, "No video track selected");
return false;
}
if (timestamp_seconds < 0.0) {
SetLastError(WebMErrorCode::SeekError, "Invalid timestamp");
return false;
}
// 시간이 0이면 리셋
if (timestamp_seconds == 0.0) {
return Reset();
}
// 세그먼트 정보에서 타임코드 스케일 가져오기
const mkvparser::SegmentInfo* info = m_state->segment->GetInfo();
if (!info) {
SetLastError(WebMErrorCode::SeekError, "No segment info available");
return false;
}
long long timecode_scale = info->GetTimeCodeScale();
if (timecode_scale <= 0) {
SetLastError(WebMErrorCode::SeekError, "Invalid timecode scale");
return false;
}
// 타겟 타임코드 계산
long long target_timecode = WebMUtils::SecondsToTimecode(timestamp_seconds, timecode_scale);
// 타겟 시간에 가장 가까운 클러스터 찾기
const mkvparser::Cluster* target_cluster = FindClusterByTime(timestamp_seconds);
if (!target_cluster) {
SetLastError(WebMErrorCode::SeekError, "Cannot find target cluster");
return false;
}
// 상태 업데이트
m_state->current_cluster = target_cluster;
m_state->current_block_entry = nullptr;
m_state->current_timestamp = timestamp_seconds;
m_state->end_of_file = false;
// 정확한 시간의 프레임까지 이동
while (!m_state->end_of_file) {
if (!AdvanceToNextFrame()) {
m_state->end_of_file = true;
break;
}
// 현재 블록의 시간 확인
if (m_state->current_block_entry && m_state->current_block_entry->GetBlock()) {
const mkvparser::Block* block = m_state->current_block_entry->GetBlock();
long long cluster_time = m_state->current_cluster->GetTime();
long long block_time = block->GetTime(m_state->current_cluster);
double current_time = WebMUtils::TimecodeToSeconds(cluster_time + block_time, timecode_scale);
if (current_time >= timestamp_seconds) {
m_state->current_timestamp = current_time;
SetLastError(WebMErrorCode::Success);
return true;
}
}
m_state->current_frame_index++;
}
SetLastError(WebMErrorCode::SeekError, "Cannot reach target time");
return false;
}
uint64_t WebMFileReader::GetCurrentFrameIndex() const {
return m_state ? m_state->current_frame_index : 0;
}
double WebMFileReader::GetCurrentTimestamp() const {
return m_state ? m_state->current_timestamp : 0.0;
}
bool WebMFileReader::IsEndOfFile() const {
return m_state ? m_state->end_of_file : true;
}
bool WebMFileReader::Reset() {
if (!IsFileOpen()) return false;
std::lock_guard<std::mutex> lock(m_access_mutex);
// 첫 번째 클러스터로 이동
const mkvparser::Cluster* cluster = m_state->segment->GetFirst();
if (!cluster) {
SetLastError(WebMErrorCode::ReadError, "No clusters found");
return false;
}
m_state->current_cluster = cluster;
m_state->current_block_entry = nullptr;
m_state->current_frame_index = 0;
m_state->current_timestamp = 0.0;
m_state->end_of_file = false;
return true;
}
uint64_t WebMFileReader::GetTotalFrames() const {
return m_state ? m_state->metadata.total_frames : 0;
}
double WebMFileReader::GetDuration() const {
return m_state ? m_state->metadata.duration_seconds : 0.0;
}
WebMErrorCode WebMFileReader::GetLastError() const {
return m_state ? m_state->last_error : WebMErrorCode::Unknown;
}
std::string WebMFileReader::GetLastErrorString() const {
if (!m_state) return "Internal error";
std::string result = ErrorCodeToString(m_state->last_error);
if (!m_state->last_error_message.empty()) {
result += ": " + m_state->last_error_message;
}
return result;
}
std::string WebMFileReader::GetLibWebMVersion() {
int major, minor, build, revision;
mkvparser::GetVersion(major, minor, build, revision);
return std::to_string(major) + "." + std::to_string(minor) + "." +
std::to_string(build) + "." + std::to_string(revision);
}
std::vector<std::string> WebMFileReader::GetSupportedCodecs() {
return { WebMUtils::CodecIds::AV1, WebMUtils::CodecIds::VP9, WebMUtils::CodecIds::VP8 };
}
// 내부 helper 메서드 구현
bool WebMFileReader::InitializeParser() {
long long pos = 0;
mkvparser::EBMLHeader ebml_header;
// EBML 헤더 파싱
long long ebml_header_size = ebml_header.Parse(m_state->reader.get(), pos);
if (ebml_header_size < 0) {
std::string detailed_error = "Invalid EBML header - Parse() returned: " + std::to_string(ebml_header_size);
// 추가 디버깅: 파일 시작 부분 읽기 (올바른 Read 메서드 시그니처 사용)
char buffer[32];
int read_result = m_state->reader->Read(0, 32, reinterpret_cast<unsigned char*>(buffer));
if (read_result == 0) {
std::string hex_dump = ". First 32 bytes: ";
for (int i = 0; i < 32; i++) {
char hex[4];
sprintf_s(hex, "%02X ", static_cast<unsigned char>(buffer[i]));
hex_dump += hex;
}
detailed_error += hex_dump;
}
SetLastError(WebMErrorCode::InvalidFormat, detailed_error);
return false;
}
// WebM 파일 확인
if (!ebml_header.m_docType || std::string(ebml_header.m_docType) != "webm") {
std::string doc_type = ebml_header.m_docType ? std::string(ebml_header.m_docType) : "NULL";
std::string error_msg = "Not a WebM file - doc type is: '" + doc_type + "'";
// 추가 디버깅 정보
error_msg += ". EBML header details: version=" + std::to_string(ebml_header.m_version) +
", docTypeVersion=" + std::to_string(ebml_header.m_docTypeVersion);
SetLastError(WebMErrorCode::InvalidFormat, error_msg);
return false;
}
// Segment 파싱
mkvparser::Segment* segment_ptr = nullptr;
long long create_status = mkvparser::Segment::CreateInstance(
m_state->reader.get(), pos, segment_ptr);
if (create_status < 0 || !segment_ptr) {
SetLastError(WebMErrorCode::InvalidFormat, "Cannot create segment");
return false;
}
m_state->segment.reset(segment_ptr);
if (!m_state->segment) {
SetLastError(WebMErrorCode::InvalidFormat, "Cannot create segment");
return false;
}
// Segment 정보 로드
long load_status = m_state->segment->Load();
if (load_status < 0) {
SetLastError(WebMErrorCode::InvalidFormat, "Cannot load segment");
return false;
}
return true;
}
void WebMFileReader::CleanupParser() {
if (m_state) {
m_state->segment.reset();
}
}
bool WebMFileReader::ExtractVideoTracks() {
if (!m_state->segment) return false;
m_state->video_tracks.clear();
const mkvparser::Tracks* tracks = m_state->segment->GetTracks();
if (!tracks) return false;
for (unsigned long i = 0; i < tracks->GetTracksCount(); ++i) {
const mkvparser::Track* track = tracks->GetTrackByIndex(i);
if (!track || track->GetType() != mkvparser::Track::kVideo) {
continue;
}
const mkvparser::VideoTrack* video_track =
static_cast<const mkvparser::VideoTrack*>(track);
VideoTrackInfo info;
info.track_number = video_track->GetNumber();
info.codec_id = video_track->GetCodecId() ? video_track->GetCodecId() : "";
// 디버깅: 실제 코덱 ID를 파일에 출력
std::string debug_msg = "Found video track #" + std::to_string(info.track_number) +
" with codec_id: '" + info.codec_id + "'";
SetLastError(WebMErrorCode::Success, debug_msg); // 임시로 이 메시지를 상태에 표시
// 파일에 코덱 ID 기록
std::ofstream codec_debug("codec_debug.log", std::ios::app);
codec_debug << "WebM Codec Detection:" << std::endl;
codec_debug << " Track #" << info.track_number << std::endl;
codec_debug << " Codec ID: '" << info.codec_id << "'" << std::endl;
codec_debug << " Expected AV1 variants:" << std::endl;
codec_debug << " - V_AV01: '" << WebMUtils::CodecIds::AV1 << "'" << std::endl;
codec_debug << " - V_AV1: '" << WebMUtils::CodecIds::AV1_ALT1 << "'" << std::endl;
codec_debug << " - AV01: '" << WebMUtils::CodecIds::AV1_ALT2 << "'" << std::endl;
codec_debug << " Match AV1: " << (info.codec_id == WebMUtils::CodecIds::AV1 ? "YES" : "NO") << std::endl;
codec_debug << " Match AV1_ALT1: " << (info.codec_id == WebMUtils::CodecIds::AV1_ALT1 ? "YES" : "NO") << std::endl;
codec_debug << " Match AV1_ALT2: " << (info.codec_id == WebMUtils::CodecIds::AV1_ALT2 ? "YES" : "NO") << std::endl;
codec_debug << " Supported: " << (IsVideoCodecSupported(info.codec_id) ? "YES" : "NO") << std::endl;
codec_debug << std::endl;
codec_debug.close();
info.codec_type = DetectCodecType(info.codec_id);
info.codec_name = ExtractCodecName(info.codec_id);
info.width = static_cast<uint32_t>(video_track->GetWidth());
info.height = static_cast<uint32_t>(video_track->GetHeight());
info.frame_rate = CalculateFrameRate(video_track);
info.frame_count = 0; // TODO: 실제 계산
info.is_default = false; // TODO: 트랙의 기본 설정 확인
m_state->video_tracks.push_back(info);
}
return !m_state->video_tracks.empty();
}
bool WebMFileReader::ExtractVideoMetadata() {
if (m_state->selected_track_number == 0) return false;
// 선택된 트랙 찾기
auto it = std::find_if(m_state->video_tracks.begin(), m_state->video_tracks.end(),
[this](const VideoTrackInfo& info) {
return info.track_number == m_state->selected_track_number;
});
if (it == m_state->video_tracks.end()) return false;
// 메타데이터 설정
VideoMetadata& meta = m_state->metadata;
meta.width = it->width;
meta.height = it->height;
meta.frame_rate = it->frame_rate;
meta.codec_type = it->codec_type;
meta.codec_name = it->codec_name;
meta.color_space = ColorSpace::YUV420P; // 기본값
// 세그먼트 정보에서 duration 가져오기
const mkvparser::SegmentInfo* info = m_state->segment->GetInfo();
if (info) {
long long duration_ns = info->GetDuration();
long long timecode_scale = info->GetTimeCodeScale();
if (duration_ns > 0 && timecode_scale > 0) {
meta.duration_seconds = static_cast<double>(duration_ns) / timecode_scale / 1000000000.0;
}
}
// 총 프레임 수 추정
if (meta.frame_rate > 0 && meta.duration_seconds > 0) {
meta.total_frames = static_cast<uint64_t>(meta.frame_rate * meta.duration_seconds);
}
meta.file_path = m_state->file_path;
return meta.IsValid();
}
VideoCodecType WebMFileReader::DetectCodecType(const std::string& codec_id) const {
if (codec_id == WebMUtils::CodecIds::AV1 ||
codec_id == WebMUtils::CodecIds::AV1_ALT1 ||
codec_id == WebMUtils::CodecIds::AV1_ALT2) {
return VideoCodecType::AV1;
}
if (codec_id == WebMUtils::CodecIds::VP9) return VideoCodecType::VP9;
if (codec_id == WebMUtils::CodecIds::VP8) return VideoCodecType::VP8;
return VideoCodecType::AV1; // 기본값
}
double WebMFileReader::CalculateFrameRate(const mkvparser::VideoTrack* video_track) const {
if (!video_track) return 0.0;
double frame_rate = video_track->GetFrameRate();
if (frame_rate > 0) return frame_rate;
// TODO: 클러스터 분석으로 프레임 레이트 추정
return 30.0; // 임시 기본값
}
void WebMFileReader::SetLastError(WebMErrorCode error, const std::string& message) {
if (m_state) {
m_state->last_error = error;
m_state->last_error_message = message;
}
}
std::string WebMFileReader::ErrorCodeToString(WebMErrorCode error) const {
switch (error) {
case WebMErrorCode::Success: return "Success";
case WebMErrorCode::FileNotFound: return "File not found";
case WebMErrorCode::InvalidFormat: return "Invalid format";
case WebMErrorCode::UnsupportedCodec: return "Unsupported codec";
case WebMErrorCode::NoVideoTrack: return "No video track";
case WebMErrorCode::ReadError: return "Read error";
case WebMErrorCode::SeekError: return "Seek error";
default: return "Unknown error";
}
}
bool WebMFileReader::IsVideoCodecSupported(const std::string& codec_id) {
return codec_id == WebMUtils::CodecIds::AV1 ||
codec_id == WebMUtils::CodecIds::AV1_ALT1 ||
codec_id == WebMUtils::CodecIds::AV1_ALT2 ||
codec_id == WebMUtils::CodecIds::VP9; // VP8 currently unsupported
}
std::string WebMFileReader::ExtractCodecName(const std::string& codec_id) {
if (codec_id == WebMUtils::CodecIds::AV1 ||
codec_id == WebMUtils::CodecIds::AV1_ALT1 ||
codec_id == WebMUtils::CodecIds::AV1_ALT2) {
return "AV1";
}
if (codec_id == WebMUtils::CodecIds::VP9) return "VP9";
if (codec_id == WebMUtils::CodecIds::VP8) return "VP8";
return "Unknown";
}
bool WebMFileReader::ReadPacketFromBlock(const mkvparser::Block* block, VideoPacket& packet) {
if (!block) return false;
// 선택된 트랙과 블록의 트랙 번호 확인
if (static_cast<uint64_t>(block->GetTrackNumber()) != m_state->selected_track_number) {
return false; // 다른 트랙의 블록이므로 스킵
}
// 블록에서 프레임 수 확인 (일반적으로 1개)
int frame_count = block->GetFrameCount();
if (frame_count <= 0) return false;
// 첫 번째 프레임 데이터 가져오기
const mkvparser::Block::Frame& frame = block->GetFrame(0);
if (frame.len <= 0) return false;
// VideoPacket 메모리 할당
if (!packet.AllocateData(static_cast<size_t>(frame.len))) {
return false;
}
// 프레임 데이터 읽기
if (frame.Read(m_state->reader.get(), packet.data.get()) < 0) {
packet.data.reset();
packet.size = 0;
return false;
}
return true;
}
bool WebMFileReader::AdvanceToNextFrame() {
if (!m_state->current_cluster) {
// 첫 번째 클러스터로 시작
m_state->current_cluster = m_state->segment->GetFirst();
if (!m_state->current_cluster) return false;
}
// 현재 클러스터에서 다음 블록 엔트리 찾기
while (m_state->current_cluster) {
const mkvparser::BlockEntry* block_entry = nullptr;
if (m_state->current_block_entry) {
// 다음 블록 엔트리로 이동
long status = m_state->current_cluster->GetNext(m_state->current_block_entry, block_entry);
if (status < 0) return false;
} else {
// 클러스터의 첫 번째 블록 엔트리 가져오기
long status = m_state->current_cluster->GetFirst(block_entry);
if (status < 0) return false;
}
// 블록 엔트리가 있으면서 선택된 트랙의 블록인지 확인
while (block_entry && !block_entry->EOS()) {
const mkvparser::Block* block = block_entry->GetBlock();
if (block && static_cast<uint64_t>(block->GetTrackNumber()) == m_state->selected_track_number) {
m_state->current_block_entry = block_entry;
return true;
}
// 다른 트랙의 블록이므로 다음 블록으로 이동
const mkvparser::BlockEntry* next_entry = nullptr;
long status = m_state->current_cluster->GetNext(block_entry, next_entry);
if (status < 0) break;
block_entry = next_entry;
}
// 현재 클러스터에서 더 이상 블록이 없으면 다음 클러스터로 이동
m_state->current_cluster = m_state->segment->GetNext(m_state->current_cluster);
m_state->current_block_entry = nullptr;
}
// 더 이상 클러스터가 없음
return false;
}
const mkvparser::Cluster* WebMFileReader::FindClusterByTime(double timestamp_seconds) {
if (!m_state->segment) return nullptr;
const mkvparser::SegmentInfo* info = m_state->segment->GetInfo();
if (!info) return nullptr;
long long timecode_scale = info->GetTimeCodeScale();
if (timecode_scale <= 0) return nullptr;
// 타겟 타임코드 계산
long long target_timecode = WebMUtils::SecondsToTimecode(timestamp_seconds, timecode_scale);
// 첫 번째 클러스터부터 순차 탐색
const mkvparser::Cluster* cluster = m_state->segment->GetFirst();
const mkvparser::Cluster* best_cluster = cluster;
while (cluster && !cluster->EOS()) {
long long cluster_time = cluster->GetTime();
// 타겟 시간에 가장 가까운 클러스터 찾기
if (cluster_time <= target_timecode) {
best_cluster = cluster;
} else {
break; // 타겟 시간을 넘어섰으므로 이전 클러스터가 최적
}
cluster = m_state->segment->GetNext(cluster);
}
return best_cluster;
}
// WebMUtils 구현
namespace WebMUtils {
bool IsWebMFile(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
if (!file.is_open()) return false;
uint8_t header[32];
file.read(reinterpret_cast<char*>(header), sizeof(header));
return IsValidWebMHeader(header, file.gcount());
}
bool IsValidWebMHeader(const uint8_t* data, size_t size) {
if (!data || size < 4) return false;
// EBML 헤더 시그니처 확인 (0x1A45DFA3)
return data[0] == 0x1A && data[1] == 0x45 &&
data[2] == 0xDF && data[3] == 0xA3;
}
std::vector<CodecInfo> GetAllWebMCodecs() {
return {
{ CodecIds::AV1, "AV1", VideoCodecType::AV1, true },
{ CodecIds::VP9, "VP9", VideoCodecType::VP9, false }, // TODO: VP9 support
{ CodecIds::VP8, "VP8", VideoCodecType::VP8, false }
};
}
CodecInfo GetCodecInfo(const std::string& codec_id) {
auto codecs = GetAllWebMCodecs();
auto it = std::find_if(codecs.begin(), codecs.end(),
[&codec_id](const CodecInfo& info) {
return info.codec_id == codec_id;
});
if (it != codecs.end()) {
return *it;
}
return { codec_id, "Unknown", VideoCodecType::AV1, false };
}
double TimecodeToSeconds(long long timecode, long long timecode_scale) {
if (timecode_scale <= 0) return 0.0;
return static_cast<double>(timecode) * timecode_scale / 1000000000.0;
}
long long SecondsToTimecode(double seconds, long long timecode_scale) {
if (timecode_scale <= 0) return 0;
return static_cast<long long>(seconds * 1000000000.0 / timecode_scale);
}
} // namespace WebMUtils
} // namespace Vav2Player

View File

@@ -1,144 +0,0 @@
#pragma once
#include "../Common/VideoTypes.h"
#include "IWebMFileReader.h"
#include <mkvparser.hpp>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
namespace Vav2Player {
// WebM/MKV 파일을 파싱하여 AV1 비디오 스트림을 추출하는 클래스
// libwebm의 mkvparser를 사용하여 구현
class WebMFileReader : public IWebMFileReader {
public:
WebMFileReader();
~WebMFileReader();
// 복사 방지
WebMFileReader(const WebMFileReader&) = delete;
WebMFileReader& operator=(const WebMFileReader&) = delete;
// 파일 열기/닫기 (interface implementation)
bool OpenFile(const std::string& file_path) override;
void CloseFile() override;
bool IsFileOpen() const override;
// 파일 및 스트림 정보 (interface implementation)
const VideoMetadata& GetVideoMetadata() const override;
std::string GetFilePath() const override;
// 비디오 트랙 관리 (interface implementation)
std::vector<VideoTrackInfo> GetVideoTracks() const override;
bool SelectVideoTrack(uint64_t track_number) override;
uint64_t GetSelectedTrackNumber() const override;
// 패킷 읽기 (interface implementation)
bool ReadNextPacket(VideoPacket& packet) override;
bool SeekToFrame(uint64_t frame_index) override;
bool SeekToTime(double timestamp_seconds) override;
// 위치 정보 (interface implementation)
uint64_t GetCurrentFrameIndex() const override;
double GetCurrentTimestamp() const override;
bool IsEndOfFile() const override;
// 파일 탐색 및 통계 (interface implementation)
bool Reset() override;
uint64_t GetTotalFrames() const override;
double GetDuration() const override;
// 에러 처리 (interface implementation)
WebMErrorCode GetLastError() const override;
std::string GetLastErrorString() const override;
// libwebm 관련 정보
static std::string GetLibWebMVersion();
static std::vector<std::string> GetSupportedCodecs();
private:
// libwebm 관련 내부 클래스들
class MkvReader; // mkvparser::IMkvReader 구현체
// 내부 상태 관리
struct InternalState;
std::unique_ptr<InternalState> m_state;
// Thread safety for multi-instance file access
mutable std::mutex m_access_mutex;
// 내부 helper 메서드들
bool InitializeParser();
void CleanupParser();
bool ParseFileHeader();
bool ExtractVideoTracks();
bool ValidateSelectedTrack();
// 메타데이터 추출
bool ExtractVideoMetadata();
VideoCodecType DetectCodecType(const std::string& codec_id) const;
double CalculateFrameRate(const mkvparser::VideoTrack* video_track) const;
uint64_t EstimateFrameCount() const;
// 패킷 읽기 관련
bool ReadPacketFromBlock(const mkvparser::Block* block, VideoPacket& packet);
bool AdvanceToNextFrame();
// 탐색 관련
const mkvparser::Cluster* FindClusterByTime(double timestamp_seconds);
const mkvparser::Block* FindBlockByFrame(uint64_t frame_index);
// 에러 처리
void SetLastError(WebMErrorCode error, const std::string& message = "");
std::string ErrorCodeToString(WebMErrorCode error) const;
// 유틸리티 함수들
static bool IsVideoCodecSupported(const std::string& codec_id);
static std::string ExtractCodecName(const std::string& codec_id);
static ColorSpace GetColorSpaceFromTrack(const mkvparser::VideoTrack* track);
};
// WebM 관련 유틸리티 함수들
namespace WebMUtils {
// WebM 파일 검증
bool IsWebMFile(const std::string& file_path);
bool IsValidWebMHeader(const uint8_t* data, size_t size);
// 코덱 정보
struct CodecInfo {
std::string codec_id;
std::string friendly_name;
VideoCodecType codec_type;
bool is_supported;
};
std::vector<CodecInfo> GetAllWebMCodecs();
CodecInfo GetCodecInfo(const std::string& codec_id);
// WebM 포맷 상수들
namespace CodecIds {
constexpr const char* AV1 = "V_AV01";
constexpr const char* AV1_ALT1 = "V_AV1"; // 대체 AV1 ID
constexpr const char* AV1_ALT2 = "AV01"; // 짧은 형태
constexpr const char* VP9 = "V_VP9";
constexpr const char* VP8 = "V_VP8";
}
// 시간/타임스탬프 변환
double TimecodeToSeconds(long long timecode, long long timecode_scale);
long long SecondsToTimecode(double seconds, long long timecode_scale);
// 파일 크기 및 정보
struct FileInfo {
uint64_t file_size;
std::string file_format;
std::string muxing_app;
std::string writing_app;
double duration_seconds;
};
FileInfo GetWebMFileInfo(const std::string& file_path);
}
} // namespace Vav2Player

View File

@@ -0,0 +1,201 @@
#include "pch.h"
#include "SimpleLogger.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <cstdarg>
#include <windows.h>
namespace Vav2Player {
SimpleLogger& SimpleLogger::GetInstance() {
static SimpleLogger instance;
return instance;
}
SimpleLogger::SimpleLogger() : m_currentLevel(LogLevel::Info) {}
void SimpleLogger::SetLogLevel(LogLevel level) {
std::lock_guard<std::mutex> lock(m_mutex);
m_currentLevel = level;
}
// ===== wstring overloads (original implementation) =====
void SimpleLogger::LogMessage(LogLevel level, const std::wstring& message, const std::wstring& source) {
if (static_cast<int>(level) < static_cast<int>(m_currentLevel)) {
return;
}
std::lock_guard<std::mutex> lock(m_mutex);
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
std::tm tm;
localtime_s(&tm, &time_t);
std::wostringstream oss;
oss << std::put_time(&tm, L"%H:%M:%S") << L"."
<< std::setfill(L'0') << std::setw(3) << ms.count()
<< L" [" << GetLevelString(level) << L"]";
if (!source.empty()) {
oss << L" (" << source << L")";
}
oss << L": " << message;
OutputToConsoleAndDebug(oss.str());
}
void SimpleLogger::LogDebug(const std::wstring& message, const std::wstring& source) {
LogMessage(LogLevel::Debug, message, source);
}
void SimpleLogger::LogInfo(const std::wstring& message, const std::wstring& source) {
LogMessage(LogLevel::Info, message, source);
}
void SimpleLogger::LogWarning(const std::wstring& message, const std::wstring& source) {
LogMessage(LogLevel::Warning, message, source);
}
void SimpleLogger::LogError(const std::wstring& message, const std::wstring& source) {
LogMessage(LogLevel::Error, message, source);
}
// ===== std::string overloads (new implementation) =====
void SimpleLogger::LogMessage(LogLevel level, const std::string& message, const std::string& source) {
// Convert std::string to std::wstring
std::wstring wmessage(message.begin(), message.end());
std::wstring wsource(source.begin(), source.end());
LogMessage(level, wmessage, wsource);
}
void SimpleLogger::LogDebug(const std::string& message, const std::string& source) {
LogMessage(LogLevel::Debug, message, source);
}
void SimpleLogger::LogInfo(const std::string& message, const std::string& source) {
LogMessage(LogLevel::Info, message, source);
}
void SimpleLogger::LogWarning(const std::string& message, const std::string& source) {
LogMessage(LogLevel::Warning, message, source);
}
void SimpleLogger::LogError(const std::string& message, const std::string& source) {
LogMessage(LogLevel::Error, message, source);
}
// ===== printf-style variadic overloads (new implementation) =====
void SimpleLogger::LogDebugF(const char* format, ...) {
va_list args;
va_start(args, format);
char buffer[4096];
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
LogDebug(std::string(buffer), "");
}
void SimpleLogger::LogInfoF(const char* format, ...) {
va_list args;
va_start(args, format);
char buffer[4096];
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
LogInfo(std::string(buffer), "");
}
void SimpleLogger::LogWarningF(const char* format, ...) {
va_list args;
va_start(args, format);
char buffer[4096];
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
LogWarning(std::string(buffer), "");
}
void SimpleLogger::LogErrorF(const char* format, ...) {
va_list args;
va_start(args, format);
char buffer[4096];
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
LogError(std::string(buffer), "");
}
// ===== Video-specific logging methods =====
void SimpleLogger::LogVideoLoad(const std::wstring& filename, bool success) {
if (success) {
LogInfo(L"Video loaded: " + filename, L"VideoPlayer");
} else {
LogError(L"Failed to load video: " + filename, L"VideoPlayer");
}
}
void SimpleLogger::LogVideoPlay(const std::wstring& filename) {
LogInfo(L"Playing: " + filename, L"VideoPlayer");
}
void SimpleLogger::LogVideoPause(const std::wstring& filename) {
LogInfo(L"Paused: " + filename, L"VideoPlayer");
}
void SimpleLogger::LogVideoStop(const std::wstring& filename) {
LogInfo(L"Stopped: " + filename, L"VideoPlayer");
}
void SimpleLogger::LogVideoError(const std::wstring& error, const std::wstring& filename) {
std::wstring msg = error;
if (!filename.empty()) {
msg += L" (File: " + filename + L")";
}
LogError(msg, L"VideoPlayer");
}
void SimpleLogger::LogFrameInfo(int currentFrame, int totalFrames, double fps) {
std::wostringstream oss;
oss << L"Frame " << currentFrame << L"/" << totalFrames
<< L" @ " << std::fixed << std::setprecision(1) << fps << L" FPS";
LogDebug(oss.str(), L"VideoPlayer");
}
void SimpleLogger::LogDecoderInfo(const std::wstring& decoderName, const std::wstring& info) {
LogInfo(decoderName + L": " + info, L"Decoder");
}
// ===== Private helper methods =====
std::wstring SimpleLogger::GetLevelString(LogLevel level) {
switch (level) {
case LogLevel::Debug: return L"DEBUG";
case LogLevel::Info: return L"INFO";
case LogLevel::Warning: return L"WARN";
case LogLevel::Error: return L"ERROR";
default: return L"UNKNOWN";
}
}
void SimpleLogger::OutputToConsoleAndDebug(const std::wstring& logLine) {
// Output to console
std::wcout << logLine << std::endl;
// Output to Visual Studio Output window
OutputDebugStringW((logLine + L"\n").c_str());
}
} // namespace Vav2Player

View File

@@ -1,11 +1,6 @@
#pragma once
#include <string>
#include <iostream>
#include <fstream>
#include <mutex>
#include <chrono>
#include <sstream>
#include <iomanip>
namespace Vav2Player {
@@ -16,132 +11,87 @@ enum class LogLevel {
Error = 3
};
/**
* Simple logger with support for both wstring and variadic arguments
* Thread-safe singleton logger for both console and WinUI3 environments
*
* Features:
* - wstring support (native WinUI3)
* - std::string support (native C++)
* - printf-style variadic arguments
* - OutputDebugString for Visual Studio
* - Console output for debugging
*/
class SimpleLogger {
public:
static SimpleLogger& GetInstance() {
static SimpleLogger instance;
return instance;
}
static SimpleLogger& GetInstance();
void SetLogLevel(LogLevel level) {
std::lock_guard<std::mutex> lock(m_mutex);
m_currentLevel = level;
}
void SetLogLevel(LogLevel level);
void LogMessage(LogLevel level, const std::wstring& message, const std::wstring& source = L"") {
if (static_cast<int>(level) < static_cast<int>(m_currentLevel)) {
return; // Skip messages below current log level
}
// ===== wstring overloads (original) =====
void LogMessage(LogLevel level, const std::wstring& message, const std::wstring& source = L"");
void LogDebug(const std::wstring& message, const std::wstring& source = L"");
void LogInfo(const std::wstring& message, const std::wstring& source = L"");
void LogWarning(const std::wstring& message, const std::wstring& source = L"");
void LogError(const std::wstring& message, const std::wstring& source = L"");
std::lock_guard<std::mutex> lock(m_mutex);
// ===== std::string overloads (new) =====
void LogMessage(LogLevel level, const std::string& message, const std::string& source = "");
void LogDebug(const std::string& message, const std::string& source = "");
void LogInfo(const std::string& message, const std::string& source = "");
void LogWarning(const std::string& message, const std::string& source = "");
void LogError(const std::string& message, const std::string& source = "");
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
// ===== printf-style variadic overloads (new) =====
void LogDebugF(const char* format, ...);
void LogInfoF(const char* format, ...);
void LogWarningF(const char* format, ...);
void LogErrorF(const char* format, ...);
std::tm tm;
localtime_s(&tm, &time_t);
std::wostringstream oss;
oss << std::put_time(&tm, L"%H:%M:%S") << L"."
<< std::setfill(L'0') << std::setw(3) << ms.count()
<< L" [" << GetLevelString(level) << L"]";
if (!source.empty()) {
oss << L" (" << source << L")";
}
oss << L": " << message;
std::wstring logLine = oss.str();
// Output to console
std::wcout << logLine << std::endl;
// Also output to debug console in Windows
#ifdef _WIN32
OutputDebugStringW((logLine + L"\n").c_str());
#endif
}
// Convenience methods
void LogDebug(const std::wstring& message, const std::wstring& source = L"") {
LogMessage(LogLevel::Debug, message, source);
}
void LogInfo(const std::wstring& message, const std::wstring& source = L"") {
LogMessage(LogLevel::Info, message, source);
}
void LogWarning(const std::wstring& message, const std::wstring& source = L"") {
LogMessage(LogLevel::Warning, message, source);
}
void LogError(const std::wstring& message, const std::wstring& source = L"") {
LogMessage(LogLevel::Error, message, source);
}
// Video-specific logging methods
void LogVideoLoad(const std::wstring& filename, bool success = true) {
if (success) {
LogInfo(L"Video loaded: " + filename, L"VideoPlayer");
} else {
LogError(L"Failed to load video: " + filename, L"VideoPlayer");
}
}
void LogVideoPlay(const std::wstring& filename) {
LogInfo(L"Playing: " + filename, L"VideoPlayer");
}
void LogVideoPause(const std::wstring& filename) {
LogInfo(L"Paused: " + filename, L"VideoPlayer");
}
void LogVideoStop(const std::wstring& filename) {
LogInfo(L"Stopped: " + filename, L"VideoPlayer");
}
void LogVideoError(const std::wstring& error, const std::wstring& filename = L"") {
std::wstring msg = error;
if (!filename.empty()) {
msg += L" (File: " + filename + L")";
}
LogError(msg, L"VideoPlayer");
}
void LogFrameInfo(int currentFrame, int totalFrames, double fps) {
std::wostringstream oss;
oss << L"Frame " << currentFrame << L"/" << totalFrames
<< L" @ " << std::fixed << std::setprecision(1) << fps << L" FPS";
LogDebug(oss.str(), L"VideoPlayer");
}
void LogDecoderInfo(const std::wstring& decoderName, const std::wstring& info) {
LogInfo(decoderName + L": " + info, L"Decoder");
}
// ===== Video-specific logging methods =====
void LogVideoLoad(const std::wstring& filename, bool success = true);
void LogVideoPlay(const std::wstring& filename);
void LogVideoPause(const std::wstring& filename);
void LogVideoStop(const std::wstring& filename);
void LogVideoError(const std::wstring& error, const std::wstring& filename = L"");
void LogFrameInfo(int currentFrame, int totalFrames, double fps);
void LogDecoderInfo(const std::wstring& decoderName, const std::wstring& info);
private:
SimpleLogger() : m_currentLevel(LogLevel::Info) {}
SimpleLogger();
~SimpleLogger() = default;
SimpleLogger(const SimpleLogger&) = delete;
SimpleLogger& operator=(const SimpleLogger&) = delete;
std::wstring GetLevelString(LogLevel level) {
switch (level) {
case LogLevel::Debug: return L"DEBUG";
case LogLevel::Info: return L"INFO";
case LogLevel::Warning: return L"WARN";
case LogLevel::Error: return L"ERROR";
default: return L"UNKNOWN";
}
}
std::wstring GetLevelString(LogLevel level);
void OutputToConsoleAndDebug(const std::wstring& logLine);
std::mutex m_mutex;
LogLevel m_currentLevel;
};
// Global convenience macros for easier usage
// ===== Logging Macros =====
// Use these macros instead of creating new logging functions
// LOG_* : For std::wstring messages with optional source parameter
// Usage: LOG_INFO(L"Video loaded", L"VideoPlayer")
#define LOG_DEBUG(msg, source) Vav2Player::SimpleLogger::GetInstance().LogDebug(msg, source)
#define LOG_INFO(msg, source) Vav2Player::SimpleLogger::GetInstance().LogInfo(msg, source)
#define LOG_WARNING(msg, source) Vav2Player::SimpleLogger::GetInstance().LogWarning(msg, source)
#define LOG_ERROR(msg, source) Vav2Player::SimpleLogger::GetInstance().LogError(msg, source)
} // namespace Vav2Player
// LOGF_* : For printf-style formatted messages (const char* format, ...)
// Usage: LOGF_INFO("[Component] Value: %d", value)
#define LOGF_DEBUG(...) Vav2Player::SimpleLogger::GetInstance().LogDebugF(__VA_ARGS__)
#define LOGF_INFO(...) Vav2Player::SimpleLogger::GetInstance().LogInfoF(__VA_ARGS__)
#define LOGF_WARNING(...) Vav2Player::SimpleLogger::GetInstance().LogWarningF(__VA_ARGS__)
#define LOGF_ERROR(...) Vav2Player::SimpleLogger::GetInstance().LogErrorF(__VA_ARGS__)
// LOGS_* : For std::string messages with optional source parameter
// Usage: LOGS_INFO(errorMessage, "NetworkModule")
#define LOGS_DEBUG(msg, source) Vav2Player::SimpleLogger::GetInstance().LogDebug(msg, source)
#define LOGS_INFO(msg, source) Vav2Player::SimpleLogger::GetInstance().LogInfo(msg, source)
#define LOGS_WARNING(msg, source) Vav2Player::SimpleLogger::GetInstance().LogWarning(msg, source)
#define LOGS_ERROR(msg, source) Vav2Player::SimpleLogger::GetInstance().LogError(msg, source)
} // namespace Vav2Player

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "FrameProcessor.h"
#include "../Logger/SimpleLogger.h"
#include <iostream>
#include <Windows.h>
@@ -31,7 +32,7 @@ bool FrameProcessor::ProcessFrame(VavCorePlayer* player,
std::function<void(bool success)> onComplete)
{
if (!player || !m_renderer || !m_dispatcherQueue) {
OutputDebugStringA("[FrameProcessor] Invalid state: missing player/renderer/dispatcherQueue\n");
LOGF_ERROR("[FrameProcessor] Invalid state: missing player/renderer/dispatcherQueue");
return false;
}
@@ -39,31 +40,27 @@ bool FrameProcessor::ProcessFrame(VavCorePlayer* player,
bool expected = false;
if (!m_frameProcessing.compare_exchange_strong(expected, true)) {
m_framesDropped++;
char buf[256];
sprintf_s(buf, "[FrameProcessor] Frame dropped (#%llu) - previous frame still processing (decoded: %llu)\n",
LOGF_INFO("[FrameProcessor] Frame dropped (#%llu) - previous frame still processing (decoded: %llu)",
m_framesDropped.load(), m_framesDecoded.load());
OutputDebugStringA(buf);
return false;
}
char buf2[256];
sprintf_s(buf2, "[FrameProcessor] ProcessFrame START (decoded: %llu, dropped: %llu)\n",
LOGF_INFO("[FrameProcessor] ProcessFrame START (decoded: %llu, dropped: %llu)",
m_framesDecoded.load(), m_framesDropped.load());
OutputDebugStringA(buf2);
// Get NV12 texture from renderer
OutputDebugStringA("[FrameProcessor] Getting NV12 texture...\n");
LOGF_INFO("[FrameProcessor] Getting NV12 texture...");
ID3D12Resource* nv12Texture = m_renderer->GetNV12TextureForCUDAInterop();
if (!nv12Texture) {
OutputDebugStringA("[FrameProcessor] ERROR: Failed to get NV12 texture - clearing flag\n");
LOGF_ERROR("[FrameProcessor] Failed to get NV12 texture - clearing flag");
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
return false;
}
OutputDebugStringA("[FrameProcessor] NV12 texture acquired\n");
LOGF_INFO("[FrameProcessor] NV12 texture acquired");
// Decode frame to D3D12 surface (blocking)
OutputDebugStringA("[FrameProcessor] Starting vavcore_decode_to_surface (BLOCKING)...\n");
LOGF_INFO("[FrameProcessor] Starting vavcore_decode_to_surface (BLOCKING)...");
VavCoreVideoFrame vavFrame = {};
VavCoreResult result = vavcore_decode_to_surface(
player,
@@ -71,16 +68,14 @@ bool FrameProcessor::ProcessFrame(VavCorePlayer* player,
nv12Texture,
&vavFrame
);
OutputDebugStringA("[FrameProcessor] vavcore_decode_to_surface COMPLETED\n");
LOGF_INFO("[FrameProcessor] vavcore_decode_to_surface COMPLETED");
if (result != VAVCORE_SUCCESS) {
if (result != VAVCORE_END_OF_STREAM) {
m_decodeErrors++;
char errbuf[256];
sprintf_s(errbuf, "[FrameProcessor] Decode ERROR: result=%d - clearing flag\n", result);
OutputDebugStringA(errbuf);
LOGF_ERROR("[FrameProcessor] Decode ERROR: result=%d - clearing flag", result);
} else {
OutputDebugStringA("[FrameProcessor] End of stream reached - clearing flag\n");
LOGF_INFO("[FrameProcessor] End of stream reached - clearing flag");
}
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
@@ -88,47 +83,43 @@ bool FrameProcessor::ProcessFrame(VavCorePlayer* player,
}
m_framesDecoded++;
OutputDebugStringA("[FrameProcessor] Decode SUCCESS - frame decoded\n");
LOGF_INFO("[FrameProcessor] Decode SUCCESS - frame decoded");
// Enqueue render on UI thread with fence value for GPU sync
uint64_t fenceValue = vavFrame.sync_fence_value;
char fencebuf[256];
sprintf_s(fencebuf, "[FrameProcessor] Attempting to enqueue render (fenceValue=%llu)...\n", fenceValue);
OutputDebugStringA(fencebuf);
LOGF_INFO("[FrameProcessor] Attempting to enqueue render (fenceValue=%llu)...", fenceValue);
bool enqueued = m_dispatcherQueue.TryEnqueue([this, fenceValue, onComplete]() {
OutputDebugStringA("[FrameProcessor] *** UI THREAD CALLBACK STARTED ***\n");
LOGF_INFO("[FrameProcessor] *** UI THREAD CALLBACK STARTED ***");
HRESULT hr = m_renderer->RenderNV12TextureToBackBuffer(fenceValue);
bool renderSuccess = SUCCEEDED(hr);
if (!renderSuccess) {
m_renderErrors++;
char buf[256];
sprintf_s(buf, "[FrameProcessor] Render error: HRESULT = 0x%08X\n", hr);
OutputDebugStringA(buf);
LOGF_ERROR("[FrameProcessor] Render error: HRESULT = 0x%08X", hr);
} else {
OutputDebugStringA("[FrameProcessor] Render succeeded\n");
LOGF_INFO("[FrameProcessor] Render succeeded");
}
// Mark frame processing complete
OutputDebugStringA("[FrameProcessor] CLEARING m_frameProcessing flag\n");
LOGF_INFO("[FrameProcessor] CLEARING m_frameProcessing flag");
m_frameProcessing.store(false);
OutputDebugStringA("[FrameProcessor] Flag cleared - ready for next frame\n");
LOGF_INFO("[FrameProcessor] Flag cleared - ready for next frame");
if (onComplete) {
onComplete(renderSuccess);
}
OutputDebugStringA("[FrameProcessor] *** UI THREAD CALLBACK ENDED ***\n");
LOGF_INFO("[FrameProcessor] *** UI THREAD CALLBACK ENDED ***");
});
if (!enqueued) {
OutputDebugStringA("[FrameProcessor] ERROR: TryEnqueue FAILED - clearing flag\n");
LOGF_ERROR("[FrameProcessor] TryEnqueue FAILED - clearing flag");
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
return false;
}
OutputDebugStringA("[FrameProcessor] TryEnqueue SUCCESS - callback queued, ProcessFrame returning true\n");
LOGF_INFO("[FrameProcessor] TryEnqueue SUCCESS - callback queued, ProcessFrame returning true");
return true;
}

View File

@@ -1,7 +1,8 @@
#include "pch.h"
#include "PlaybackController.h"
#include "../Logger/SimpleLogger.h"
#include <chrono>
#include <iostream>
#include <sstream>
namespace Vav2Player {
@@ -9,12 +10,12 @@ PlaybackController::PlaybackController()
{
// NOTE: VavCore is initialized globally in App.xaml.cpp
// Do NOT call vavcore_initialize() here to avoid duplicate initialization
OutputDebugStringA("[PlaybackController] Constructor called (VavCore already initialized)\n");
LOGF_INFO("[PlaybackController] Constructor called (VavCore already initialized)");
}
PlaybackController::~PlaybackController()
{
OutputDebugStringA("[PlaybackController] Destructor called\n");
LOGF_INFO("[PlaybackController] Destructor called");
Stop();
Unload();
// NOTE: VavCore cleanup is handled globally in App.xaml.cpp
@@ -25,18 +26,18 @@ bool PlaybackController::InitializeVavCore()
{
VavCoreResult result = vavcore_initialize();
if (result != VAVCORE_SUCCESS) {
std::cerr << "[PlaybackController] Failed to initialize VavCore: " << result << std::endl;
LOGF_ERROR("[PlaybackController] Failed to initialize VavCore: %d", result);
return false;
}
std::cout << "[PlaybackController] VavCore initialized" << std::endl;
LOGF_INFO("[PlaybackController] VavCore initialized");
return true;
}
void PlaybackController::CleanupVavCore()
{
vavcore_cleanup();
std::cout << "[PlaybackController] VavCore cleaned up" << std::endl;
LOGF_INFO("[PlaybackController] VavCore cleaned up");
}
void PlaybackController::SetD3DDevice(void* d3d_device, VavCoreSurfaceType type)
@@ -47,54 +48,52 @@ void PlaybackController::SetD3DDevice(void* d3d_device, VavCoreSurfaceType type)
bool PlaybackController::LoadVideo(const std::wstring& filePath)
{
std::cout << "[PlaybackController] LoadVideo START" << std::endl;
LOGF_INFO("[PlaybackController] LoadVideo START");
// Unload previous video if any
if (m_isLoaded) {
std::cout << "[PlaybackController] Unloading previous video..." << std::endl;
LOGF_INFO("[PlaybackController] Unloading previous video...");
Unload();
}
// Create VavCore player
std::cout << "[PlaybackController] Creating VavCore player..." << std::endl;
LOGF_INFO("[PlaybackController] Creating VavCore player...");
m_vavCorePlayer = vavcore_create_player();
if (!m_vavCorePlayer) {
std::cerr << "[PlaybackController] ERROR: Failed to create VavCore player" << std::endl;
LOGF_ERROR("[PlaybackController] Failed to create VavCore player");
return false;
}
std::cout << "[PlaybackController] VavCore player created successfully" << std::endl;
LOGF_INFO("[PlaybackController] VavCore player created successfully");
// Set decoder type before opening file
std::cout << "[PlaybackController] Setting decoder type: " << m_decoderType << std::endl;
LOGF_INFO("[PlaybackController] Setting decoder type: %d", m_decoderType);
vavcore_set_decoder_type(m_vavCorePlayer, m_decoderType);
// Set D3D device before opening file, if it was provided
if (m_d3dDevice) {
std::cout << "[PlaybackController] Setting D3D12 device (surface type: " << m_d3dSurfaceType << ")" << std::endl;
LOGF_INFO("[PlaybackController] Setting D3D12 device (surface type: %d)", m_d3dSurfaceType);
vavcore_set_d3d_device(m_vavCorePlayer, m_d3dDevice, m_d3dSurfaceType);
} else {
std::cout << "[PlaybackController] No D3D12 device to set (will use CPU decoding)" << std::endl;
LOGF_INFO("[PlaybackController] No D3D12 device to set (will use CPU decoding)");
}
// Convert wstring to UTF-8 string
std::cout << "[PlaybackController] Converting file path to UTF-8..." << std::endl;
int size_needed = WideCharToMultiByte(CP_UTF8, 0, filePath.c_str(), -1, nullptr, 0, nullptr, nullptr);
std::string utf8FilePath(size_needed - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, filePath.c_str(), -1, &utf8FilePath[0], size_needed, nullptr, nullptr);
std::cout << "[PlaybackController] UTF-8 path: " << utf8FilePath << std::endl;
// Open video file (this will initialize the decoder)
std::cout << "[PlaybackController] Calling vavcore_open_file..." << std::endl;
LOGF_INFO("[PlaybackController] Calling vavcore_open_file...");
VavCoreResult result = vavcore_open_file(m_vavCorePlayer, utf8FilePath.c_str());
std::cout << "[PlaybackController] vavcore_open_file returned: " << result << std::endl;
LOGF_INFO("[PlaybackController] vavcore_open_file returned: %d", result);
if (result != VAVCORE_SUCCESS) {
std::cerr << "[PlaybackController] ERROR: Failed to open file with result code: " << result << std::endl;
LOGF_ERROR("[PlaybackController] Failed to open file with result code: %d", result);
vavcore_destroy_player(m_vavCorePlayer);
m_vavCorePlayer = nullptr;
return false;
}
std::cout << "[PlaybackController] File opened successfully" << std::endl;
LOGF_INFO("[PlaybackController] File opened successfully");
// Get video metadata
VavCoreVideoMetadata metadata;
@@ -106,10 +105,8 @@ bool PlaybackController::LoadVideo(const std::wstring& filePath)
m_totalFrames = metadata.total_frames;
m_duration = metadata.duration_seconds;
std::cout << "[PlaybackController] Video loaded: "
<< m_videoWidth << "x" << m_videoHeight
<< " @ " << m_frameRate << "fps"
<< ", Duration: " << m_duration << "s" << std::endl;
LOGF_INFO("[PlaybackController] Video loaded: %dx%d @ %.1ffps, Duration: %.1fs",
m_videoWidth, m_videoHeight, m_frameRate, m_duration);
}
m_currentFilePath = filePath;
@@ -144,18 +141,18 @@ void PlaybackController::Unload()
m_currentFilePath.clear();
m_isLoaded = false;
std::cout << "[PlaybackController] Video unloaded" << std::endl;
LOGF_INFO("[PlaybackController] Video unloaded");
}
void PlaybackController::Play(std::function<void()> onFrameReady)
{
if (!m_isLoaded) {
std::cerr << "[PlaybackController] Cannot play: video not loaded" << std::endl;
LOGF_ERROR("[PlaybackController] Cannot play: video not loaded");
return;
}
if (m_isPlaying) {
std::cout << "[PlaybackController] Already playing" << std::endl;
LOGF_INFO("[PlaybackController] Already playing");
return;
}
@@ -164,7 +161,7 @@ void PlaybackController::Play(std::function<void()> onFrameReady)
StartTimingThread();
std::cout << "[PlaybackController] Playback started" << std::endl;
LOGF_INFO("[PlaybackController] Playback started");
}
void PlaybackController::Pause()
@@ -174,7 +171,7 @@ void PlaybackController::Pause()
}
m_isPlaying = false;
std::cout << "[PlaybackController] Playback paused" << std::endl;
LOGF_INFO("[PlaybackController] Playback paused");
}
void PlaybackController::Stop()
@@ -189,7 +186,7 @@ void PlaybackController::Stop()
m_currentFrame = 0;
m_currentTime = 0.0;
std::cout << "[PlaybackController] Playback stopped" << std::endl;
LOGF_INFO("[PlaybackController] Playback stopped");
}
void PlaybackController::Seek(double timeSeconds)
@@ -201,9 +198,9 @@ void PlaybackController::Seek(double timeSeconds)
VavCoreResult result = vavcore_seek_to_time(m_vavCorePlayer, timeSeconds);
if (result == VAVCORE_SUCCESS) {
m_currentTime = timeSeconds;
std::cout << "[PlaybackController] Seeked to " << timeSeconds << "s" << std::endl;
LOGF_INFO("[PlaybackController] Seeked to %.2fs", timeSeconds);
} else {
std::cerr << "[PlaybackController] Seek failed: " << result << std::endl;
LOGF_ERROR("[PlaybackController] Seek failed: %d", result);
}
}
@@ -213,7 +210,7 @@ void PlaybackController::SetDecoderType(VavCoreDecoderType type)
if (m_vavCorePlayer) {
vavcore_set_decoder_type(m_vavCorePlayer, type);
std::cout << "[PlaybackController] Decoder type set to " << static_cast<int>(type) << std::endl;
LOGF_INFO("[PlaybackController] Decoder type set to %d", static_cast<int>(type));
}
}
@@ -229,7 +226,7 @@ void PlaybackController::StartTimingThread()
TimingThreadLoop();
});
std::cout << "[PlaybackController] Timing thread started" << std::endl;
LOGF_INFO("[PlaybackController] Timing thread started");
}
void PlaybackController::StopTimingThread()
@@ -246,7 +243,7 @@ void PlaybackController::StopTimingThread()
m_timingThread.reset();
std::cout << "[PlaybackController] Timing thread stopped" << std::endl;
LOGF_INFO("[PlaybackController] Timing thread stopped");
}
void PlaybackController::TimingThreadLoop()
@@ -270,7 +267,7 @@ void PlaybackController::TimingThreadLoop()
std::this_thread::sleep_until(nextFrame);
}
std::cout << "[PlaybackController] Timing thread loop exited" << std::endl;
LOGF_INFO("[PlaybackController] Timing thread loop exited");
}
} // namespace Vav2Player

View File

@@ -8,6 +8,7 @@
#include <d3dcompiler.h>
#include <cstring>
#include <iostream>
#include "../Logger/SimpleLogger.h"
namespace Vav2Player {
@@ -1332,9 +1333,8 @@ HRESULT D3D12VideoRenderer::CreateRingBuffers(uint32_t yWidth, uint32_t yHeight,
UINT optimalBufferCount = CalculateOptimalBufferCount(yWidth * 2, yHeight); // Use Y plane dimensions
if (optimalBufferCount != m_dynamicRingBufferCount) {
std::cout << "[D3D12VideoRenderer] Adjusting ring buffer count from "
<< m_dynamicRingBufferCount << " to " << optimalBufferCount
<< " for " << yWidth * 2 << "x" << yHeight << " video" << std::endl;
LOGF_INFO("[D3D12VideoRenderer] Adjusting ring buffer count from %d to %d for %dx%d video",
m_dynamicRingBufferCount, optimalBufferCount, yWidth * 2, yHeight);
hr = ResizeRingBuffers(optimalBufferCount, yWidth, yHeight, uvWidth, uvHeight);
if (FAILED(hr)) return hr;
@@ -1666,7 +1666,7 @@ HRESULT D3D12VideoRenderer::ExecuteRingBufferTextureUpdate(uint32_t bufferIndex)
// Check if YUV textures are initialized before creating barriers
if (!m_yTexture || !m_uTexture || !m_vTexture) {
std::cout << "[D3D12VideoRenderer] ERROR: YUV textures not initialized. Skipping ResourceBarrier." << std::endl;
LOGF_ERROR("[D3D12VideoRenderer] YUV textures not initialized. Skipping ResourceBarrier.");
return E_FAIL;
}
@@ -2511,9 +2511,8 @@ UINT D3D12VideoRenderer::CalculateOptimalBufferCount(uint32_t videoWidth, uint32
optimalCount = std::max(MIN_RING_BUFFER_COUNT,
std::min(MAX_RING_BUFFER_COUNT, optimalCount));
std::cout << "[DynamicRingBuffer] Calculated optimal buffer count: " << optimalCount
<< " for " << videoWidth << "x" << videoHeight
<< " (frame size: " << frameSize / 1024 << "KB)" << std::endl;
LOGF_INFO("[DynamicRingBuffer] Calculated optimal buffer count: %d for %dx%d (frame size: %zuKB)",
optimalCount, videoWidth, videoHeight, frameSize / 1024);
return optimalCount;
}
@@ -2543,9 +2542,9 @@ UINT64 D3D12VideoRenderer::GetAvailableVideoMemory()
// Return 80% of dedicated video memory as available
UINT64 availableMemory = static_cast<UINT64>(adapterDesc.DedicatedVideoMemory * 0.8);
std::cout << "[DynamicRingBuffer] Detected video memory: "
<< adapterDesc.DedicatedVideoMemory / (1024 * 1024) << "MB, "
<< "Available: " << availableMemory / (1024 * 1024) << "MB" << std::endl;
LOGF_INFO("[DynamicRingBuffer] Detected video memory: %lluMB, Available: %lluMB",
adapterDesc.DedicatedVideoMemory / (1024 * 1024),
availableMemory / (1024 * 1024));
return availableMemory;
}
@@ -2576,8 +2575,8 @@ HRESULT D3D12VideoRenderer::ResizeRingBuffers(UINT newBufferCount, uint32_t yWid
// Reset current buffer index
m_currentBufferIndex = 0;
std::cout << "[DynamicRingBuffer] Resized ring buffer array to " << newBufferCount
<< " buffers for " << yWidth * 2 << "x" << yHeight << " video" << std::endl;
LOGF_INFO("[DynamicRingBuffer] Resized ring buffer array to %d buffers for %dx%d video",
newBufferCount, yWidth * 2, yHeight);
return S_OK;
}

View File

@@ -1,7 +1,7 @@
#include "pch.h"
#include "SimpleGPURenderer.h"
#include "../Logger/SimpleLogger.h"
#include <d3dcompiler.h>
#include <iostream>
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
@@ -248,7 +248,7 @@ HRESULT SimpleGPURenderer::Present()
HRESULT hr = m_swapChain->Present(1, 0);
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Present failed: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Present failed: 0x%x", hr);
return hr;
}
@@ -272,7 +272,7 @@ HRESULT SimpleGPURenderer::CreateDevice()
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create D3D12 device: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create D3D12 device: 0x%x", hr);
return hr;
}
@@ -288,7 +288,7 @@ HRESULT SimpleGPURenderer::CreateCommandQueue()
HRESULT hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create command queue: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create command queue: 0x%x", hr);
return hr;
}
@@ -352,7 +352,7 @@ HRESULT SimpleGPURenderer::CreateDescriptorHeaps()
HRESULT hr = m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create RTV heap: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create RTV heap: 0x%x", hr);
return hr;
}
@@ -367,7 +367,7 @@ HRESULT SimpleGPURenderer::CreateDescriptorHeaps()
hr = m_device->CreateDescriptorHeap(&srvUavHeapDesc, IID_PPV_ARGS(&m_srvUavHeap));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create SRV/UAV heap: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create SRV/UAV heap: 0x%x", hr);
return hr;
}
@@ -386,7 +386,7 @@ HRESULT SimpleGPURenderer::CreateRenderTargets()
HRESULT hr = m_swapChain->GetBuffer(i, IID_PPV_ARGS(&m_renderTargets[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to get swap chain buffer " << i << ": 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to get swap chain buffer %d: 0x%x", i, hr);
return hr;
}
@@ -403,7 +403,7 @@ HRESULT SimpleGPURenderer::CreateSynchronizationObjects()
HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create fence: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create fence: 0x%x", hr);
return hr;
}
@@ -417,7 +417,7 @@ HRESULT SimpleGPURenderer::CreateSynchronizationObjects()
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocators[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create command allocator " << i << ": 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create command allocator %d: 0x%x", i, hr);
return hr;
}
}
@@ -426,7 +426,7 @@ HRESULT SimpleGPURenderer::CreateSynchronizationObjects()
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocators[0].Get(), nullptr, IID_PPV_ARGS(&m_commandList));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create command list: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create command list: 0x%x", hr);
return hr;
}
@@ -434,7 +434,7 @@ HRESULT SimpleGPURenderer::CreateSynchronizationObjects()
hr = m_commandList->Close();
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to close initial command list: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to close initial command list: 0x%x", hr);
return hr;
}
@@ -478,7 +478,7 @@ HRESULT SimpleGPURenderer::CreateComputeShaderResources()
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_constantBuffer));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create constant buffer: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create constant buffer: 0x%x", hr);
return hr;
}
@@ -517,7 +517,7 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&m_yTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create Y texture " << i << ": 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create Y texture %d: 0x%x", i, hr);
return hr;
}
@@ -528,7 +528,7 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&m_uTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create U texture " << i << ": 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create U texture %d: 0x%x", i, hr);
return hr;
}
@@ -537,7 +537,7 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&m_vTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create V texture " << i << ": 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create V texture %d: 0x%x", i, hr);
return hr;
}
}
@@ -557,7 +557,7 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(&m_rgbTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create RGB texture " << i << ": 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create RGB texture %d: 0x%x", i, hr);
return hr;
}
}
@@ -584,7 +584,7 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_yUploadBuffers[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create Y upload buffer " << i << ": 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create Y upload buffer %d: 0x%x", i, hr);
return hr;
}
@@ -641,7 +641,7 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
}
else
{
std::cout << "[SimpleGPURenderer] Warning: SRV/UAV heap not available for descriptor creation" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Warning: SRV/UAV heap not available for descriptor creation");
}
return S_OK;
@@ -650,7 +650,7 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
HRESULT SimpleGPURenderer::CreateNV12TextureR8Layout(uint32_t videoWidth, uint32_t videoHeight)
{
if (!m_device) {
std::cout << "[SimpleGPURenderer::CreateNV12Texture] ERROR: Device not initialized" << std::endl;
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] ERROR: Device not initialized");
return E_FAIL;
}
@@ -680,38 +680,38 @@ HRESULT SimpleGPURenderer::CreateNV12TextureR8Layout(uint32_t videoWidth, uint32
UINT64 requiredBytes = 0;
m_device->GetCopyableFootprints(&nv12TextureDesc, 0, 2, 0, layouts, numRows, rowSizes, &requiredBytes);
std::cout << "[SimpleGPURenderer::CreateNV12Texture] GetCopyableFootprints requires: "
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] GetCopyableFootprints requires: ");
<< requiredBytes << " bytes" << std::endl;
// Query actual allocation size for this descriptor
D3D12_RESOURCE_ALLOCATION_INFO allocInfo = m_device->GetResourceAllocationInfo(0, 1, &nv12TextureDesc);
std::cout << "[SimpleGPURenderer::CreateNV12Texture] GetResourceAllocationInfo returns: "
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] GetResourceAllocationInfo returns: ");
<< allocInfo.SizeInBytes << " bytes" << std::endl;
// If allocation is too small, increase texture height until we have enough space
if (allocInfo.SizeInBytes < requiredBytes)
{
std::cout << "[SimpleGPURenderer::CreateNV12Texture] Allocation too small, increasing height..." << std::endl;
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] Allocation too small, increasing height...");
for (UINT extraHeight = 64; extraHeight <= videoHeight; extraHeight += 64)
{
nv12TextureDesc.Height = videoHeight + extraHeight;
allocInfo = m_device->GetResourceAllocationInfo(0, 1, &nv12TextureDesc);
std::cout << "[SimpleGPURenderer::CreateNV12Texture] Testing height " << nv12TextureDesc.Height
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] Testing height %d", nv12TextureDesc.Height);
<< ": " << allocInfo.SizeInBytes << " bytes" << std::endl;
if (allocInfo.SizeInBytes >= requiredBytes)
{
std::cout << "[SimpleGPURenderer::CreateNV12Texture] Found sufficient height: "
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] Found sufficient height: ");
<< nv12TextureDesc.Height << std::endl;
break;
}
}
}
std::cout << "[SimpleGPURenderer::CreateNV12Texture] Final NV12 texture: " << videoWidth << "x"
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] Final NV12 texture: %dx", videoWidth);
<< nv12TextureDesc.Height << ", allocation: " << allocInfo.SizeInBytes << " bytes" << std::endl;
D3D12_HEAP_PROPERTIES sharedHeapProps = {};
@@ -728,12 +728,12 @@ HRESULT SimpleGPURenderer::CreateNV12TextureR8Layout(uint32_t videoWidth, uint32
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer::CreateNV12Texture] Failed to create NV12 shared texture: 0x"
LOGF_ERROR("[SimpleGPURenderer::CreateNV12Texture] Failed to create NV12 shared texture: 0x");
<< std::hex << hr << std::endl;
return hr;
}
std::cout << "[SimpleGPURenderer::CreateNV12Texture] Created NV12 shared texture ("
LOGF_INFO("[SimpleGPURenderer::CreateNV12Texture] Created NV12 shared texture (");
<< videoWidth << "x" << videoHeight << ") for VavCore zero-copy decode" << std::endl;
return S_OK;
@@ -824,7 +824,7 @@ float4 PSMain(PSInput input) : SV_TARGET
"VSMain", "vs_5_0", compileFlags, 0, &m_nv12VertexShaderBlob, &errorBlob);
if (FAILED(hr)) {
if (errorBlob) {
std::cout << "[SimpleGPURenderer] NV12 Vertex shader compilation error: "
LOGF_INFO("[SimpleGPURenderer] NV12 Vertex shader compilation error: ");
<< (char*)errorBlob->GetBufferPointer() << std::endl;
}
return hr;
@@ -835,13 +835,13 @@ float4 PSMain(PSInput input) : SV_TARGET
"PSMain", "ps_5_0", compileFlags, 0, &m_nv12PixelShaderBlob, &errorBlob);
if (FAILED(hr)) {
if (errorBlob) {
std::cout << "[SimpleGPURenderer] NV12 Pixel shader compilation error: "
LOGF_INFO("[SimpleGPURenderer] NV12 Pixel shader compilation error: ");
<< (char*)errorBlob->GetBufferPointer() << std::endl;
}
return hr;
}
std::cout << "[SimpleGPURenderer] NV12 shaders compiled successfully" << std::endl;
LOGF_INFO("[SimpleGPURenderer] NV12 shaders compiled successfully");
return S_OK;
}
@@ -900,7 +900,7 @@ HRESULT SimpleGPURenderer::CreateNV12RootSignature()
HRESULT hr = D3D12SerializeVersionedRootSignature(&rootSigDesc, &signature, &error);
if (FAILED(hr)) {
if (error) {
std::cout << "[SimpleGPURenderer] NV12 Root signature serialization error: "
LOGF_INFO("[SimpleGPURenderer] NV12 Root signature serialization error: ");
<< (char*)error->GetBufferPointer() << std::endl;
}
return hr;
@@ -909,7 +909,7 @@ HRESULT SimpleGPURenderer::CreateNV12RootSignature()
hr = m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(),
IID_PPV_ARGS(&m_nv12RootSignature));
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to create NV12 root signature: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create NV12 root signature: 0x%x", hr);
return hr;
}
@@ -935,11 +935,11 @@ HRESULT SimpleGPURenderer::CreateNV12PipelineState()
HRESULT hr = m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_nv12PipelineState));
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to create NV12 pipeline state: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create NV12 pipeline state: 0x%x", hr);
return hr;
}
std::cout << "[SimpleGPURenderer] NV12 graphics pipeline created successfully" << std::endl;
LOGF_INFO("[SimpleGPURenderer] NV12 graphics pipeline created successfully");
return S_OK;
}
@@ -953,7 +953,7 @@ HRESULT SimpleGPURenderer::CreateNV12SrvHeap()
HRESULT hr = m_device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&m_nv12SrvHeap));
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to create NV12 SRV heap: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create NV12 SRV heap: 0x%x", hr);
return hr;
}
@@ -1005,19 +1005,19 @@ HRESULT SimpleGPURenderer::CreateNV12GraphicsPipeline()
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create NV12 constant buffer: 0x"
LOGF_ERROR("[SimpleGPURenderer] Failed to create NV12 constant buffer: 0x");
<< std::hex << hr << std::endl;
return hr;
}
std::cout << "[SimpleGPURenderer] NV12 graphics pipeline initialized successfully" << std::endl;
LOGF_INFO("[SimpleGPURenderer] NV12 graphics pipeline initialized successfully");
return S_OK;
}
HRESULT SimpleGPURenderer::RenderNV12TextureToBackBuffer(uint64_t fenceValue)
{
if (!m_nv12Texture || !m_initialized) {
std::cout << "[SimpleGPURenderer::RenderNV12Texture] ERROR: NV12 texture or renderer not initialized" << std::endl;
LOGF_INFO("[SimpleGPURenderer::RenderNV12Texture] ERROR: NV12 texture or renderer not initialized");
return E_FAIL;
}
@@ -1035,7 +1035,7 @@ HRESULT SimpleGPURenderer::RenderNV12TextureToBackBuffer(uint64_t fenceValue)
char buf[256];
sprintf_s(buf, "[SimpleGPURenderer::RenderNV12Texture] Failed to create NV12 graphics pipeline: HRESULT = 0x%08X\n", hr);
OutputDebugStringA(buf);
std::cout << "[SimpleGPURenderer::RenderNV12Texture] Failed to create NV12 graphics pipeline" << std::endl;
LOGF_INFO("[SimpleGPURenderer::RenderNV12Texture] Failed to create NV12 graphics pipeline");
return hr;
}
OutputDebugStringA("[SimpleGPURenderer::RenderNV12Texture] NV12 graphics pipeline created successfully\n");
@@ -1065,7 +1065,7 @@ HRESULT SimpleGPURenderer::RenderNV12TextureToBackBuffer(uint64_t fenceValue)
m_device->CreateShaderResourceView(m_nv12Texture.Get(), &uvPlaneSrvDesc, srvHandle);
OutputDebugStringA("[SimpleGPURenderer::RenderNV12Texture] NV12 SRVs created (Y + UV planes)\n");
std::cout << "[SimpleGPURenderer::RenderNV12Texture] NV12 native format SRVs created (Y + UV planes)" << std::endl;
LOGF_INFO("[SimpleGPURenderer::RenderNV12Texture] NV12 native format SRVs created (Y + UV planes)");
}
// CRITICAL: Do NOT wait for current frame - this blocks UI thread!
@@ -1196,7 +1196,7 @@ HRESULT SimpleGPURenderer::RenderNV12TextureToBackBuffer(uint64_t fenceValue)
char buf[256];
sprintf_s(buf, "[SimpleGPURenderer::RenderNV12Texture] Present failed: HRESULT = 0x%08X\n", hr);
OutputDebugStringA(buf);
std::cout << "[SimpleGPURenderer::RenderNV12Texture] Present failed: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer::RenderNV12Texture] Present failed: 0x%x", hr);
return hr;
}
OutputDebugStringA("[SimpleGPURenderer::RenderNV12Texture] Present succeeded\n");
@@ -1218,13 +1218,13 @@ HRESULT SimpleGPURenderer::ExecuteGPUPipeline(const VavCoreVideoFrame& frame)
// Step 1: Reset command allocator and list ONCE per frame
hr = m_commandAllocators[m_frameIndex]->Reset();
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to reset command allocator: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to reset command allocator: 0x%x", hr);
return hr;
}
hr = m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), nullptr);
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to reset command list: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to reset command list: 0x%x", hr);
return hr;
}
@@ -1244,7 +1244,7 @@ HRESULT SimpleGPURenderer::ExecuteGPUPipeline(const VavCoreVideoFrame& frame)
// Step 5: Close and execute command list ONCE
hr = m_commandList->Close();
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to close command list: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to close command list: 0x%x", hr);
return hr;
}
@@ -1280,7 +1280,7 @@ HRESULT SimpleGPURenderer::UpdateVideoTexturesInternal(const VavCoreVideoFrame&
// Validate device and buffer state
if (!m_device) {
std::cout << "[SimpleGPURenderer] D3D12 device is null" << std::endl;
LOGF_INFO("[SimpleGPURenderer] D3D12 device is null");
return E_FAIL;
}
@@ -1298,7 +1298,7 @@ HRESULT SimpleGPURenderer::UpdateVideoTexturesInternal(const VavCoreVideoFrame&
// Additional safety: Check if upload buffer is valid before mapping
if (!yUploadBuffer)
{
std::cout << "[SimpleGPURenderer] Y upload buffer is null!" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Y upload buffer is null!");
return E_FAIL;
}
@@ -1307,20 +1307,20 @@ HRESULT SimpleGPURenderer::UpdateVideoTexturesInternal(const VavCoreVideoFrame&
hr = yUploadBuffer->Map(0, &readRange, &yMappedData);
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to map Y upload buffer: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to map Y upload buffer: 0x%x", hr);
return hr;
}
if (!yMappedData)
{
std::cout << "[SimpleGPURenderer] Y upload buffer mapping returned null pointer!" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Y upload buffer mapping returned null pointer!");
yUploadBuffer->Unmap(0, nullptr);
return E_FAIL;
}
// Validate frame data
if (!frame.y_plane || frame.width == 0 || frame.height == 0) {
std::cout << "[SimpleGPURenderer] Invalid frame data" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Invalid frame data");
yUploadBuffer->Unmap(0, nullptr);
return E_FAIL;
}
@@ -1455,7 +1455,7 @@ HRESULT SimpleGPURenderer::ExecuteComputeShaderInternal()
{
if (!m_computePipelineState || !m_computeRootSignature)
{
std::cout << "[SimpleGPURenderer] Compute pipeline not ready" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Compute pipeline not ready");
return E_FAIL;
}
@@ -1618,7 +1618,7 @@ HRESULT SimpleGPURenderer::CreateComputeRootSignature()
{
if (error)
{
std::cout << "[SimpleGPURenderer] Root signature serialization error: "
LOGF_INFO("[SimpleGPURenderer] Root signature serialization error: ");
<< (char*)error->GetBufferPointer() << std::endl;
}
return hr;
@@ -1628,7 +1628,7 @@ HRESULT SimpleGPURenderer::CreateComputeRootSignature()
IID_PPV_ARGS(&m_computeRootSignature));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create compute root signature: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create compute root signature: 0x%x", hr);
return hr;
}
@@ -1716,7 +1716,7 @@ void main(uint3 id : SV_DispatchThreadID)
{
if (errorBlob)
{
std::cout << "[SimpleGPURenderer] Shader compilation failed: "
LOGF_ERROR("[SimpleGPURenderer] Shader compilation failed: ");
<< (char*)errorBlob->GetBufferPointer() << std::endl;
}
return hr;
@@ -1732,7 +1732,7 @@ HRESULT SimpleGPURenderer::CreateComputePipelineState()
{
if (!m_computeShaderBlob)
{
std::cout << "[SimpleGPURenderer] Compute shader blob not available" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Compute shader blob not available");
return E_FAIL;
}
@@ -1745,7 +1745,7 @@ HRESULT SimpleGPURenderer::CreateComputePipelineState()
HRESULT hr = m_device->CreateComputePipelineState(&psoDesc, IID_PPV_ARGS(&m_computePipelineState));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create compute pipeline state: 0x"
LOGF_ERROR("[SimpleGPURenderer] Failed to create compute pipeline state: 0x");
<< std::hex << hr << std::endl;
return hr;
}
@@ -1781,7 +1781,7 @@ HRESULT SimpleGPURenderer::CreateGraphicsShaderResources()
hr = m_device->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&m_graphicsSrvHeap));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create graphics SRV heap" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Failed to create graphics SRV heap");
return hr;
}
@@ -1795,7 +1795,7 @@ HRESULT SimpleGPURenderer::CreateGraphicsShaderResources()
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create AspectFit constant buffer" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Failed to create AspectFit constant buffer");
return hr;
}
@@ -1896,7 +1896,7 @@ float4 PSMain(VSOutput input) : SV_TARGET
if (FAILED(hr))
{
if (errorBlob)
std::cout << "[SimpleGPURenderer] VS compilation failed: " << (char*)errorBlob->GetBufferPointer() << std::endl;
LOGF_ERROR("[SimpleGPURenderer] VS compilation failed: %d", (char*)errorBlob->GetBufferPointer());
return hr;
}
@@ -1908,7 +1908,7 @@ float4 PSMain(VSOutput input) : SV_TARGET
if (FAILED(hr))
{
if (errorBlob)
std::cout << "[SimpleGPURenderer] PS compilation failed: " << (char*)errorBlob->GetBufferPointer() << std::endl;
LOGF_ERROR("[SimpleGPURenderer] PS compilation failed: %d", (char*)errorBlob->GetBufferPointer());
return hr;
}
@@ -1967,7 +1967,7 @@ HRESULT SimpleGPURenderer::CreateGraphicsRootSignature()
if (FAILED(hr))
{
if (error)
std::cout << "[SimpleGPURenderer] Root signature serialization failed: " << (char*)error->GetBufferPointer() << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Root signature serialization failed: %d", (char*)error->GetBufferPointer());
return hr;
}
@@ -1976,7 +1976,7 @@ HRESULT SimpleGPURenderer::CreateGraphicsRootSignature()
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create graphics root signature" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Failed to create graphics root signature");
return hr;
}
@@ -1987,7 +1987,7 @@ HRESULT SimpleGPURenderer::CreateGraphicsPipelineState()
{
if (!m_vertexShaderBlob || !m_pixelShaderBlob)
{
std::cout << "[SimpleGPURenderer] Graphics shader blobs not available" << std::endl;
LOGF_INFO("[SimpleGPURenderer] Graphics shader blobs not available");
return E_FAIL;
}
@@ -2025,7 +2025,7 @@ HRESULT SimpleGPURenderer::CreateGraphicsPipelineState()
HRESULT hr = m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_graphicsPipelineState));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create graphics pipeline state: 0x" << std::hex << hr << std::endl;
LOGF_ERROR("[SimpleGPURenderer] Failed to create graphics pipeline state: 0x%x", hr);
return hr;
}

View File

@@ -913,7 +913,9 @@ bool NVDECAV1Decoder::DecodeToSurface(const uint8_t* packet_data, size_t packet_
}
// CRITICAL: Set CUDA context for current thread (may be background thread)
// Without this, cuvidDecodePicture will fail when called from non-main thread
// Thread safety: Lock mutex to prevent context conflicts in multi-threaded scenarios
std::lock_guard<std::mutex> contextLock(m_cudaContextMutex);
CUresult ctxResult = cuCtxSetCurrent(m_cuContext);
if (ctxResult != CUDA_SUCCESS) {
sprintf_s(debug_buf, "[NVDECAV1Decoder::DecodeToSurface] ERROR: cuCtxSetCurrent failed with code %d\n", ctxResult);
@@ -999,13 +1001,25 @@ bool NVDECAV1Decoder::DecodeToSurface(const uint8_t* packet_data, size_t packet_
return false;
}
// Create D3D12 surface handler on-demand
if (!m_d3d12Handler) {
// Create D3D12 surface handler on-demand, or recreate if device/context changed
if (!m_d3d12Handler ||
m_cachedD3D12Device != m_d3d12Device ||
m_cachedCuContext != m_cuContext) {
// Release old handler if exists (cleans up external memory cache)
m_d3d12Handler.reset();
// Create new handler with current device/context
m_d3d12Handler = std::make_unique<D3D12SurfaceHandler>(
static_cast<ID3D12Device*>(m_d3d12Device),
m_cuContext
);
OutputDebugStringA("[NVDECAV1Decoder::DecodeToSurface] D3D12SurfaceHandler created\n");
// Update cache
m_cachedD3D12Device = m_d3d12Device;
m_cachedCuContext = m_cuContext;
OutputDebugStringA("[NVDECAV1Decoder::DecodeToSurface] D3D12SurfaceHandler (re)created with current device/context\n");
}
// Wait for decoded frame to be available (blocks until callback completes)

View File

@@ -127,6 +127,13 @@ private:
std::mutex m_frameQueueMutex;
std::condition_variable m_frameAvailable; // Notify when frame is ready
// CUDA context thread safety
std::mutex m_cudaContextMutex; // Protects cuCtxSetCurrent calls
// D3D12SurfaceHandler recreation tracking
void* m_cachedD3D12Device = nullptr; // Last used D3D12 device
CUcontext m_cachedCuContext = nullptr; // Last used CUDA context
// Helper methods
bool CheckCUDACapability();
bool CreateDecoder();

View File

@@ -697,17 +697,25 @@ VAVCORE_API VavCoreResult vavcore_set_d3d_device(VavCorePlayer* player, void* d3
return VAVCORE_ERROR_INVALID_PARAM;
}
// If decoder doesn't exist yet, store for later use
if (!player->impl->decoder) {
player->impl->pendingD3DDevice = d3d_device;
player->impl->pendingD3DSurfaceType = type;
OutputDebugStringA("[vavcore_set_d3d_device] Decoder not created yet, storing D3D device for later\n");
// Always store for pending use (in case decoder is recreated)
player->impl->pendingD3DDevice = d3d_device;
player->impl->pendingD3DSurfaceType = type;
// If decoder exists, also apply immediately
if (player->impl->decoder) {
bool success = player->impl->decoder->SetD3DDevice(d3d_device, type);
if (success) {
OutputDebugStringA("[vavcore_set_d3d_device] D3D device applied to existing decoder\n");
return VAVCORE_SUCCESS;
} else {
OutputDebugStringA("[vavcore_set_d3d_device] WARNING: Failed to apply D3D device to existing decoder (will retry on next decode)\n");
// Still return success - device is stored for later use
return VAVCORE_SUCCESS;
}
} else {
OutputDebugStringA("[vavcore_set_d3d_device] Decoder not created yet, D3D device stored for later\n");
return VAVCORE_SUCCESS;
}
// If decoder exists, set it immediately
bool success = player->impl->decoder->SetD3DDevice(d3d_device, type);
return success ? VAVCORE_SUCCESS : VAVCORE_ERROR_NOT_SUPPORTED;
}
VAVCORE_API void* vavcore_get_sync_fence(VavCorePlayer* player) {