diff --git a/vav2/platforms/android/vavcore/CMakeLists.txt b/vav2/platforms/android/vavcore/CMakeLists.txt index bbcb517..fe973a2 100644 --- a/vav2/platforms/android/vavcore/CMakeLists.txt +++ b/vav2/platforms/android/vavcore/CMakeLists.txt @@ -61,7 +61,7 @@ endif() # Common source files (cross-platform) - no PCH for Android set(VAVCORE_COMMON_SOURCES ${VAVCORE_ROOT}/src/Decoder/VideoDecoderFactory.cpp - ${VAVCORE_ROOT}/src/VavCore.cpp + ${VAVCORE_ROOT}/src/VavCore_Android.cpp ) # Android-specific source files diff --git a/vav2/platforms/windows/tests/headless/Vav2PlayerHeadless.vcxproj b/vav2/platforms/windows/tests/headless/Vav2PlayerHeadless.vcxproj index 66f88ae..3ca67fe 100644 --- a/vav2/platforms/windows/tests/headless/Vav2PlayerHeadless.vcxproj +++ b/vav2/platforms/windows/tests/headless/Vav2PlayerHeadless.vcxproj @@ -60,7 +60,7 @@ Console true - $(ProjectDir)..\..\vavcore\lib;$(ProjectDir)..\..\..\..\..\lib\libwebm;$(ProjectDir)..\..\..\..\..\lib\dav1d;$(ProjectDir)..\..\..\..\..\lib\amf;$(ProjectDir)..\..\..\..\..\lib\libvpl;$(ProjectDir)..\..\..\..\..\oss\nvidia-video-codec\Lib\x64;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0\lib\x64 + $(ProjectDir)..\..\vavcore\lib;$(ProjectDir)..\..\..\..\..\lib\windows-x64\libwebm;$(ProjectDir)..\..\..\..\..\lib\windows-x64\dav1d;$(ProjectDir)..\..\..\..\..\lib\windows-x64\amf;$(ProjectDir)..\..\..\..\..\lib\windows-x64\libvpl;$(ProjectDir)..\..\..\..\..\oss\nvidia-video-codec\Lib\x64;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0\lib\x64 VavCore-debug.lib;webm-debug.lib;dav1d-debug.lib;amf-debug.lib;vpld.lib;nvcuvid.lib;cuda.lib;mfplat.lib;mf.lib;mfuuid.lib;d3d11.lib;d3d12.lib;dxgi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -87,7 +87,7 @@ echo VavCore Debug DLL copy completed. true true true - $(ProjectDir)..\..\vavcore\lib;$(ProjectDir)..\..\..\..\..\lib\libwebm;$(ProjectDir)..\..\..\..\..\lib\dav1d;$(ProjectDir)..\..\..\..\..\lib\amf;$(ProjectDir)..\..\..\..\..\lib\libvpl;$(ProjectDir)..\..\..\..\..\oss\nvidia-video-codec\Lib\x64;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0\lib\x64 + $(ProjectDir)..\..\vavcore\lib;$(ProjectDir)..\..\..\..\..\lib\windows-x64\libwebm;$(ProjectDir)..\..\..\..\..\lib\windows-x64\dav1d;$(ProjectDir)..\..\..\..\..\lib\windows-x64\amf;$(ProjectDir)..\..\..\..\..\lib\windows-x64\libvpl;$(ProjectDir)..\..\..\..\..\oss\nvidia-video-codec\Lib\x64;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0\lib\x64 VavCore.lib;webm.lib;dav1d.lib;amf.lib;vpl.lib;nvcuvid.lib;cuda.lib;mfplat.lib;mf.lib;mfuuid.lib;d3d11.lib;d3d12.lib;dxgi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) diff --git a/vav2/platforms/windows/vavcore/VavCore.vcxproj b/vav2/platforms/windows/vavcore/VavCore.vcxproj index d834cb5..cf7e3b2 100644 --- a/vav2/platforms/windows/vavcore/VavCore.vcxproj +++ b/vav2/platforms/windows/vavcore/VavCore.vcxproj @@ -142,7 +142,7 @@ Create Create - + diff --git a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSurfaceManager.cpp b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSurfaceManager.cpp index e3dcda2..138c41d 100644 --- a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSurfaceManager.cpp +++ b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSurfaceManager.cpp @@ -388,18 +388,10 @@ bool MediaCodecSurfaceManager::CreateVulkanImage(void* vk_device, void* vk_insta ycbcrConversionCreateInfo.format = vulkan_format; - // DIAGNOSTIC: Try BT.601 instead of BT.709 for NV21 devices - // Qualcomm devices may expect BT.601 color matrix - if (is_nv21_device) { - ycbcrConversionCreateInfo.ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601; // BT.601 - ycbcrConversionCreateInfo.ycbcrRange = VK_SAMPLER_YCBCR_RANGE_ITU_NARROW; // Limited range - LogInfo(" Using BT.601 + ITU_NARROW for NV21 device"); - } else { - // Use MediaCodec suggested settings - ycbcrConversionCreateInfo.ycbcrModel = static_cast(ahb_format_props.suggestedYcbcrModel); - ycbcrConversionCreateInfo.ycbcrRange = static_cast(ahb_format_props.suggestedYcbcrRange); - LogInfo(" Using MediaCodec suggested YCbCr model and range"); - } + // Use MediaCodec suggested YCbCr model and range for all devices + ycbcrConversionCreateInfo.ycbcrModel = static_cast(ahb_format_props.suggestedYcbcrModel); + ycbcrConversionCreateInfo.ycbcrRange = static_cast(ahb_format_props.suggestedYcbcrRange); + LogInfo(" Using MediaCodec suggested YCbCr model and range"); // Log color space settings LogInfo("YCbCr conversion:"); @@ -414,28 +406,25 @@ bool MediaCodecSurfaceManager::CreateVulkanImage(void* vk_device, void* vk_insta LogInfo(" b: " + std::to_string(ahb_format_props.samplerYcbcrConversionComponents.b)); LogInfo(" a: " + std::to_string(ahb_format_props.samplerYcbcrConversionComponents.a)); - // Use already-detected is_nv21_device from above - if (is_nv21_device || ahb_format_props.samplerYcbcrConversionComponents.r == 0) { - // NV21 device: Need to swap Cb/Cr which appear as G/B in conversion output - // In YCbCr conversion, Cb maps to B-Y and Cr maps to R-Y - // For NV21 (CrCb), we get the order reversed - ycbcrConversionCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - ycbcrConversionCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - ycbcrConversionCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - ycbcrConversionCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - LogInfo(" Using IDENTITY components (will use BT.601 instead of BT.709)"); + // Qualcomm Adreno GPU requires component swizzle because it outputs NV21 (CrCb) + // but VkFormat expects NV12 (CbCr) + if (is_qualcomm_gpu) { + ycbcrConversionCreateInfo.components.r = VK_COMPONENT_SWIZZLE_B; // R (Cr) from B position + ycbcrConversionCreateInfo.components.g = VK_COMPONENT_SWIZZLE_G; // G (Y) unchanged + ycbcrConversionCreateInfo.components.b = VK_COMPONENT_SWIZZLE_R; // B (Cb) from R position + ycbcrConversionCreateInfo.components.a = VK_COMPONENT_SWIZZLE_A; // A unchanged + LogInfo(" Using SWAPPED components (R<-B, B<-R) for Adreno NV21"); } else { - // Use MediaCodec suggested component mapping ycbcrConversionCreateInfo.components = ahb_format_props.samplerYcbcrConversionComponents; - LogInfo(" Using MediaCodec suggested component mapping"); + LogInfo(" Using MediaCodec suggested components"); } - // DIAGNOSTIC: Override with MIDPOINT chroma offset (standard for JPEG/MPEG) - ycbcrConversionCreateInfo.xChromaOffset = VK_CHROMA_LOCATION_MIDPOINT; - ycbcrConversionCreateInfo.yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT; + // Use MediaCodec suggested chroma offset (DON'T override!) + ycbcrConversionCreateInfo.xChromaOffset = static_cast(ahb_format_props.suggestedXChromaOffset); + ycbcrConversionCreateInfo.yChromaOffset = static_cast(ahb_format_props.suggestedYChromaOffset); LogInfo(" MediaCodec suggested xChromaOffset: " + std::to_string(ahb_format_props.suggestedXChromaOffset)); LogInfo(" MediaCodec suggested yChromaOffset: " + std::to_string(ahb_format_props.suggestedYChromaOffset)); - LogInfo(" Overriding with MIDPOINT (0) chroma offset"); + LogInfo(" Using MediaCodec suggested chroma offset"); ycbcrConversionCreateInfo.chromaFilter = VK_FILTER_LINEAR; ycbcrConversionCreateInfo.forceExplicitReconstruction = VK_FALSE; diff --git a/vav2/platforms/windows/vavcore/src/VavCore.cpp b/vav2/platforms/windows/vavcore/src/VavCore_Android.cpp similarity index 72% rename from vav2/platforms/windows/vavcore/src/VavCore.cpp rename to vav2/platforms/windows/vavcore/src/VavCore_Android.cpp index 2d387d3..dc13474 100644 --- a/vav2/platforms/windows/vavcore/src/VavCore.cpp +++ b/vav2/platforms/windows/vavcore/src/VavCore_Android.cpp @@ -1,7 +1,10 @@ +// VavCore_Android_Full.cpp - Complete Android implementation of VavCore C API +// All platform-specific code consolidated in this file + #include "pch.h" #include "VavCore/VavCore.h" -#include "Common/VideoTypes.h" // Internal VavCore types -#include "Common/AdaptiveTypes.h" // Adaptive types +#include "Common/VideoTypes.h" +#include "Common/AdaptiveTypes.h" #include "Decoder/IVideoDecoder.h" #include "Decoder/VideoDecoderFactory.h" #include "FileIO/WebMFileReader.h" @@ -13,59 +16,42 @@ #include #ifdef ANDROID -#include // For JNI functions and types -#endif +#include // Use VavCore namespace internally using namespace VavCore; -// Forward declarations for DllMain-based initialization -extern "C" bool PerformSafeDllInitialization(); -extern "C" bool IsDllReadyForInitialization(); - -// Forward declaration for Android JavaVM access -#ifdef ANDROID -namespace VavCore { - JavaVM* GetAndroidJavaVM(); -} -#endif - // Global state static bool g_initialized = false; static bool g_jni_loaded = false; static std::mutex g_mutex; - -#ifdef ANDROID -static JavaVM* g_android_java_vm = nullptr; // Global JavaVM for Android JNI operations +static JavaVM* g_android_java_vm = nullptr; // Android JNI initialization - equivalent to DllMain for lazy loading JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { std::lock_guard lock(g_mutex); - g_android_java_vm = vm; // Store JavaVM for later use + g_android_java_vm = vm; g_jni_loaded = true; + LOGF_INFO("[VavCore Android] JNI_OnLoad: JavaVM registered at %p", vm); return JNI_VERSION_1_6; } JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { std::lock_guard lock(g_mutex); - // Perform cleanup if initialized if (g_initialized) { - // Note: We can't call vavcore_cleanup() here as it might not be safe - // The cleanup should be handled by the application calling vavcore_cleanup() g_initialized = false; } + LOGF_INFO("[VavCore Android] JNI_OnUnload: JavaVM=%p", vm); g_jni_loaded = false; } // Internal function to get JavaVM for use by MediaCodec decoders -// Defined here so it's available before first use namespace VavCore { JavaVM* GetAndroidJavaVM() { std::lock_guard lock(g_mutex); - // DEBUG: Log every call to GetAndroidJavaVM() LOGF_INFO("[GetAndroidJavaVM] Called - g_android_java_vm = %p", g_android_java_vm); LOGF_INFO("[GetAndroidJavaVM] g_jni_loaded = %d", g_jni_loaded); @@ -83,7 +69,6 @@ namespace VavCore { static bool IsAndroidLibraryReady() { return g_jni_loaded; } -#endif // Error message mapping static const char* get_error_message(VavCoreResult result) { @@ -104,7 +89,7 @@ class VavCorePlayerImpl; // C-compatible player structure (pimpl pattern) struct VavCorePlayer { - VavCorePlayerImpl* impl; // Opaque pointer to C++ implementation + VavCorePlayerImpl* impl; }; // C++ implementation class (hidden from C API) @@ -120,11 +105,7 @@ public: double currentTimeSeconds; std::string decoderName; - // Store D3D device before decoder creation - void* pendingD3DDevice; - VavCoreSurfaceType pendingD3DSurfaceType; - - // Store Vulkan device before decoder creation + // Store Vulkan device before decoder creation (Android-specific) void* vulkan_device; void* vulkan_instance; void* vulkan_physical_device; @@ -132,7 +113,7 @@ public: // Debug options VavCoreDebugOptions debugOptions; - std::string debugOutputPath; // Owned copy of debug_output_path + std::string debugOutputPath; VavCorePlayerImpl() : qualityMode(VAVCORE_QUALITY_CONSERVATIVE) @@ -141,8 +122,6 @@ public: , currentFrame(0) , currentTimeSeconds(0.0) , decoderName("unknown") - , pendingD3DDevice(nullptr) - , pendingD3DSurfaceType(VAVCORE_SURFACE_CPU) , vulkan_device(nullptr) , vulkan_instance(nullptr) , vulkan_physical_device(nullptr) @@ -151,7 +130,6 @@ public: { fileReader = std::make_unique(); - // Initialize debug options with defaults debugOptions.enable_first_frame_debug = false; debugOptions.first_frame_debug_count = 3; debugOptions.enable_rgba_debug = false; @@ -164,7 +142,6 @@ public: } void close_internal() { - // Clean up decoder and file reader if (decoder) { decoder->Cleanup(); decoder.reset(); @@ -176,7 +153,6 @@ public: currentFrame = 0; currentTimeSeconds = 0.0; } - }; // Convert internal quality mode to adaptive quality mode @@ -194,10 +170,7 @@ static VavCore::VideoDecoderFactory::DecoderType to_decoder_type(VavCoreDecoderT switch (type) { case VAVCORE_DECODER_AUTO: return VavCore::VideoDecoderFactory::DecoderType::AUTO; case VAVCORE_DECODER_DAV1D: return VavCore::VideoDecoderFactory::DecoderType::DAV1D; - case VAVCORE_DECODER_NVDEC: return VavCore::VideoDecoderFactory::DecoderType::NVDEC; - case VAVCORE_DECODER_MEDIA_FOUNDATION: return VavCore::VideoDecoderFactory::DecoderType::MEDIA_FOUNDATION; - case VAVCORE_DECODER_VPL: return VavCore::VideoDecoderFactory::DecoderType::VPL; - case VAVCORE_DECODER_AMF: return VavCore::VideoDecoderFactory::DecoderType::AMF; + case VAVCORE_DECODER_MEDIACODEC: return VavCore::VideoDecoderFactory::DecoderType::MEDIACODEC; default: return VavCore::VideoDecoderFactory::DecoderType::AUTO; } } @@ -208,25 +181,20 @@ static void copy_frame_data(const VideoFrame& src, VavCoreVideoFrame* dst) { dst->width = src.width; dst->height = src.height; - dst->timestamp_us = static_cast(src.timestamp_seconds * 1000000.0); // Convert seconds to microseconds + dst->timestamp_us = static_cast(src.timestamp_seconds * 1000000.0); dst->frame_number = src.frame_index; - - // Set default surface type to CPU dst->surface_type = VAVCORE_SURFACE_CPU; - // Use actual plane sizes from source frame size_t y_size = src.y_size; size_t u_size = src.u_size; size_t v_size = src.v_size; - // Allocate memory for frame data dst->y_plane = static_cast(malloc(y_size)); dst->u_plane = static_cast(malloc(u_size)); dst->v_plane = static_cast(malloc(v_size)); if (dst->y_plane && dst->u_plane && dst->v_plane && src.y_plane && src.u_plane && src.v_plane) { - // Copy frame data from individual planes memcpy(dst->y_plane, src.y_plane.get(), y_size); memcpy(dst->u_plane, src.u_plane.get(), u_size); memcpy(dst->v_plane, src.v_plane.get(), v_size); @@ -236,7 +204,6 @@ static void copy_frame_data(const VideoFrame& src, VavCoreVideoFrame* dst) { dst->u_stride = src.u_stride; dst->v_stride = src.v_stride; - // Initialize CPU surface data for backward compatibility dst->surface_data.cpu.planes[0] = dst->y_plane; dst->surface_data.cpu.planes[1] = dst->u_plane; dst->surface_data.cpu.planes[2] = dst->v_plane; @@ -245,7 +212,10 @@ static void copy_frame_data(const VideoFrame& src, VavCoreVideoFrame* dst) { dst->surface_data.cpu.strides[2] = dst->v_stride; } -// API Implementation +// ============================================================================ +// C API Implementation - Android Platform +// ============================================================================ + extern "C" { VAVCORE_API VavCoreResult vavcore_initialize(void) { @@ -255,30 +225,16 @@ VAVCORE_API VavCoreResult vavcore_initialize(void) { return VAVCORE_SUCCESS; } -#ifndef ANDROID - // Check if DLL is ready for safe initialization - if (!IsDllReadyForInitialization()) { - return VAVCORE_ERROR_INIT_FAILED; - } - - // Perform safe DLL-level initialization - if (!PerformSafeDllInitialization()) { - return VAVCORE_ERROR_INIT_FAILED; - } -#else - // Android: Check if JNI library is ready + // Android-specific: Check if JNI library is ready if (!IsAndroidLibraryReady()) { + LOGF_ERROR("[VavCore Android] JNI not loaded, cannot initialize"); return VAVCORE_ERROR_INIT_FAILED; } -#endif - - // Initialize decoder factory - // Note: InitializeFactory() handles platform-specific decoder registration internally - // - Android: Explicitly calls RegisterMediaCodecDecoders() due to JNI initialization order - // - Windows: Uses static initialization for decoder registration + // Initialize decoder factory (Android explicitly calls RegisterMediaCodecDecoders) VideoDecoderFactory::InitializeFactory(); g_initialized = true; + LOGF_INFO("[VavCore Android] Initialization complete (JavaVM=%p)", g_android_java_vm); return VAVCORE_SUCCESS; } @@ -286,8 +242,8 @@ VAVCORE_API void vavcore_cleanup(void) { std::lock_guard lock(g_mutex); if (g_initialized) { - // Cleanup subsystems g_initialized = false; + LOGF_INFO("[VavCore Android] Cleanup complete"); } } @@ -311,7 +267,6 @@ VAVCORE_API VavCorePlayer* vavcore_create_player(void) { VavCorePlayer* player = new VavCorePlayer(); player->impl = new VavCorePlayerImpl(); - // Verify fileReader was created successfully if (!player->impl->fileReader) { delete player->impl; delete player; @@ -338,16 +293,13 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f return VAVCORE_ERROR_INVALID_PARAM; } - // Verify fileReader exists before proceeding if (!player->impl->fileReader) { return VAVCORE_ERROR_INIT_FAILED; } try { - // Debug log LOGF_DEBUG("[VavCore] Opening file: %s", filepath); - // Open file with WebM reader if (!player->impl->fileReader->OpenFile(filepath)) { LOGF_DEBUG("[VavCore] OpenFile() returned false"); return VAVCORE_ERROR_FILE_NOT_FOUND; @@ -355,9 +307,7 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f LOGF_DEBUG("[VavCore] OpenFile() succeeded"); - // Get video tracks and select the first AV1 track auto tracks = player->impl->fileReader->GetVideoTracks(); - LOGF_DEBUG("[VavCore] Found %zu video tracks", tracks.size()); bool foundAV1 = false; @@ -369,7 +319,6 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f LOGF_DEBUG("[VavCore] AV1 track found! Selecting track..."); if (player->impl->fileReader->SelectVideoTrack(track.track_number)) { LOGF_DEBUG("[VavCore] Track selected successfully"); - // Get full metadata from WebMFileReader (includes codec_private_data) player->impl->metadata = player->impl->fileReader->GetVideoMetadata(); foundAV1 = true; break; @@ -383,11 +332,10 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f return VAVCORE_ERROR_NOT_SUPPORTED; } - // Create appropriate decoder LOGF_DEBUG("[VavCore] Creating decoder..."); auto decoderType = to_decoder_type(player->impl->decoderType); - LOGF_DEBUG("[VavCore] Decoder type requested: %d (0=AUTO, 1=NVDEC, 2=VPL, 3=AMF, 4=DAV1D, 5=MF, 6=MEDIACODEC)", + LOGF_DEBUG("[VavCore] Decoder type requested: %d (0=AUTO, 4=DAV1D, 6=MEDIACODEC)", static_cast(decoderType)); player->impl->decoder = VavCore::VideoDecoderFactory::CreateDecoder(VavCore::VideoCodecType::AV1, decoderType); @@ -400,29 +348,13 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f LOGF_DEBUG("[VavCore] Decoder created successfully."); - // Apply pending D3D device if it was set before decoder creation - if (player->impl->pendingD3DDevice) { - LOGF_DEBUG("[VavCore] Applying pending D3D device before decoder initialization..."); - LOGF_DEBUG("[VavCore] Pending D3D device: %p, Type: %d", - player->impl->pendingD3DDevice, static_cast(player->impl->pendingD3DSurfaceType)); - - player->impl->decoder->SetD3DDevice(player->impl->pendingD3DDevice, player->impl->pendingD3DSurfaceType); - - // Clear pending device after applying - player->impl->pendingD3DDevice = nullptr; - player->impl->pendingD3DSurfaceType = VAVCORE_SURFACE_CPU; - } - -#ifdef ANDROID - // CRITICAL: Apply Vulkan device BEFORE decoder initialization - // This allows MediaCodec to be created with ImageReader surface from the start + // Android-specific: Apply pending Vulkan device BEFORE decoder initialization if (player->impl->has_vulkan_device) { LOGF_DEBUG("[VavCore] Applying pending Vulkan device BEFORE decoder initialization..."); LOGF_DEBUG("[VavCore] Vulkan device: %p, instance: %p, physical device: %p", player->impl->vulkan_device, player->impl->vulkan_instance, player->impl->vulkan_physical_device); // Pre-check: Vulkan device requires JavaVM for ImageReader initialization - // If JavaVM is not available, decoder initialization will 100% fail JavaVM* javaVM = VavCore::GetAndroidJavaVM(); if (!javaVM) { LOGF_ERROR("[VavCore] CRITICAL: Vulkan device set but JavaVM unavailable!"); @@ -449,11 +381,9 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f return VAVCORE_ERROR_INIT_FAILED; } } -#endif LOGF_DEBUG("[VavCore] Initializing decoder..."); - // Initialize decoder (now with Vulkan device already set!) if (!player->impl->decoder->Initialize(player->impl->metadata)) { LOGF_ERROR("[VavCore] Decoder initialization failed (unsupported format or hardware unavailable)"); player->impl->decoder.reset(); @@ -463,22 +393,11 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f LOGF_DEBUG("[VavCore] Decoder initialized successfully!"); - // Apply debug options to newly created decoder player->impl->decoder->SetDebugOptions(&player->impl->debugOptions); LOGF_DEBUG("[VavCore] Debug options applied to decoder"); - // Store the actual decoder name for later retrieval player->impl->decoderName = player->impl->decoder->GetCodecName(); - // Set adaptive quality mode if supported - // TODO: Implement adaptive quality support in VavCore v1.1 - // Currently disabled as adaptive decoders don't implement IAdaptiveVideoDecoder interface yet - // auto adaptiveDecoder = dynamic_cast(player->impl->decoder.get()); - // if (adaptiveDecoder) { - // adaptiveDecoder->SetQualityMode(to_adaptive_quality_mode(player->impl->qualityMode)); - // } - - // Final verification - both fileReader and decoder should be ready if (!player->impl->fileReader || !player->impl->decoder) { if (player->impl->fileReader) { player->impl->fileReader->CloseFile(); @@ -520,19 +439,16 @@ VAVCORE_API VavCoreResult vavcore_decode_next_frame(VavCorePlayer* player, VavCo } try { - // Read next packet VideoPacket packet; if (!player->impl->fileReader->ReadNextPacket(packet)) { - return VAVCORE_END_OF_STREAM; // End of file + return VAVCORE_END_OF_STREAM; } - // Decode frame VideoFrame videoFrame; if (!player->impl->decoder->DecodeFrame(packet, videoFrame)) { return VAVCORE_ERROR_DECODE_FAILED; } - // Copy frame data to C structure copy_frame_data(videoFrame, frame); player->impl->currentFrame++; @@ -577,7 +493,6 @@ VAVCORE_API VavCoreResult vavcore_seek_to_frame(VavCorePlayer* player, uint64_t } } -// Test function to verify linking VAVCORE_API VavCoreResult vavcore_test_function(void) { return VAVCORE_SUCCESS; } @@ -592,7 +507,6 @@ VAVCORE_API VavCoreResult vavcore_reset(VavCorePlayer* player) { } try { - // Reset decoder if available if (player->impl->decoder) { if (!player->impl->decoder->Reset()) { // Continue anyway - not fatal @@ -601,7 +515,6 @@ VAVCORE_API VavCoreResult vavcore_reset(VavCorePlayer* player) { return VAVCORE_ERROR_INIT_FAILED; } - // Reset file reader if available if (player->impl->fileReader) { if (!player->impl->fileReader->Reset()) { // Continue anyway - not fatal @@ -610,7 +523,6 @@ VAVCORE_API VavCoreResult vavcore_reset(VavCorePlayer* player) { return VAVCORE_ERROR_INIT_FAILED; } - // Reset state variables player->impl->currentFrame = 0; player->impl->currentTimeSeconds = 0.0; @@ -633,7 +545,7 @@ VAVCORE_API VavCoreResult vavcore_get_metadata(VavCorePlayer* player, VavCoreVid metadata->frame_rate = player->impl->metadata.frame_rate; metadata->duration_seconds = player->impl->metadata.duration_seconds; metadata->total_frames = player->impl->metadata.total_frames; - metadata->codec_name = "AV1"; // Static for now + metadata->codec_name = "AV1"; return VAVCORE_SUCCESS; } @@ -648,7 +560,7 @@ VAVCORE_API double vavcore_get_current_time(VavCorePlayer* player) { VAVCORE_API int vavcore_is_end_of_file(VavCorePlayer* player) { if (!player || !player->impl || !player->impl->isOpen || !player->impl->fileReader) { - return 1; // Consider as EOF if invalid + return 1; } return player->impl->fileReader->IsEndOfFile() ? 1 : 0; } @@ -669,10 +581,6 @@ VAVCORE_API VavCoreResult vavcore_set_quality_mode(VavCorePlayer* player, VavCor if (player->impl->isOpen && player->impl->decoder) { // TODO: Implement adaptive quality support in VavCore v1.1 - // auto adaptiveDecoder = dynamic_cast(player->impl->decoder.get()); - // if (adaptiveDecoder) { - // adaptiveDecoder->SetQualityMode(to_adaptive_quality_mode(mode)); - // } } return VAVCORE_SUCCESS; @@ -688,19 +596,8 @@ VAVCORE_API VavCoreResult vavcore_get_performance_metrics(VavCorePlayer* player, } // TODO: Implement adaptive performance metrics in VavCore v1.1 - // auto adaptiveDecoder = dynamic_cast(player->decoder.get()); - // if (adaptiveDecoder) { - // auto perfMetrics = adaptiveDecoder->GetPerformanceMetrics(); - // metrics->average_decode_time_ms = perfMetrics.average_decode_time_ms; - // metrics->current_fps = perfMetrics.current_fps; - // metrics->frames_decoded = perfMetrics.frames_decoded; - // metrics->frames_dropped = perfMetrics.frames_dropped; - // metrics->current_quality_level = static_cast(adaptiveDecoder->GetCurrentQualityLevel()); - // } else { - // Default metrics for non-adaptive decoders - memset(metrics, 0, sizeof(VavCorePerformanceMetrics)); - metrics->current_quality_level = 4; // ULTRA quality - // } + memset(metrics, 0, sizeof(VavCorePerformanceMetrics)); + metrics->current_quality_level = 4; return VAVCORE_SUCCESS; } @@ -720,12 +617,6 @@ VAVCORE_API VavCoreResult vavcore_enable_adaptive_quality(VavCorePlayer* player, } // TODO: Implement adaptive mode control in VavCore v1.1 - // auto adaptiveDecoder = dynamic_cast(player->decoder.get()); - // if (adaptiveDecoder) { - // adaptiveDecoder->EnableAdaptiveMode(enable != 0); - // return VAVCORE_SUCCESS; - // } - return VAVCORE_ERROR_NOT_SUPPORTED; } @@ -735,12 +626,6 @@ VAVCORE_API VavCoreResult vavcore_set_target_framerate(VavCorePlayer* player, do } // TODO: Implement adaptive framerate control in VavCore v1.1 - // auto adaptiveDecoder = dynamic_cast(player->decoder.get()); - // if (adaptiveDecoder) { - // adaptiveDecoder->SetTargetFrameRate(fps); - // return VAVCORE_SUCCESS; - // } - return VAVCORE_ERROR_NOT_SUPPORTED; } @@ -756,39 +641,83 @@ VAVCORE_API void vavcore_free_frame(VavCoreVideoFrame* frame) { frame->v_plane = nullptr; } -// D3D Surface decoding API functions +// Android-specific Vulkan Surface decoding API functions VAVCORE_API int vavcore_supports_surface_type(VavCorePlayer* player, VavCoreSurfaceType type) { if (!player || !player->impl || !player->impl->decoder) { - return 0; // false + return 0; } return player->impl->decoder->SupportsSurfaceType(type) ? 1 : 0; } -VAVCORE_API VavCoreResult vavcore_set_d3d_device(VavCorePlayer* player, void* d3d_device, VavCoreSurfaceType type) { - if (!player || !player->impl || !d3d_device) { +VAVCORE_API VavCoreResult vavcore_set_vulkan_device(VavCorePlayer* player, void* vk_device, void* vk_instance, void* vk_physical_device) { + if (!player || !player->impl) { return VAVCORE_ERROR_INVALID_PARAM; } - // 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) { - LOGF_DEBUG("[vavcore_set_d3d_device] D3D device applied to existing decoder"); - return VAVCORE_SUCCESS; - } else { - LOGF_ERROR("[vavcore_set_d3d_device] WARNING: Failed to apply D3D device to existing decoder (will retry on next decode)"); - // Still return success - device is stored for later use - return VAVCORE_SUCCESS; - } - } else { - LOGF_DEBUG("[vavcore_set_d3d_device] Decoder not created yet, D3D device stored for later"); - return VAVCORE_SUCCESS; + if (!vk_device || !vk_instance || !vk_physical_device) { + LOGF_ERROR("[vavcore_set_vulkan_device] Invalid Vulkan handles"); + return VAVCORE_ERROR_INVALID_PARAM; } + + LOGF_INFO("[vavcore_set_vulkan_device] Registering Vulkan device with VavCore"); + LOGF_DEBUG("[vavcore_set_vulkan_device] VkDevice: %p, VkInstance: %p, VkPhysicalDevice: %p", + vk_device, vk_instance, vk_physical_device); + + // Store Vulkan device for later use (when decoder is created) + player->impl->vulkan_device = vk_device; + player->impl->vulkan_instance = vk_instance; + player->impl->vulkan_physical_device = vk_physical_device; + player->impl->has_vulkan_device = true; + + LOGF_INFO("[vavcore_set_vulkan_device] Vulkan device registered successfully - will be passed to decoder during initialization"); + + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_set_current_frame_fence(VavCorePlayer* player, void* vk_fence) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!player->impl->decoder) { + LOGF_WARNING("[vavcore_set_current_frame_fence] Decoder not initialized yet"); + return VAVCORE_ERROR_INIT_FAILED; + } + + LOGF_DEBUG("[vavcore_set_current_frame_fence] Setting VkFence=%p for current frame", vk_fence); + + bool success = player->impl->decoder->SetCurrentFrameFence(vk_fence); + + if (success) { + LOGF_DEBUG("[vavcore_set_current_frame_fence] VkFence set successfully"); + return VAVCORE_SUCCESS; + } else { + LOGF_ERROR("[vavcore_set_current_frame_fence] Failed to set VkFence (decoder may not support this operation)"); + return VAVCORE_ERROR_NOT_SUPPORTED; + } +} + +VAVCORE_API VavCoreResult vavcore_set_android_java_vm(void* java_vm) { + if (!java_vm) { + LOGF_ERROR("[vavcore_set_android_java_vm] Invalid JavaVM pointer"); + return VAVCORE_ERROR_INVALID_PARAM; + } + + std::lock_guard lock(g_mutex); + g_android_java_vm = static_cast(java_vm); + LOGF_INFO("[vavcore_set_android_java_vm] JavaVM registered successfully: %p", java_vm); + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_set_android_surface(VavCorePlayer* player, void* native_window) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + // TODO: Implement Android surface registration + LOGF_DEBUG("[vavcore_set_android_surface] Android surface registration requested (NOT YET IMPLEMENTED)"); + return VAVCORE_SUCCESS; } VAVCORE_API void* vavcore_get_sync_fence(VavCorePlayer* player) { @@ -810,7 +739,6 @@ VAVCORE_API VavCoreResult vavcore_decode_to_surface(VavCorePlayer* player, return VAVCORE_ERROR_INIT_FAILED; } - // Check if decoder supports the requested surface type if (!player->impl->decoder->SupportsSurfaceType(target_type)) { return VAVCORE_ERROR_NOT_SUPPORTED; } @@ -820,12 +748,9 @@ VAVCORE_API VavCoreResult vavcore_decode_to_surface(VavCorePlayer* player, size_t packet_size = 0; VideoPacket packet; - // Drain mode: target_surface==NULL means flush buffered frames if (target_surface == nullptr) { LOGF_DEBUG("[vavcore_decode_to_surface] Drain mode - flushing buffered frames"); - // packet_data remains NULL to signal drain mode } else { - // Normal mode: Read next packet from file if (!player->impl->fileReader->ReadNextPacket(packet)) { if (player->impl->fileReader->IsEndOfFile()) { LOGF_DEBUG("[vavcore_decode_to_surface] End of file reached"); @@ -837,7 +762,6 @@ VAVCORE_API VavCoreResult vavcore_decode_to_surface(VavCorePlayer* player, packet_size = packet.size; } - // Decode to surface (handles both normal and drain modes) VideoFrame videoFrame; bool success = player->impl->decoder->DecodeToSurface( packet_data, packet_size, @@ -845,51 +769,28 @@ VAVCORE_API VavCoreResult vavcore_decode_to_surface(VavCorePlayer* player, videoFrame ); - // Interpret result based on videoFrame content if (!success) { - // Decoder returned false if (videoFrame.width == 0 && videoFrame.height == 0) { - // No frame output yet - priming/buffering phase LOGF_DEBUG("[vavcore_decode_to_surface] Packet accepted, no output yet (priming)"); return VAVCORE_PACKET_ACCEPTED; } else { - // Actual decode error LOGF_ERROR("[vavcore_decode_to_surface] Decode failed"); return VAVCORE_ERROR_DECODE_FAILED; } } - // Success - check if we actually got a frame if (videoFrame.width == 0 || videoFrame.height == 0 || !videoFrame.is_valid) { - // Decoder returned true but no valid frame (should not happen, but handle it) LOGF_WARNING("[vavcore_decode_to_surface] Decoder returned success but frame invalid"); return VAVCORE_PACKET_ACCEPTED; } - // Convert to VavCoreVideoFrame with surface data frame->width = videoFrame.width; frame->height = videoFrame.height; frame->timestamp_us = static_cast(videoFrame.timestamp_seconds * 1000000.0); frame->frame_number = videoFrame.frame_index; frame->surface_type = target_type; - // Set surface-specific data switch (target_type) { - case VAVCORE_SURFACE_D3D11_TEXTURE: - frame->surface_data.d3d11.d3d11_texture = target_surface; - break; - case VAVCORE_SURFACE_D3D12_RESOURCE: - frame->surface_data.d3d12.d3d12_resource = target_surface; - // CRITICAL FIX: Copy CUDA fence value for D3D12-CUDA synchronization - // This fence value is set by NVDECAV1Decoder after CUDA kernel completion - frame->surface_data.d3d12.fence_value = videoFrame.sync_fence_value; - break; - case VAVCORE_SURFACE_CUDA_DEVICE: - // CUDA device pointer will be set by decoder implementation - break; - case VAVCORE_SURFACE_AMF_SURFACE: - frame->surface_data.amf.amf_surface = target_surface; - break; case VAVCORE_SURFACE_VULKAN_IMAGE: // Android MediaCodec → ImageReader → VkImage pipeline frame->surface_data.vulkan.vk_image = videoFrame.surface_data.vulkan.vk_image; @@ -904,7 +805,6 @@ VAVCORE_API VavCoreResult vavcore_decode_to_surface(VavCorePlayer* player, break; case VAVCORE_SURFACE_CPU: default: - // Fallback to CPU decoding copy_frame_data(videoFrame, frame); break; } @@ -936,19 +836,16 @@ VAVCORE_API VavCoreResult vavcore_set_debug_options(VavCorePlayer* player, const return VAVCORE_ERROR_INVALID_PARAM; } - // Copy debug options player->impl->debugOptions.enable_first_frame_debug = options->enable_first_frame_debug; player->impl->debugOptions.first_frame_debug_count = options->first_frame_debug_count; player->impl->debugOptions.enable_rgba_debug = options->enable_rgba_debug; player->impl->debugOptions.rgba_debug_count = options->rgba_debug_count; - // Copy debug output path if provided if (options->debug_output_path) { player->impl->debugOutputPath = options->debug_output_path; player->impl->debugOptions.debug_output_path = player->impl->debugOutputPath.c_str(); } - // Pass debug options to decoder if it exists if (player->impl->decoder) { player->impl->decoder->SetDebugOptions(&player->impl->debugOptions); } @@ -965,7 +862,6 @@ VAVCORE_API VavCoreResult vavcore_get_debug_options(VavCorePlayer* player, VavCo return VAVCORE_ERROR_INVALID_PARAM; } - // Copy current debug options to output *options = player->impl->debugOptions; return VAVCORE_SUCCESS; @@ -979,130 +875,25 @@ VAVCORE_API int vavcore_get_pending_decode_count(VavCorePlayer* player) { return player->impl->decoder->GetPendingDecodeCount(); } -// Android GPU Surface API stubs (Phase 1-3 implementation) -// TODO: Implement Vulkan device registration for MediaCodec → Vulkan pipeline - -VAVCORE_API VavCoreResult vavcore_set_vulkan_device(VavCorePlayer* player, void* vk_device, void* vk_instance, void* vk_physical_device) { - if (!player || !player->impl) { - return VAVCORE_ERROR_INVALID_PARAM; - } - - if (!vk_device || !vk_instance || !vk_physical_device) { - LOGF_ERROR("[vavcore_set_vulkan_device] Invalid Vulkan handles"); - return VAVCORE_ERROR_INVALID_PARAM; - } - - LOGF_INFO("[vavcore_set_vulkan_device] Registering Vulkan device with VavCore"); - LOGF_DEBUG("[vavcore_set_vulkan_device] VkDevice: %p, VkInstance: %p, VkPhysicalDevice: %p", - vk_device, vk_instance, vk_physical_device); - -#ifdef ANDROID - // Store Vulkan device for later use (when decoder is created) - player->impl->vulkan_device = vk_device; - player->impl->vulkan_instance = vk_instance; - player->impl->vulkan_physical_device = vk_physical_device; - player->impl->has_vulkan_device = true; - - LOGF_INFO("[vavcore_set_vulkan_device] Vulkan device registered successfully - will be passed to decoder during initialization"); - - // Note: Vulkan device will be passed to MediaCodec surface manager during decoder initialization - // in vavcore_open_file() after the decoder is created - - return VAVCORE_SUCCESS; -#else - LOGF_WARNING("[vavcore_set_vulkan_device] Vulkan device registration not supported on this platform"); +// Stub implementations for unsupported GPU APIs on Android +VAVCORE_API VavCoreResult vavcore_set_d3d_device(VavCorePlayer* player, void* d3d_device, VavCoreSurfaceType type) { + LOGF_WARNING("[vavcore_set_d3d_device] D3D device registration not supported on Android"); return VAVCORE_ERROR_NOT_SUPPORTED; -#endif -} - -VAVCORE_API VavCoreResult vavcore_set_current_frame_fence(VavCorePlayer* player, void* vk_fence) { - if (!player || !player->impl) { - return VAVCORE_ERROR_INVALID_PARAM; - } - -#ifdef ANDROID - // Check if decoder exists and is MediaCodec-based - if (!player->impl->decoder) { - LOGF_WARNING("[vavcore_set_current_frame_fence] Decoder not initialized yet"); - return VAVCORE_ERROR_INIT_FAILED; - } - - // Pass fence to decoder's surface manager - // This allows GPU-synchronized Image release in the next frame - LOGF_DEBUG("[vavcore_set_current_frame_fence] Setting VkFence=%p for current frame", vk_fence); - - // Get decoder's surface manager and set the fence - // The fence will be waited on before releasing the Image in the next ProcessAsyncOutputFrame call - bool success = player->impl->decoder->SetCurrentFrameFence(vk_fence); - - if (success) { - LOGF_DEBUG("[vavcore_set_current_frame_fence] VkFence set successfully"); - return VAVCORE_SUCCESS; - } else { - LOGF_ERROR("[vavcore_set_current_frame_fence] Failed to set VkFence (decoder may not support this operation)"); - return VAVCORE_ERROR_NOT_SUPPORTED; - } -#else - LOGF_WARNING("[vavcore_set_current_frame_fence] VkFence setting not supported on this platform"); - return VAVCORE_ERROR_NOT_SUPPORTED; -#endif -} - -VAVCORE_API VavCoreResult vavcore_set_android_java_vm(void* java_vm) { -#ifdef ANDROID - if (!java_vm) { - LOGF_ERROR("[vavcore_set_android_java_vm] Invalid JavaVM pointer"); - return VAVCORE_ERROR_INVALID_PARAM; - } - - std::lock_guard lock(g_mutex); - g_android_java_vm = static_cast(java_vm); - LOGF_INFO("[vavcore_set_android_java_vm] JavaVM registered successfully: %p", java_vm); - return VAVCORE_SUCCESS; -#else - LOGF_WARNING("[vavcore_set_android_java_vm] JavaVM registration not supported on this platform"); - return VAVCORE_ERROR_NOT_SUPPORTED; -#endif -} - -VAVCORE_API VavCoreResult vavcore_set_android_surface(VavCorePlayer* player, void* native_window) { - if (!player || !player->impl) { - return VAVCORE_ERROR_INVALID_PARAM; - } - - // TODO: Implement Android surface registration - LOGF_DEBUG("[vavcore_set_android_surface] Android surface registration requested (NOT YET IMPLEMENTED)"); - return VAVCORE_SUCCESS; } VAVCORE_API VavCoreResult vavcore_set_opengl_es_context(VavCorePlayer* player, void* egl_context) { - if (!player || !player->impl) { - return VAVCORE_ERROR_INVALID_PARAM; - } - - // TODO: Implement OpenGL ES context registration LOGF_DEBUG("[vavcore_set_opengl_es_context] OpenGL ES context registration requested (NOT YET IMPLEMENTED)"); return VAVCORE_SUCCESS; } VAVCORE_API VavCoreResult vavcore_set_opengl_context(VavCorePlayer* player, void* gl_context) { - if (!player || !player->impl) { - return VAVCORE_ERROR_INVALID_PARAM; - } - - // TODO: Implement OpenGL context registration LOGF_DEBUG("[vavcore_set_opengl_context] OpenGL context registration requested (NOT YET IMPLEMENTED)"); return VAVCORE_SUCCESS; } VAVCORE_API VavCoreResult vavcore_set_metal_device(VavCorePlayer* player, void* metal_device) { - if (!player || !player->impl) { - return VAVCORE_ERROR_INVALID_PARAM; - } - - // TODO: Implement Metal device registration - LOGF_DEBUG("[vavcore_set_metal_device] Metal device registration requested (NOT YET IMPLEMENTED)"); - return VAVCORE_SUCCESS; + LOGF_WARNING("[vavcore_set_metal_device] Metal device registration not supported on Android"); + return VAVCORE_ERROR_NOT_SUPPORTED; } VAVCORE_API VavCoreResult vavcore_convert_yuv_to_rgb( @@ -1114,9 +905,10 @@ VAVCORE_API VavCoreResult vavcore_convert_yuv_to_rgb( return VAVCORE_ERROR_INVALID_PARAM; } - // TODO: Implement YUV to RGB conversion LOGF_DEBUG("[vavcore_convert_yuv_to_rgb] YUV→RGB conversion requested (NOT YET IMPLEMENTED)"); return VAVCORE_ERROR_NOT_SUPPORTED; } -} // extern "C" \ No newline at end of file +} // extern "C" + +#endif // ANDROID diff --git a/vav2/platforms/windows/vavcore/src/VavCore_Apple.cpp b/vav2/platforms/windows/vavcore/src/VavCore_Apple.cpp new file mode 100644 index 0000000..c2fe4bd --- /dev/null +++ b/vav2/platforms/windows/vavcore/src/VavCore_Apple.cpp @@ -0,0 +1,783 @@ +// VavCore_Apple_Full.cpp - Complete iOS/macOS implementation of VavCore C API +// All platform-specific code consolidated in this file + +#include "pch.h" +#include "VavCore/VavCore.h" +#include "Common/VideoTypes.h" +#include "Common/AdaptiveTypes.h" +#include "Decoder/IVideoDecoder.h" +#include "Decoder/VideoDecoderFactory.h" +#include "FileIO/WebMFileReader.h" +#include "Common/VavCoreLogger.h" + +#include +#include +#include +#include + +#if defined(__APPLE__) +#include + +// Use VavCore namespace internally +using namespace VavCore; + +// Global state +static bool g_initialized = false; +static std::mutex g_mutex; + +// Error message mapping +static const char* get_error_message(VavCoreResult result) { + switch (result) { + case VAVCORE_SUCCESS: return "Success"; + case VAVCORE_ERROR_INIT_FAILED: return "Initialization failed"; + case VAVCORE_ERROR_INVALID_PARAM: return "Invalid parameter"; + case VAVCORE_ERROR_FILE_NOT_FOUND: return "File not found"; + case VAVCORE_ERROR_DECODE_FAILED: return "Decode failed"; + case VAVCORE_ERROR_OUT_OF_MEMORY: return "Out of memory"; + case VAVCORE_ERROR_NOT_SUPPORTED: return "Not supported"; + default: return "Unknown error"; + } +} + +// Forward declaration of implementation class +class VavCorePlayerImpl; + +// C-compatible player structure (pimpl pattern) +struct VavCorePlayer { + VavCorePlayerImpl* impl; +}; + +// C++ implementation class (hidden from C API) +class VavCorePlayerImpl { +public: + std::unique_ptr decoder; + std::unique_ptr fileReader; + VideoMetadata metadata; + VavCoreQualityMode qualityMode; + VavCoreDecoderType decoderType; + bool isOpen; + uint64_t currentFrame; + double currentTimeSeconds; + std::string decoderName; + + // Store Metal device before decoder creation (Apple-specific) + void* metal_device; + bool has_metal_device; + + // Debug options + VavCoreDebugOptions debugOptions; + std::string debugOutputPath; + + VavCorePlayerImpl() + : qualityMode(VAVCORE_QUALITY_CONSERVATIVE) + , decoderType(VAVCORE_DECODER_AUTO) + , isOpen(false) + , currentFrame(0) + , currentTimeSeconds(0.0) + , decoderName("unknown") + , metal_device(nullptr) + , has_metal_device(false) + , debugOutputPath("./debug_output") + { + fileReader = std::make_unique(); + + debugOptions.enable_first_frame_debug = false; + debugOptions.first_frame_debug_count = 3; + debugOptions.enable_rgba_debug = false; + debugOptions.rgba_debug_count = 1; + debugOptions.debug_output_path = debugOutputPath.c_str(); + } + + ~VavCorePlayerImpl() { + close_internal(); + } + + void close_internal() { + if (decoder) { + decoder->Cleanup(); + decoder.reset(); + } + if (fileReader) { + fileReader->CloseFile(); + } + isOpen = false; + currentFrame = 0; + currentTimeSeconds = 0.0; + } +}; + +// Convert internal quality mode to adaptive quality mode +static VavCore::AdaptiveQualityMode to_adaptive_quality_mode(VavCoreQualityMode mode) { + switch (mode) { + case VAVCORE_QUALITY_CONSERVATIVE: return VavCore::AdaptiveQualityMode::CONSERVATIVE; + case VAVCORE_QUALITY_FAST: return VavCore::AdaptiveQualityMode::FAST; + case VAVCORE_QUALITY_ULTRA_FAST: return VavCore::AdaptiveQualityMode::ULTRA_FAST; + default: return VavCore::AdaptiveQualityMode::CONSERVATIVE; + } +} + +// Convert internal decoder type to factory decoder type +static VavCore::VideoDecoderFactory::DecoderType to_decoder_type(VavCoreDecoderType type) { + switch (type) { + case VAVCORE_DECODER_AUTO: return VavCore::VideoDecoderFactory::DecoderType::AUTO; + case VAVCORE_DECODER_DAV1D: return VavCore::VideoDecoderFactory::DecoderType::DAV1D; + case VAVCORE_DECODER_VIDEO_TOOLBOX: return VavCore::VideoDecoderFactory::DecoderType::VIDEO_TOOLBOX; + default: return VavCore::VideoDecoderFactory::DecoderType::AUTO; + } +} + +// Convert VideoFrame to VavCoreVideoFrame +static void copy_frame_data(const VideoFrame& src, VavCoreVideoFrame* dst) { + if (!dst) return; + + dst->width = src.width; + dst->height = src.height; + dst->timestamp_us = static_cast(src.timestamp_seconds * 1000000.0); + dst->frame_number = src.frame_index; + dst->surface_type = VAVCORE_SURFACE_CPU; + + size_t y_size = src.y_size; + size_t u_size = src.u_size; + size_t v_size = src.v_size; + + dst->y_plane = static_cast(malloc(y_size)); + dst->u_plane = static_cast(malloc(u_size)); + dst->v_plane = static_cast(malloc(v_size)); + + if (dst->y_plane && dst->u_plane && dst->v_plane && + src.y_plane && src.u_plane && src.v_plane) { + memcpy(dst->y_plane, src.y_plane.get(), y_size); + memcpy(dst->u_plane, src.u_plane.get(), u_size); + memcpy(dst->v_plane, src.v_plane.get(), v_size); + } + + dst->y_stride = src.y_stride; + dst->u_stride = src.u_stride; + dst->v_stride = src.v_stride; + + dst->surface_data.cpu.planes[0] = dst->y_plane; + dst->surface_data.cpu.planes[1] = dst->u_plane; + dst->surface_data.cpu.planes[2] = dst->v_plane; + dst->surface_data.cpu.strides[0] = dst->y_stride; + dst->surface_data.cpu.strides[1] = dst->u_stride; + dst->surface_data.cpu.strides[2] = dst->v_stride; +} + +// ============================================================================ +// C API Implementation - Apple Platform (iOS/macOS) +// ============================================================================ + +extern "C" { + +VAVCORE_API VavCoreResult vavcore_initialize(void) { + std::lock_guard lock(g_mutex); + + if (g_initialized) { + return VAVCORE_SUCCESS; + } + + // Apple platforms don't need special initialization like DllMain or JNI + // Hardware decoders (VideoToolbox) are automatically available + VideoDecoderFactory::InitializeFactory(); + g_initialized = true; + LOGF_INFO("[VavCore Apple] Initialization complete"); + return VAVCORE_SUCCESS; +} + +VAVCORE_API void vavcore_cleanup(void) { + std::lock_guard lock(g_mutex); + + if (g_initialized) { + g_initialized = false; + LOGF_INFO("[VavCore Apple] Cleanup complete"); + } +} + +VAVCORE_API const char* vavcore_get_version_string(void) { + static std::string version = std::to_string(VAVCORE_VERSION_MAJOR) + "." + + std::to_string(VAVCORE_VERSION_MINOR) + "." + + std::to_string(VAVCORE_VERSION_PATCH); + return version.c_str(); +} + +VAVCORE_API const char* vavcore_get_error_string(VavCoreResult result) { + return get_error_message(result); +} + +VAVCORE_API VavCorePlayer* vavcore_create_player(void) { + if (!g_initialized) { + return nullptr; + } + + try { + VavCorePlayer* player = new VavCorePlayer(); + player->impl = new VavCorePlayerImpl(); + + if (!player->impl->fileReader) { + delete player->impl; + delete player; + return nullptr; + } + + return player; + } catch (const std::exception& e) { + return nullptr; + } catch (...) { + return nullptr; + } +} + +VAVCORE_API void vavcore_destroy_player(VavCorePlayer* player) { + if (player) { + delete player->impl; + delete player; + } +} + +VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* filepath) { + if (!player || !player->impl || !filepath) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!player->impl->fileReader) { + return VAVCORE_ERROR_INIT_FAILED; + } + + try { + LOGF_DEBUG("[VavCore] Opening file: %s", filepath); + + if (!player->impl->fileReader->OpenFile(filepath)) { + LOGF_DEBUG("[VavCore] OpenFile() returned false"); + return VAVCORE_ERROR_FILE_NOT_FOUND; + } + + LOGF_DEBUG("[VavCore] OpenFile() succeeded"); + + auto tracks = player->impl->fileReader->GetVideoTracks(); + LOGF_DEBUG("[VavCore] Found %zu video tracks", tracks.size()); + + bool foundAV1 = false; + for (const auto& track : tracks) { + LOGF_DEBUG("[VavCore] Track %lld: codec_type=%d (AV1=%d)", + track.track_number, (int)track.codec_type, (int)VideoCodecType::AV1); + + if (track.codec_type == VideoCodecType::AV1) { + LOGF_DEBUG("[VavCore] AV1 track found! Selecting track..."); + if (player->impl->fileReader->SelectVideoTrack(track.track_number)) { + LOGF_DEBUG("[VavCore] Track selected successfully"); + player->impl->metadata = player->impl->fileReader->GetVideoMetadata(); + foundAV1 = true; + break; + } + } + } + + if (!foundAV1) { + LOGF_ERROR("[VavCore] No AV1 tracks found - returning VAVCORE_ERROR_NOT_SUPPORTED"); + player->impl->fileReader->CloseFile(); + return VAVCORE_ERROR_NOT_SUPPORTED; + } + + LOGF_DEBUG("[VavCore] Creating decoder..."); + auto decoderType = to_decoder_type(player->impl->decoderType); + + LOGF_DEBUG("[VavCore] Decoder type requested: %d (0=AUTO, 4=DAV1D, 7=VIDEOTOOLBOX)", + static_cast(decoderType)); + + player->impl->decoder = VavCore::VideoDecoderFactory::CreateDecoder(VavCore::VideoCodecType::AV1, decoderType); + + if (!player->impl->decoder) { + LOGF_ERROR("[VavCore] No suitable decoder found (VideoDecoderFactory returned NULL)"); + player->impl->fileReader->CloseFile(); + return VAVCORE_ERROR_NO_DECODER; + } + + LOGF_DEBUG("[VavCore] Decoder created successfully."); + + // Apple-specific: Apply pending Metal device if it was set before decoder creation + if (player->impl->has_metal_device) { + LOGF_DEBUG("[VavCore] Applying pending Metal device before decoder initialization..."); + LOGF_DEBUG("[VavCore] Metal device: %p", player->impl->metal_device); + + // TODO: Implement SetMetalDevice in decoder interface + // bool metal_success = player->impl->decoder->SetMetalDevice(player->impl->metal_device); + } + + LOGF_DEBUG("[VavCore] Initializing decoder..."); + + if (!player->impl->decoder->Initialize(player->impl->metadata)) { + LOGF_ERROR("[VavCore] Decoder initialization failed (unsupported format or hardware unavailable)"); + player->impl->decoder.reset(); + player->impl->fileReader->CloseFile(); + return VAVCORE_ERROR_DECODER_UNAVAILABLE; + } + + LOGF_DEBUG("[VavCore] Decoder initialized successfully!"); + + player->impl->decoder->SetDebugOptions(&player->impl->debugOptions); + LOGF_DEBUG("[VavCore] Debug options applied to decoder"); + + player->impl->decoderName = player->impl->decoder->GetCodecName(); + + if (!player->impl->fileReader || !player->impl->decoder) { + if (player->impl->fileReader) { + player->impl->fileReader->CloseFile(); + } + if (player->impl->decoder) { + player->impl->decoder.reset(); + } + return VAVCORE_ERROR_INIT_FAILED; + } + + player->impl->isOpen = true; + player->impl->currentFrame = 0; + player->impl->currentTimeSeconds = 0.0; + + return VAVCORE_SUCCESS; + } catch (const std::exception& e) { + return VAVCORE_ERROR_INIT_FAILED; + } catch (...) { + return VAVCORE_ERROR_INIT_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_close_file(VavCorePlayer* player) { + if (!player) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->close_internal(); + return VAVCORE_SUCCESS; +} + +VAVCORE_API int vavcore_is_open(VavCorePlayer* player) { + return (player && player->impl && player->impl->isOpen) ? 1 : 0; +} + +VAVCORE_API VavCoreResult vavcore_decode_next_frame(VavCorePlayer* player, VavCoreVideoFrame* frame) { + if (!player || !player->impl || !frame || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + VideoPacket packet; + if (!player->impl->fileReader->ReadNextPacket(packet)) { + return VAVCORE_END_OF_STREAM; + } + + VideoFrame videoFrame; + if (!player->impl->decoder->DecodeFrame(packet, videoFrame)) { + return VAVCORE_ERROR_DECODE_FAILED; + } + + copy_frame_data(videoFrame, frame); + + player->impl->currentFrame++; + player->impl->currentTimeSeconds = packet.timestamp_seconds; + + return VAVCORE_SUCCESS; + } catch (...) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_seek_to_time(VavCorePlayer* player, double time_seconds) { + if (!player || !player->impl || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + if (player->impl->fileReader->SeekToTime(time_seconds)) { + player->impl->currentTimeSeconds = time_seconds; + return VAVCORE_SUCCESS; + } + return VAVCORE_ERROR_DECODE_FAILED; + } catch (...) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_seek_to_frame(VavCorePlayer* player, uint64_t frame_number) { + if (!player || !player->impl || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + if (player->impl->fileReader->SeekToFrame(frame_number)) { + player->impl->currentFrame = frame_number; + player->impl->currentTimeSeconds = static_cast(frame_number) / player->impl->metadata.frame_rate; + return VAVCORE_SUCCESS; + } + return VAVCORE_ERROR_DECODE_FAILED; + } catch (...) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_test_function(void) { + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_reset(VavCorePlayer* player) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + if (player->impl->decoder) { + if (!player->impl->decoder->Reset()) { + // Continue anyway - not fatal + } + } else { + return VAVCORE_ERROR_INIT_FAILED; + } + + if (player->impl->fileReader) { + if (!player->impl->fileReader->Reset()) { + // Continue anyway - not fatal + } + } else { + return VAVCORE_ERROR_INIT_FAILED; + } + + player->impl->currentFrame = 0; + player->impl->currentTimeSeconds = 0.0; + + return VAVCORE_SUCCESS; + + } catch (const std::exception& e) { + return VAVCORE_ERROR_INIT_FAILED; + } catch (...) { + return VAVCORE_ERROR_INIT_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_get_metadata(VavCorePlayer* player, VavCoreVideoMetadata* metadata) { + if (!player || !player->impl || !metadata || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + metadata->width = player->impl->metadata.width; + metadata->height = player->impl->metadata.height; + metadata->frame_rate = player->impl->metadata.frame_rate; + metadata->duration_seconds = player->impl->metadata.duration_seconds; + metadata->total_frames = player->impl->metadata.total_frames; + metadata->codec_name = "AV1"; + + return VAVCORE_SUCCESS; +} + +VAVCORE_API uint64_t vavcore_get_current_frame(VavCorePlayer* player) { + return (player && player->impl) ? player->impl->currentFrame : 0; +} + +VAVCORE_API double vavcore_get_current_time(VavCorePlayer* player) { + return (player && player->impl) ? player->impl->currentTimeSeconds : 0.0; +} + +VAVCORE_API int vavcore_is_end_of_file(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->isOpen || !player->impl->fileReader) { + return 1; + } + return player->impl->fileReader->IsEndOfFile() ? 1 : 0; +} + +VAVCORE_API const char* vavcore_get_codec_name(VavCorePlayer* player) { + if (!player || !player->impl) { + return "unknown"; + } + return player->impl->decoderName.c_str(); +} + +VAVCORE_API VavCoreResult vavcore_set_quality_mode(VavCorePlayer* player, VavCoreQualityMode mode) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->qualityMode = mode; + + if (player->impl->isOpen && player->impl->decoder) { + // TODO: Implement adaptive quality support in VavCore v1.1 + } + + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreQualityMode vavcore_get_quality_mode(VavCorePlayer* player) { + return (player && player->impl) ? player->impl->qualityMode : VAVCORE_QUALITY_CONSERVATIVE; +} + +VAVCORE_API VavCoreResult vavcore_get_performance_metrics(VavCorePlayer* player, VavCorePerformanceMetrics* metrics) { + if (!player || !player->impl || !metrics || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + // TODO: Implement adaptive performance metrics in VavCore v1.1 + memset(metrics, 0, sizeof(VavCorePerformanceMetrics)); + metrics->current_quality_level = 4; + + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_set_decoder_type(VavCorePlayer* player, VavCoreDecoderType type) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->decoderType = type; + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_enable_adaptive_quality(VavCorePlayer* player, int enable) { + if (!player || !player->impl || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + // TODO: Implement adaptive mode control in VavCore v1.1 + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_target_framerate(VavCorePlayer* player, double fps) { + if (!player || !player->impl || !player->impl->isOpen || fps <= 0.0) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + // TODO: Implement adaptive framerate control in VavCore v1.1 + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API void vavcore_free_frame(VavCoreVideoFrame* frame) { + if (!frame) return; + + free(frame->y_plane); + free(frame->u_plane); + free(frame->v_plane); + + frame->y_plane = nullptr; + frame->u_plane = nullptr; + frame->v_plane = nullptr; +} + +// Apple-specific Metal Surface decoding API functions +VAVCORE_API int vavcore_supports_surface_type(VavCorePlayer* player, VavCoreSurfaceType type) { + if (!player || !player->impl || !player->impl->decoder) { + return 0; + } + + return player->impl->decoder->SupportsSurfaceType(type) ? 1 : 0; +} + +VAVCORE_API VavCoreResult vavcore_set_metal_device(VavCorePlayer* player, void* metal_device) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->metal_device = metal_device; + player->impl->has_metal_device = true; + + LOGF_DEBUG("[vavcore_set_metal_device] Metal device registration requested (NOT YET IMPLEMENTED)"); + return VAVCORE_SUCCESS; +} + +VAVCORE_API void* vavcore_get_sync_fence(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->decoder) { + return nullptr; + } + return player->impl->decoder->GetSyncFence(); +} + +VAVCORE_API VavCoreResult vavcore_decode_to_surface(VavCorePlayer* player, + VavCoreSurfaceType target_type, + void* target_surface, + VavCoreVideoFrame* frame) { + if (!player || !player->impl || !player->impl->decoder || !frame) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!player->impl->isOpen) { + return VAVCORE_ERROR_INIT_FAILED; + } + + if (!player->impl->decoder->SupportsSurfaceType(target_type)) { + return VAVCORE_ERROR_NOT_SUPPORTED; + } + + try { + const uint8_t* packet_data = nullptr; + size_t packet_size = 0; + VideoPacket packet; + + if (target_surface == nullptr) { + LOGF_DEBUG("[vavcore_decode_to_surface] Drain mode - flushing buffered frames"); + } else { + if (!player->impl->fileReader->ReadNextPacket(packet)) { + if (player->impl->fileReader->IsEndOfFile()) { + LOGF_DEBUG("[vavcore_decode_to_surface] End of file reached"); + return VAVCORE_END_OF_STREAM; + } + return VAVCORE_ERROR_DECODE_FAILED; + } + packet_data = packet.data.get(); + packet_size = packet.size; + } + + VideoFrame videoFrame; + bool success = player->impl->decoder->DecodeToSurface( + packet_data, packet_size, + target_type, target_surface, + videoFrame + ); + + if (!success) { + if (videoFrame.width == 0 && videoFrame.height == 0) { + LOGF_DEBUG("[vavcore_decode_to_surface] Packet accepted, no output yet (priming)"); + return VAVCORE_PACKET_ACCEPTED; + } else { + LOGF_ERROR("[vavcore_decode_to_surface] Decode failed"); + return VAVCORE_ERROR_DECODE_FAILED; + } + } + + if (videoFrame.width == 0 || videoFrame.height == 0 || !videoFrame.is_valid) { + LOGF_WARNING("[vavcore_decode_to_surface] Decoder returned success but frame invalid"); + return VAVCORE_PACKET_ACCEPTED; + } + + frame->width = videoFrame.width; + frame->height = videoFrame.height; + frame->timestamp_us = static_cast(videoFrame.timestamp_seconds * 1000000.0); + frame->frame_number = videoFrame.frame_index; + frame->surface_type = target_type; + + switch (target_type) { + case VAVCORE_SURFACE_METAL_TEXTURE: + // TODO: Implement Metal texture support + break; + case VAVCORE_SURFACE_CPU: + default: + copy_frame_data(videoFrame, frame); + break; + } + + player->impl->currentFrame++; + player->impl->currentTimeSeconds = videoFrame.timestamp_seconds; + + return VAVCORE_SUCCESS; + } + catch (const std::exception&) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreSurfaceType vavcore_get_optimal_surface_type(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->decoder) { + return VAVCORE_SURFACE_CPU; + } + + return player->impl->decoder->GetOptimalSurfaceType(); +} + +VAVCORE_API VavCoreResult vavcore_set_debug_options(VavCorePlayer* player, const VavCoreDebugOptions* options) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!options) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->debugOptions.enable_first_frame_debug = options->enable_first_frame_debug; + player->impl->debugOptions.first_frame_debug_count = options->first_frame_debug_count; + player->impl->debugOptions.enable_rgba_debug = options->enable_rgba_debug; + player->impl->debugOptions.rgba_debug_count = options->rgba_debug_count; + + if (options->debug_output_path) { + player->impl->debugOutputPath = options->debug_output_path; + player->impl->debugOptions.debug_output_path = player->impl->debugOutputPath.c_str(); + } + + if (player->impl->decoder) { + player->impl->decoder->SetDebugOptions(&player->impl->debugOptions); + } + + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_get_debug_options(VavCorePlayer* player, VavCoreDebugOptions* options) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!options) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + *options = player->impl->debugOptions; + + return VAVCORE_SUCCESS; +} + +VAVCORE_API int vavcore_get_pending_decode_count(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->decoder) { + return 0; + } + + return player->impl->decoder->GetPendingDecodeCount(); +} + +// Stub implementations for unsupported GPU APIs on Apple platforms +VAVCORE_API VavCoreResult vavcore_set_d3d_device(VavCorePlayer* player, void* d3d_device, VavCoreSurfaceType type) { + LOGF_WARNING("[vavcore_set_d3d_device] D3D device registration not supported on Apple platforms"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_vulkan_device(VavCorePlayer* player, void* vk_device, void* vk_instance, void* vk_physical_device) { + LOGF_WARNING("[vavcore_set_vulkan_device] Vulkan device registration not supported on Apple platforms"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_current_frame_fence(VavCorePlayer* player, void* vk_fence) { + LOGF_WARNING("[vavcore_set_current_frame_fence] VkFence setting not supported on Apple platforms"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_android_java_vm(void* java_vm) { + LOGF_WARNING("[vavcore_set_android_java_vm] JavaVM registration not supported on Apple platforms"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_android_surface(VavCorePlayer* player, void* native_window) { + LOGF_WARNING("[vavcore_set_android_surface] Android surface registration not supported on Apple platforms"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_opengl_es_context(VavCorePlayer* player, void* egl_context) { + LOGF_DEBUG("[vavcore_set_opengl_es_context] OpenGL ES context registration requested (NOT YET IMPLEMENTED)"); + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_set_opengl_context(VavCorePlayer* player, void* gl_context) { + LOGF_DEBUG("[vavcore_set_opengl_context] OpenGL context registration requested (NOT YET IMPLEMENTED)"); + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_convert_yuv_to_rgb( + VavCoreVideoFrame* yuv_frame, + uint8_t* rgb_buffer, + int rgb_stride) +{ + if (!yuv_frame || !rgb_buffer) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + LOGF_DEBUG("[vavcore_convert_yuv_to_rgb] YUV→RGB conversion requested (NOT YET IMPLEMENTED)"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +} // extern "C" + +#endif // __APPLE__ diff --git a/vav2/platforms/windows/vavcore/src/VavCore_Windows.cpp b/vav2/platforms/windows/vavcore/src/VavCore_Windows.cpp new file mode 100644 index 0000000..e2298f1 --- /dev/null +++ b/vav2/platforms/windows/vavcore/src/VavCore_Windows.cpp @@ -0,0 +1,819 @@ +// VavCore_Windows_Full.cpp - Complete Windows implementation of VavCore C API +// All platform-specific code consolidated in this file + +#include "pch.h" +#include "VavCore/VavCore.h" +#include "Common/VideoTypes.h" +#include "Common/AdaptiveTypes.h" +#include "Decoder/IVideoDecoder.h" +#include "Decoder/VideoDecoderFactory.h" +#include "FileIO/WebMFileReader.h" +#include "Common/VavCoreLogger.h" + +#include +#include +#include +#include + +// Use VavCore namespace internally +using namespace VavCore; + +// Forward declarations for DllMain-based initialization +extern "C" bool PerformSafeDllInitialization(); +extern "C" bool IsDllReadyForInitialization(); + +// Global state +static bool g_initialized = false; +static std::mutex g_mutex; + +// Error message mapping +static const char* get_error_message(VavCoreResult result) { + switch (result) { + case VAVCORE_SUCCESS: return "Success"; + case VAVCORE_ERROR_INIT_FAILED: return "Initialization failed"; + case VAVCORE_ERROR_INVALID_PARAM: return "Invalid parameter"; + case VAVCORE_ERROR_FILE_NOT_FOUND: return "File not found"; + case VAVCORE_ERROR_DECODE_FAILED: return "Decode failed"; + case VAVCORE_ERROR_OUT_OF_MEMORY: return "Out of memory"; + case VAVCORE_ERROR_NOT_SUPPORTED: return "Not supported"; + default: return "Unknown error"; + } +} + +// Forward declaration of implementation class +class VavCorePlayerImpl; + +// C-compatible player structure (pimpl pattern) +struct VavCorePlayer { + VavCorePlayerImpl* impl; +}; + +// C++ implementation class (hidden from C API) +class VavCorePlayerImpl { +public: + std::unique_ptr decoder; + std::unique_ptr fileReader; + VideoMetadata metadata; + VavCoreQualityMode qualityMode; + VavCoreDecoderType decoderType; + bool isOpen; + uint64_t currentFrame; + double currentTimeSeconds; + std::string decoderName; + + // Store D3D device before decoder creation + void* pendingD3DDevice; + VavCoreSurfaceType pendingD3DSurfaceType; + + // Debug options + VavCoreDebugOptions debugOptions; + std::string debugOutputPath; + + VavCorePlayerImpl() + : qualityMode(VAVCORE_QUALITY_CONSERVATIVE) + , decoderType(VAVCORE_DECODER_AUTO) + , isOpen(false) + , currentFrame(0) + , currentTimeSeconds(0.0) + , decoderName("unknown") + , pendingD3DDevice(nullptr) + , pendingD3DSurfaceType(VAVCORE_SURFACE_CPU) + , debugOutputPath("./debug_output") + { + fileReader = std::make_unique(); + + debugOptions.enable_first_frame_debug = false; + debugOptions.first_frame_debug_count = 3; + debugOptions.enable_rgba_debug = false; + debugOptions.rgba_debug_count = 1; + debugOptions.debug_output_path = debugOutputPath.c_str(); + } + + ~VavCorePlayerImpl() { + close_internal(); + } + + void close_internal() { + if (decoder) { + decoder->Cleanup(); + decoder.reset(); + } + if (fileReader) { + fileReader->CloseFile(); + } + isOpen = false; + currentFrame = 0; + currentTimeSeconds = 0.0; + } +}; + +// Convert internal quality mode to adaptive quality mode +static VavCore::AdaptiveQualityMode to_adaptive_quality_mode(VavCoreQualityMode mode) { + switch (mode) { + case VAVCORE_QUALITY_CONSERVATIVE: return VavCore::AdaptiveQualityMode::CONSERVATIVE; + case VAVCORE_QUALITY_FAST: return VavCore::AdaptiveQualityMode::FAST; + case VAVCORE_QUALITY_ULTRA_FAST: return VavCore::AdaptiveQualityMode::ULTRA_FAST; + default: return VavCore::AdaptiveQualityMode::CONSERVATIVE; + } +} + +// Convert internal decoder type to factory decoder type +static VavCore::VideoDecoderFactory::DecoderType to_decoder_type(VavCoreDecoderType type) { + switch (type) { + case VAVCORE_DECODER_AUTO: return VavCore::VideoDecoderFactory::DecoderType::AUTO; + case VAVCORE_DECODER_DAV1D: return VavCore::VideoDecoderFactory::DecoderType::DAV1D; + case VAVCORE_DECODER_NVDEC: return VavCore::VideoDecoderFactory::DecoderType::NVDEC; + case VAVCORE_DECODER_MEDIA_FOUNDATION: return VavCore::VideoDecoderFactory::DecoderType::MEDIA_FOUNDATION; + case VAVCORE_DECODER_VPL: return VavCore::VideoDecoderFactory::DecoderType::VPL; + case VAVCORE_DECODER_AMF: return VavCore::VideoDecoderFactory::DecoderType::AMF; + default: return VavCore::VideoDecoderFactory::DecoderType::AUTO; + } +} + +// Convert VideoFrame to VavCoreVideoFrame +static void copy_frame_data(const VideoFrame& src, VavCoreVideoFrame* dst) { + if (!dst) return; + + dst->width = src.width; + dst->height = src.height; + dst->timestamp_us = static_cast(src.timestamp_seconds * 1000000.0); + dst->frame_number = src.frame_index; + dst->surface_type = VAVCORE_SURFACE_CPU; + + size_t y_size = src.y_size; + size_t u_size = src.u_size; + size_t v_size = src.v_size; + + dst->y_plane = static_cast(malloc(y_size)); + dst->u_plane = static_cast(malloc(u_size)); + dst->v_plane = static_cast(malloc(v_size)); + + if (dst->y_plane && dst->u_plane && dst->v_plane && + src.y_plane && src.u_plane && src.v_plane) { + memcpy(dst->y_plane, src.y_plane.get(), y_size); + memcpy(dst->u_plane, src.u_plane.get(), u_size); + memcpy(dst->v_plane, src.v_plane.get(), v_size); + } + + dst->y_stride = src.y_stride; + dst->u_stride = src.u_stride; + dst->v_stride = src.v_stride; + + dst->surface_data.cpu.planes[0] = dst->y_plane; + dst->surface_data.cpu.planes[1] = dst->u_plane; + dst->surface_data.cpu.planes[2] = dst->v_plane; + dst->surface_data.cpu.strides[0] = dst->y_stride; + dst->surface_data.cpu.strides[1] = dst->u_stride; + dst->surface_data.cpu.strides[2] = dst->v_stride; +} + +// ============================================================================ +// C API Implementation - Windows Platform +// ============================================================================ + +extern "C" { + +VAVCORE_API VavCoreResult vavcore_initialize(void) { + std::lock_guard lock(g_mutex); + + if (g_initialized) { + return VAVCORE_SUCCESS; + } + + // Windows-specific: Check if DLL is ready for safe initialization + if (!IsDllReadyForInitialization()) { + LOGF_ERROR("[VavCore Windows] DLL not ready for initialization"); + return VAVCORE_ERROR_INIT_FAILED; + } + + // Windows-specific: Perform safe DLL-level initialization + if (!PerformSafeDllInitialization()) { + LOGF_ERROR("[VavCore Windows] DLL initialization failed"); + return VAVCORE_ERROR_INIT_FAILED; + } + + // Initialize decoder factory (Windows uses static initialization for decoder registration) + VideoDecoderFactory::InitializeFactory(); + g_initialized = true; + LOGF_INFO("[VavCore Windows] Initialization complete"); + return VAVCORE_SUCCESS; +} + +VAVCORE_API void vavcore_cleanup(void) { + std::lock_guard lock(g_mutex); + + if (g_initialized) { + g_initialized = false; + LOGF_INFO("[VavCore Windows] Cleanup complete"); + } +} + +VAVCORE_API const char* vavcore_get_version_string(void) { + static std::string version = std::to_string(VAVCORE_VERSION_MAJOR) + "." + + std::to_string(VAVCORE_VERSION_MINOR) + "." + + std::to_string(VAVCORE_VERSION_PATCH); + return version.c_str(); +} + +VAVCORE_API const char* vavcore_get_error_string(VavCoreResult result) { + return get_error_message(result); +} + +VAVCORE_API VavCorePlayer* vavcore_create_player(void) { + if (!g_initialized) { + return nullptr; + } + + try { + VavCorePlayer* player = new VavCorePlayer(); + player->impl = new VavCorePlayerImpl(); + + if (!player->impl->fileReader) { + delete player->impl; + delete player; + return nullptr; + } + + return player; + } catch (const std::exception& e) { + return nullptr; + } catch (...) { + return nullptr; + } +} + +VAVCORE_API void vavcore_destroy_player(VavCorePlayer* player) { + if (player) { + delete player->impl; + delete player; + } +} + +VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* filepath) { + if (!player || !player->impl || !filepath) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!player->impl->fileReader) { + return VAVCORE_ERROR_INIT_FAILED; + } + + try { + LOGF_DEBUG("[VavCore] Opening file: %s", filepath); + + if (!player->impl->fileReader->OpenFile(filepath)) { + LOGF_DEBUG("[VavCore] OpenFile() returned false"); + return VAVCORE_ERROR_FILE_NOT_FOUND; + } + + LOGF_DEBUG("[VavCore] OpenFile() succeeded"); + + auto tracks = player->impl->fileReader->GetVideoTracks(); + LOGF_DEBUG("[VavCore] Found %zu video tracks", tracks.size()); + + bool foundAV1 = false; + for (const auto& track : tracks) { + LOGF_DEBUG("[VavCore] Track %lld: codec_type=%d (AV1=%d)", + track.track_number, (int)track.codec_type, (int)VideoCodecType::AV1); + + if (track.codec_type == VideoCodecType::AV1) { + LOGF_DEBUG("[VavCore] AV1 track found! Selecting track..."); + if (player->impl->fileReader->SelectVideoTrack(track.track_number)) { + LOGF_DEBUG("[VavCore] Track selected successfully"); + player->impl->metadata = player->impl->fileReader->GetVideoMetadata(); + foundAV1 = true; + break; + } + } + } + + if (!foundAV1) { + LOGF_ERROR("[VavCore] No AV1 tracks found - returning VAVCORE_ERROR_NOT_SUPPORTED"); + player->impl->fileReader->CloseFile(); + return VAVCORE_ERROR_NOT_SUPPORTED; + } + + LOGF_DEBUG("[VavCore] Creating decoder..."); + auto decoderType = to_decoder_type(player->impl->decoderType); + + LOGF_DEBUG("[VavCore] Decoder type requested: %d (0=AUTO, 1=NVDEC, 2=VPL, 3=AMF, 4=DAV1D, 5=MF)", + static_cast(decoderType)); + + player->impl->decoder = VavCore::VideoDecoderFactory::CreateDecoder(VavCore::VideoCodecType::AV1, decoderType); + + if (!player->impl->decoder) { + LOGF_ERROR("[VavCore] No suitable decoder found (VideoDecoderFactory returned NULL)"); + player->impl->fileReader->CloseFile(); + return VAVCORE_ERROR_NO_DECODER; + } + + LOGF_DEBUG("[VavCore] Decoder created successfully."); + + // Windows-specific: Apply pending D3D device if it was set before decoder creation + if (player->impl->pendingD3DDevice) { + LOGF_DEBUG("[VavCore] Applying pending D3D device before decoder initialization..."); + LOGF_DEBUG("[VavCore] Pending D3D device: %p, Type: %d", + player->impl->pendingD3DDevice, static_cast(player->impl->pendingD3DSurfaceType)); + + player->impl->decoder->SetD3DDevice(player->impl->pendingD3DDevice, player->impl->pendingD3DSurfaceType); + + player->impl->pendingD3DDevice = nullptr; + player->impl->pendingD3DSurfaceType = VAVCORE_SURFACE_CPU; + } + + LOGF_DEBUG("[VavCore] Initializing decoder..."); + + if (!player->impl->decoder->Initialize(player->impl->metadata)) { + LOGF_ERROR("[VavCore] Decoder initialization failed (unsupported format or hardware unavailable)"); + player->impl->decoder.reset(); + player->impl->fileReader->CloseFile(); + return VAVCORE_ERROR_DECODER_UNAVAILABLE; + } + + LOGF_DEBUG("[VavCore] Decoder initialized successfully!"); + + player->impl->decoder->SetDebugOptions(&player->impl->debugOptions); + LOGF_DEBUG("[VavCore] Debug options applied to decoder"); + + player->impl->decoderName = player->impl->decoder->GetCodecName(); + + if (!player->impl->fileReader || !player->impl->decoder) { + if (player->impl->fileReader) { + player->impl->fileReader->CloseFile(); + } + if (player->impl->decoder) { + player->impl->decoder.reset(); + } + return VAVCORE_ERROR_INIT_FAILED; + } + + player->impl->isOpen = true; + player->impl->currentFrame = 0; + player->impl->currentTimeSeconds = 0.0; + + return VAVCORE_SUCCESS; + } catch (const std::exception& e) { + return VAVCORE_ERROR_INIT_FAILED; + } catch (...) { + return VAVCORE_ERROR_INIT_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_close_file(VavCorePlayer* player) { + if (!player) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->close_internal(); + return VAVCORE_SUCCESS; +} + +VAVCORE_API int vavcore_is_open(VavCorePlayer* player) { + return (player && player->impl && player->impl->isOpen) ? 1 : 0; +} + +VAVCORE_API VavCoreResult vavcore_decode_next_frame(VavCorePlayer* player, VavCoreVideoFrame* frame) { + if (!player || !player->impl || !frame || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + VideoPacket packet; + if (!player->impl->fileReader->ReadNextPacket(packet)) { + return VAVCORE_END_OF_STREAM; + } + + VideoFrame videoFrame; + if (!player->impl->decoder->DecodeFrame(packet, videoFrame)) { + return VAVCORE_ERROR_DECODE_FAILED; + } + + copy_frame_data(videoFrame, frame); + + player->impl->currentFrame++; + player->impl->currentTimeSeconds = packet.timestamp_seconds; + + return VAVCORE_SUCCESS; + } catch (...) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_seek_to_time(VavCorePlayer* player, double time_seconds) { + if (!player || !player->impl || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + if (player->impl->fileReader->SeekToTime(time_seconds)) { + player->impl->currentTimeSeconds = time_seconds; + return VAVCORE_SUCCESS; + } + return VAVCORE_ERROR_DECODE_FAILED; + } catch (...) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_seek_to_frame(VavCorePlayer* player, uint64_t frame_number) { + if (!player || !player->impl || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + if (player->impl->fileReader->SeekToFrame(frame_number)) { + player->impl->currentFrame = frame_number; + player->impl->currentTimeSeconds = static_cast(frame_number) / player->impl->metadata.frame_rate; + return VAVCORE_SUCCESS; + } + return VAVCORE_ERROR_DECODE_FAILED; + } catch (...) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_test_function(void) { + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_reset(VavCorePlayer* player) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + try { + if (player->impl->decoder) { + if (!player->impl->decoder->Reset()) { + // Continue anyway - not fatal + } + } else { + return VAVCORE_ERROR_INIT_FAILED; + } + + if (player->impl->fileReader) { + if (!player->impl->fileReader->Reset()) { + // Continue anyway - not fatal + } + } else { + return VAVCORE_ERROR_INIT_FAILED; + } + + player->impl->currentFrame = 0; + player->impl->currentTimeSeconds = 0.0; + + return VAVCORE_SUCCESS; + + } catch (const std::exception& e) { + return VAVCORE_ERROR_INIT_FAILED; + } catch (...) { + return VAVCORE_ERROR_INIT_FAILED; + } +} + +VAVCORE_API VavCoreResult vavcore_get_metadata(VavCorePlayer* player, VavCoreVideoMetadata* metadata) { + if (!player || !player->impl || !metadata || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + metadata->width = player->impl->metadata.width; + metadata->height = player->impl->metadata.height; + metadata->frame_rate = player->impl->metadata.frame_rate; + metadata->duration_seconds = player->impl->metadata.duration_seconds; + metadata->total_frames = player->impl->metadata.total_frames; + metadata->codec_name = "AV1"; + + return VAVCORE_SUCCESS; +} + +VAVCORE_API uint64_t vavcore_get_current_frame(VavCorePlayer* player) { + return (player && player->impl) ? player->impl->currentFrame : 0; +} + +VAVCORE_API double vavcore_get_current_time(VavCorePlayer* player) { + return (player && player->impl) ? player->impl->currentTimeSeconds : 0.0; +} + +VAVCORE_API int vavcore_is_end_of_file(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->isOpen || !player->impl->fileReader) { + return 1; + } + return player->impl->fileReader->IsEndOfFile() ? 1 : 0; +} + +VAVCORE_API const char* vavcore_get_codec_name(VavCorePlayer* player) { + if (!player || !player->impl) { + return "unknown"; + } + return player->impl->decoderName.c_str(); +} + +VAVCORE_API VavCoreResult vavcore_set_quality_mode(VavCorePlayer* player, VavCoreQualityMode mode) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->qualityMode = mode; + + if (player->impl->isOpen && player->impl->decoder) { + // TODO: Implement adaptive quality support in VavCore v1.1 + } + + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreQualityMode vavcore_get_quality_mode(VavCorePlayer* player) { + return (player && player->impl) ? player->impl->qualityMode : VAVCORE_QUALITY_CONSERVATIVE; +} + +VAVCORE_API VavCoreResult vavcore_get_performance_metrics(VavCorePlayer* player, VavCorePerformanceMetrics* metrics) { + if (!player || !player->impl || !metrics || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + // TODO: Implement adaptive performance metrics in VavCore v1.1 + memset(metrics, 0, sizeof(VavCorePerformanceMetrics)); + metrics->current_quality_level = 4; + + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_set_decoder_type(VavCorePlayer* player, VavCoreDecoderType type) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->decoderType = type; + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_enable_adaptive_quality(VavCorePlayer* player, int enable) { + if (!player || !player->impl || !player->impl->isOpen) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + // TODO: Implement adaptive mode control in VavCore v1.1 + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_target_framerate(VavCorePlayer* player, double fps) { + if (!player || !player->impl || !player->impl->isOpen || fps <= 0.0) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + // TODO: Implement adaptive framerate control in VavCore v1.1 + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API void vavcore_free_frame(VavCoreVideoFrame* frame) { + if (!frame) return; + + free(frame->y_plane); + free(frame->u_plane); + free(frame->v_plane); + + frame->y_plane = nullptr; + frame->u_plane = nullptr; + frame->v_plane = nullptr; +} + +// Windows-specific D3D Surface decoding API functions +VAVCORE_API int vavcore_supports_surface_type(VavCorePlayer* player, VavCoreSurfaceType type) { + if (!player || !player->impl || !player->impl->decoder) { + return 0; + } + + return player->impl->decoder->SupportsSurfaceType(type) ? 1 : 0; +} + +VAVCORE_API VavCoreResult vavcore_set_d3d_device(VavCorePlayer* player, void* d3d_device, VavCoreSurfaceType type) { + if (!player || !player->impl || !d3d_device) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->pendingD3DDevice = d3d_device; + player->impl->pendingD3DSurfaceType = type; + + if (player->impl->decoder) { + bool success = player->impl->decoder->SetD3DDevice(d3d_device, type); + if (success) { + LOGF_DEBUG("[vavcore_set_d3d_device] D3D device applied to existing decoder"); + return VAVCORE_SUCCESS; + } else { + LOGF_ERROR("[vavcore_set_d3d_device] WARNING: Failed to apply D3D device to existing decoder (will retry on next decode)"); + return VAVCORE_SUCCESS; + } + } else { + LOGF_DEBUG("[vavcore_set_d3d_device] Decoder not created yet, D3D device stored for later"); + return VAVCORE_SUCCESS; + } +} + +VAVCORE_API void* vavcore_get_sync_fence(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->decoder) { + return nullptr; + } + return player->impl->decoder->GetSyncFence(); +} + +VAVCORE_API VavCoreResult vavcore_decode_to_surface(VavCorePlayer* player, + VavCoreSurfaceType target_type, + void* target_surface, + VavCoreVideoFrame* frame) { + if (!player || !player->impl || !player->impl->decoder || !frame) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!player->impl->isOpen) { + return VAVCORE_ERROR_INIT_FAILED; + } + + if (!player->impl->decoder->SupportsSurfaceType(target_type)) { + return VAVCORE_ERROR_NOT_SUPPORTED; + } + + try { + const uint8_t* packet_data = nullptr; + size_t packet_size = 0; + VideoPacket packet; + + if (target_surface == nullptr) { + LOGF_DEBUG("[vavcore_decode_to_surface] Drain mode - flushing buffered frames"); + } else { + if (!player->impl->fileReader->ReadNextPacket(packet)) { + if (player->impl->fileReader->IsEndOfFile()) { + LOGF_DEBUG("[vavcore_decode_to_surface] End of file reached"); + return VAVCORE_END_OF_STREAM; + } + return VAVCORE_ERROR_DECODE_FAILED; + } + packet_data = packet.data.get(); + packet_size = packet.size; + } + + VideoFrame videoFrame; + bool success = player->impl->decoder->DecodeToSurface( + packet_data, packet_size, + target_type, target_surface, + videoFrame + ); + + if (!success) { + if (videoFrame.width == 0 && videoFrame.height == 0) { + LOGF_DEBUG("[vavcore_decode_to_surface] Packet accepted, no output yet (priming)"); + return VAVCORE_PACKET_ACCEPTED; + } else { + LOGF_ERROR("[vavcore_decode_to_surface] Decode failed"); + return VAVCORE_ERROR_DECODE_FAILED; + } + } + + if (videoFrame.width == 0 || videoFrame.height == 0 || !videoFrame.is_valid) { + LOGF_WARNING("[vavcore_decode_to_surface] Decoder returned success but frame invalid"); + return VAVCORE_PACKET_ACCEPTED; + } + + frame->width = videoFrame.width; + frame->height = videoFrame.height; + frame->timestamp_us = static_cast(videoFrame.timestamp_seconds * 1000000.0); + frame->frame_number = videoFrame.frame_index; + frame->surface_type = target_type; + + switch (target_type) { + case VAVCORE_SURFACE_D3D11_TEXTURE: + frame->surface_data.d3d11.d3d11_texture = target_surface; + break; + case VAVCORE_SURFACE_D3D12_RESOURCE: + frame->surface_data.d3d12.d3d12_resource = target_surface; + frame->surface_data.d3d12.fence_value = videoFrame.sync_fence_value; + break; + case VAVCORE_SURFACE_CUDA_DEVICE: + break; + case VAVCORE_SURFACE_AMF_SURFACE: + frame->surface_data.amf.amf_surface = target_surface; + break; + case VAVCORE_SURFACE_CPU: + default: + copy_frame_data(videoFrame, frame); + break; + } + + player->impl->currentFrame++; + player->impl->currentTimeSeconds = videoFrame.timestamp_seconds; + + return VAVCORE_SUCCESS; + } + catch (const std::exception&) { + return VAVCORE_ERROR_DECODE_FAILED; + } +} + +VAVCORE_API VavCoreSurfaceType vavcore_get_optimal_surface_type(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->decoder) { + return VAVCORE_SURFACE_CPU; + } + + return player->impl->decoder->GetOptimalSurfaceType(); +} + +VAVCORE_API VavCoreResult vavcore_set_debug_options(VavCorePlayer* player, const VavCoreDebugOptions* options) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!options) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + player->impl->debugOptions.enable_first_frame_debug = options->enable_first_frame_debug; + player->impl->debugOptions.first_frame_debug_count = options->first_frame_debug_count; + player->impl->debugOptions.enable_rgba_debug = options->enable_rgba_debug; + player->impl->debugOptions.rgba_debug_count = options->rgba_debug_count; + + if (options->debug_output_path) { + player->impl->debugOutputPath = options->debug_output_path; + player->impl->debugOptions.debug_output_path = player->impl->debugOutputPath.c_str(); + } + + if (player->impl->decoder) { + player->impl->decoder->SetDebugOptions(&player->impl->debugOptions); + } + + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_get_debug_options(VavCorePlayer* player, VavCoreDebugOptions* options) { + if (!player || !player->impl) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + if (!options) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + *options = player->impl->debugOptions; + + return VAVCORE_SUCCESS; +} + +VAVCORE_API int vavcore_get_pending_decode_count(VavCorePlayer* player) { + if (!player || !player->impl || !player->impl->decoder) { + return 0; + } + + return player->impl->decoder->GetPendingDecodeCount(); +} + +// Stub implementations for unsupported GPU APIs on Windows +VAVCORE_API VavCoreResult vavcore_set_vulkan_device(VavCorePlayer* player, void* vk_device, void* vk_instance, void* vk_physical_device) { + LOGF_WARNING("[vavcore_set_vulkan_device] Vulkan device registration not supported on Windows"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_current_frame_fence(VavCorePlayer* player, void* vk_fence) { + LOGF_WARNING("[vavcore_set_current_frame_fence] VkFence setting not supported on Windows"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_android_java_vm(void* java_vm) { + LOGF_WARNING("[vavcore_set_android_java_vm] JavaVM registration not supported on Windows"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_android_surface(VavCorePlayer* player, void* native_window) { + LOGF_WARNING("[vavcore_set_android_surface] Android surface registration not supported on Windows"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_set_opengl_es_context(VavCorePlayer* player, void* egl_context) { + LOGF_DEBUG("[vavcore_set_opengl_es_context] OpenGL ES context registration requested (NOT YET IMPLEMENTED)"); + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_set_opengl_context(VavCorePlayer* player, void* gl_context) { + LOGF_DEBUG("[vavcore_set_opengl_context] OpenGL context registration requested (NOT YET IMPLEMENTED)"); + return VAVCORE_SUCCESS; +} + +VAVCORE_API VavCoreResult vavcore_set_metal_device(VavCorePlayer* player, void* metal_device) { + LOGF_WARNING("[vavcore_set_metal_device] Metal device registration not supported on Windows"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +VAVCORE_API VavCoreResult vavcore_convert_yuv_to_rgb( + VavCoreVideoFrame* yuv_frame, + uint8_t* rgb_buffer, + int rgb_stride) +{ + if (!yuv_frame || !rgb_buffer) { + return VAVCORE_ERROR_INVALID_PARAM; + } + + LOGF_DEBUG("[vavcore_convert_yuv_to_rgb] YUV→RGB conversion requested (NOT YET IMPLEMENTED)"); + return VAVCORE_ERROR_NOT_SUPPORTED; +} + +} // extern "C"