Refactoring NVDEC decoder & VideoPlayerControl2
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
#include "pch.h"
|
||||
#include "VideoPlayerControl2.xaml.h"
|
||||
#if __has_include("VideoPlayerControl2.g.cpp")
|
||||
#include "VideoPlayerControl2.g.cpp"
|
||||
#endif
|
||||
|
||||
#include <winrt/Microsoft.UI.Dispatching.h>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
|
||||
// D3D12 for GPU surface decoding
|
||||
#include <d3d12.h>
|
||||
#include <wrl/client.h>
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
// Include log manager for logging
|
||||
#include "src/Logger/LogManager.h"
|
||||
|
||||
// Using alias to avoid namespace conflicts
|
||||
using LogMgr = Vav2Player::LogManager;
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::UI::Xaml;
|
||||
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
namespace winrt::Vav2Player::implementation
|
||||
{
|
||||
VideoPlayerControl2::VideoPlayerControl2()
|
||||
{
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Constructor called");
|
||||
|
||||
// Create core components
|
||||
m_playbackController = std::make_unique<::Vav2Player::PlaybackController>();
|
||||
m_frameProcessor = std::make_unique<::Vav2Player::FrameProcessor>();
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Core components created");
|
||||
}
|
||||
|
||||
VideoPlayerControl2::~VideoPlayerControl2()
|
||||
{
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Destructor called");
|
||||
|
||||
// Stop playback and cleanup
|
||||
if (m_playbackController) {
|
||||
m_playbackController->Stop();
|
||||
}
|
||||
|
||||
CleanupRenderer();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// XAML Event Handlers
|
||||
// ========================================
|
||||
|
||||
void VideoPlayerControl2::UserControl_Loaded(IInspectable const&, RoutedEventArgs const&)
|
||||
{
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"UserControl_Loaded");
|
||||
|
||||
try {
|
||||
// Initialize renderer with SwapChainPanel
|
||||
InitializeRenderer();
|
||||
|
||||
// Connect FrameProcessor to renderer and dispatcher
|
||||
if (m_frameProcessor && m_gpuRenderer) {
|
||||
m_frameProcessor->SetRenderer(m_gpuRenderer.get());
|
||||
m_frameProcessor->SetDispatcherQueue(DispatcherQueue());
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"FrameProcessor configured");
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
UpdateStatus(L"Initialized");
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::string error = "Failed to initialize: " + std::string(e.what());
|
||||
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", std::wstring(error.begin(), error.end()));
|
||||
UpdateStatus(L"Initialization failed");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::UserControl_Unloaded(IInspectable const&, RoutedEventArgs const&)
|
||||
{
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"UserControl_Unloaded");
|
||||
|
||||
// Stop playback
|
||||
if (m_playbackController) {
|
||||
m_playbackController->Stop();
|
||||
m_playbackController->Unload();
|
||||
}
|
||||
|
||||
// Cleanup renderer
|
||||
CleanupRenderer();
|
||||
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::UserControl_SizeChanged(IInspectable const&, SizeChangedEventArgs const& e)
|
||||
{
|
||||
auto newSize = e.NewSize();
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2",
|
||||
L"SizeChanged: " + std::to_wstring((int)newSize.Width) + L"x" + std::to_wstring((int)newSize.Height));
|
||||
|
||||
// Update renderer size
|
||||
if (m_gpuRenderer && m_gpuRenderer->IsInitialized()) {
|
||||
HRESULT hr = m_gpuRenderer->Resize(
|
||||
static_cast<uint32_t>(newSize.Width),
|
||||
static_cast<uint32_t>(newSize.Height)
|
||||
);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to resize renderer");
|
||||
}
|
||||
}
|
||||
|
||||
// Update AspectFit if video is loaded
|
||||
if (m_playbackController && m_playbackController->IsLoaded()) {
|
||||
UpdateVideoImageAspectFit(
|
||||
m_playbackController->GetVideoWidth(),
|
||||
m_playbackController->GetVideoHeight()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::HoverDetector_PointerEntered(IInspectable const&, Input::PointerRoutedEventArgs const&)
|
||||
{
|
||||
// Future: Show video controls on hover
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::HoverDetector_PointerExited(IInspectable const&, Input::PointerRoutedEventArgs const&)
|
||||
{
|
||||
// Future: Hide video controls
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Public Properties
|
||||
// ========================================
|
||||
|
||||
winrt::hstring VideoPlayerControl2::VideoSource()
|
||||
{
|
||||
return m_videoSource;
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::VideoSource(winrt::hstring const& value)
|
||||
{
|
||||
if (m_videoSource != value) {
|
||||
m_videoSource = value;
|
||||
if (!value.empty()) {
|
||||
LoadVideo(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoPlayerControl2::ShowControls()
|
||||
{
|
||||
return m_showControls;
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::ShowControls(bool value)
|
||||
{
|
||||
m_showControls = value;
|
||||
}
|
||||
|
||||
bool VideoPlayerControl2::AutoPlay()
|
||||
{
|
||||
return m_autoPlay;
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::AutoPlay(bool value)
|
||||
{
|
||||
m_autoPlay = value;
|
||||
}
|
||||
|
||||
Vav2Player::VideoDecoderType VideoPlayerControl2::DecoderType()
|
||||
{
|
||||
if (!m_playbackController) {
|
||||
return winrt::Vav2Player::VideoDecoderType::Auto;
|
||||
}
|
||||
|
||||
VavCoreDecoderType coreType = m_playbackController->GetDecoderType();
|
||||
|
||||
// Convert VavCoreDecoderType to VideoDecoderType
|
||||
switch (coreType) {
|
||||
case VAVCORE_DECODER_NVDEC:
|
||||
return winrt::Vav2Player::VideoDecoderType::NVDEC;
|
||||
case VAVCORE_DECODER_VPL:
|
||||
return winrt::Vav2Player::VideoDecoderType::VPL;
|
||||
case VAVCORE_DECODER_AMF:
|
||||
return winrt::Vav2Player::VideoDecoderType::AMF;
|
||||
case VAVCORE_DECODER_DAV1D:
|
||||
return winrt::Vav2Player::VideoDecoderType::DAV1D;
|
||||
case VAVCORE_DECODER_MEDIA_FOUNDATION:
|
||||
return winrt::Vav2Player::VideoDecoderType::MediaFoundation;
|
||||
default:
|
||||
return winrt::Vav2Player::VideoDecoderType::Auto;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::DecoderType(Vav2Player::VideoDecoderType value)
|
||||
{
|
||||
if (!m_playbackController) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert VideoDecoderType to VavCoreDecoderType
|
||||
VavCoreDecoderType coreType = VAVCORE_DECODER_AUTO;
|
||||
|
||||
if (value == winrt::Vav2Player::VideoDecoderType::NVDEC) {
|
||||
coreType = VAVCORE_DECODER_NVDEC;
|
||||
} else if (value == winrt::Vav2Player::VideoDecoderType::VPL) {
|
||||
coreType = VAVCORE_DECODER_VPL;
|
||||
} else if (value == winrt::Vav2Player::VideoDecoderType::AMF) {
|
||||
coreType = VAVCORE_DECODER_AMF;
|
||||
} else if (value == winrt::Vav2Player::VideoDecoderType::DAV1D) {
|
||||
coreType = VAVCORE_DECODER_DAV1D;
|
||||
} else if (value == winrt::Vav2Player::VideoDecoderType::MediaFoundation) {
|
||||
coreType = VAVCORE_DECODER_MEDIA_FOUNDATION;
|
||||
}
|
||||
|
||||
m_playbackController->SetDecoderType(coreType);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Public Methods
|
||||
// ========================================
|
||||
|
||||
void VideoPlayerControl2::LoadVideo(winrt::hstring const& filePath)
|
||||
{
|
||||
if (!m_initialized || !m_playbackController) {
|
||||
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Cannot load video: not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Loading video: " + std::wstring(filePath));
|
||||
UpdateStatus(L"Loading...");
|
||||
|
||||
// Stop current playback
|
||||
if (m_playbackController->IsPlaying()) {
|
||||
m_playbackController->Stop();
|
||||
}
|
||||
|
||||
// Load video file
|
||||
std::wstring filePathStr(filePath.c_str());
|
||||
bool success = m_playbackController->LoadVideo(filePathStr);
|
||||
|
||||
if (success) {
|
||||
// Get video dimensions and create NV12 texture
|
||||
uint32_t videoWidth = m_playbackController->GetVideoWidth();
|
||||
uint32_t videoHeight = m_playbackController->GetVideoHeight();
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2",
|
||||
L"Video loaded: " + std::to_wstring(videoWidth) + L"x" + std::to_wstring(videoHeight));
|
||||
|
||||
// Create NV12 texture for CUDA interop
|
||||
if (m_gpuRenderer) {
|
||||
HRESULT hr = m_gpuRenderer->CreateNV12TextureR8Layout(videoWidth, videoHeight);
|
||||
if (SUCCEEDED(hr)) {
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"NV12 texture created");
|
||||
|
||||
// Pass D3D12 device to VavCore for CUDA-D3D12 interop
|
||||
ID3D12Device* d3dDevice = m_gpuRenderer->GetD3D12Device();
|
||||
if (d3dDevice) {
|
||||
VavCorePlayer* player = m_playbackController->GetVavCorePlayer();
|
||||
if (player) {
|
||||
vavcore_set_d3d_device(player, d3dDevice, VAVCORE_SURFACE_D3D12_RESOURCE);
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"D3D12 device passed to VavCore");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to create NV12 texture");
|
||||
}
|
||||
}
|
||||
|
||||
// Update AspectFit
|
||||
UpdateVideoImageAspectFit(videoWidth, videoHeight);
|
||||
|
||||
UpdateStatus(L"Loaded");
|
||||
|
||||
// Auto-play if enabled
|
||||
if (m_autoPlay) {
|
||||
Play();
|
||||
}
|
||||
} else {
|
||||
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to load video");
|
||||
UpdateStatus(L"Load failed");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::Play()
|
||||
{
|
||||
if (!m_initialized || !m_playbackController || !m_playbackController->IsLoaded()) {
|
||||
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Cannot play: video not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Starting playback");
|
||||
|
||||
// Start playback with frame-ready callback
|
||||
auto weakThis = get_weak();
|
||||
m_playbackController->Play([weakThis]() {
|
||||
if (auto strongThis = weakThis.get()) {
|
||||
strongThis->OnFrameReady();
|
||||
}
|
||||
});
|
||||
|
||||
UpdateStatus(L"Playing");
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::Pause()
|
||||
{
|
||||
if (!m_playbackController) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Pausing playback");
|
||||
m_playbackController->Pause();
|
||||
UpdateStatus(L"Paused");
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::Stop()
|
||||
{
|
||||
if (!m_playbackController) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Stopping playback");
|
||||
m_playbackController->Stop();
|
||||
UpdateStatus(L"Stopped");
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::Seek(double timeSeconds)
|
||||
{
|
||||
if (!m_playbackController) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Seeking to " + std::to_wstring(timeSeconds) + L"s");
|
||||
m_playbackController->Seek(timeSeconds);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Status Queries
|
||||
// ========================================
|
||||
|
||||
bool VideoPlayerControl2::IsVideoPlaying()
|
||||
{
|
||||
return m_playbackController ? m_playbackController->IsPlaying() : false;
|
||||
}
|
||||
|
||||
bool VideoPlayerControl2::IsVideoLoaded()
|
||||
{
|
||||
return m_playbackController ? m_playbackController->IsLoaded() : false;
|
||||
}
|
||||
|
||||
double VideoPlayerControl2::CurrentTime()
|
||||
{
|
||||
return m_playbackController ? m_playbackController->GetCurrentTime() : 0.0;
|
||||
}
|
||||
|
||||
double VideoPlayerControl2::Duration()
|
||||
{
|
||||
return m_playbackController ? m_playbackController->GetDuration() : 0.0;
|
||||
}
|
||||
|
||||
winrt::hstring VideoPlayerControl2::Status()
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Private Helper Methods
|
||||
// ========================================
|
||||
|
||||
void VideoPlayerControl2::InitializeRenderer()
|
||||
{
|
||||
if (m_gpuRenderer && m_gpuRenderer->IsInitialized()) {
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Renderer already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Initializing renderer");
|
||||
|
||||
m_gpuRenderer = std::make_unique<::Vav2Player::SimpleGPURenderer>();
|
||||
|
||||
// Get SwapChainPanel size
|
||||
auto panelSize = VideoSwapChainPanel().ActualSize();
|
||||
uint32_t width = static_cast<uint32_t>(panelSize.x);
|
||||
uint32_t height = static_cast<uint32_t>(panelSize.y);
|
||||
|
||||
// Initialize with SwapChainPanel
|
||||
HRESULT hr = m_gpuRenderer->InitializeWithSwapChain(
|
||||
VideoSwapChainPanel(),
|
||||
width > 0 ? width : 1920,
|
||||
height > 0 ? height : 1080
|
||||
);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Renderer initialized successfully");
|
||||
} else {
|
||||
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to initialize renderer");
|
||||
m_gpuRenderer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::CleanupRenderer()
|
||||
{
|
||||
if (m_gpuRenderer) {
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Cleaning up renderer");
|
||||
m_gpuRenderer->Shutdown();
|
||||
m_gpuRenderer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::UpdateStatus(winrt::hstring const& message)
|
||||
{
|
||||
m_status = message;
|
||||
|
||||
// Update UI on UI thread
|
||||
DispatcherQueue().TryEnqueue([weakThis = get_weak(), message]() {
|
||||
if (auto strongThis = weakThis.get()) {
|
||||
strongThis->StatusText().Text(message);
|
||||
strongThis->StatusOverlay().Visibility(Visibility::Visible);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::UpdateVideoImageAspectFit(int videoWidth, int videoHeight)
|
||||
{
|
||||
if (videoWidth == 0 || videoHeight == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get container size
|
||||
auto containerSize = VideoSwapChainPanel().ActualSize();
|
||||
double containerWidth = containerSize.x;
|
||||
double containerHeight = containerSize.y;
|
||||
|
||||
if (containerWidth == 0 || containerHeight == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate AspectFit dimensions
|
||||
double videoAspectRatio = static_cast<double>(videoWidth) / videoHeight;
|
||||
double containerAspectRatio = containerWidth / containerHeight;
|
||||
|
||||
double displayWidth, displayHeight;
|
||||
|
||||
if (videoAspectRatio > containerAspectRatio) {
|
||||
// Video is wider - fit to container width
|
||||
displayWidth = containerWidth;
|
||||
displayHeight = containerWidth / videoAspectRatio;
|
||||
} else {
|
||||
// Video is taller - fit to container height
|
||||
displayHeight = containerHeight;
|
||||
displayWidth = containerHeight * videoAspectRatio;
|
||||
}
|
||||
|
||||
// Update SwapChainPanel size
|
||||
VideoSwapChainPanel().Width(displayWidth);
|
||||
VideoSwapChainPanel().Height(displayHeight);
|
||||
|
||||
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2",
|
||||
L"AspectFit: " + std::to_wstring((int)displayWidth) + L"x" + std::to_wstring((int)displayHeight));
|
||||
}
|
||||
|
||||
void VideoPlayerControl2::OnFrameReady()
|
||||
{
|
||||
// Called from PlaybackController timing thread (30fps)
|
||||
if (!m_frameProcessor || !m_playbackController) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process frame (decode on background, render on UI thread)
|
||||
VavCorePlayer* player = m_playbackController->GetVavCorePlayer();
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_frameProcessor->ProcessFrame(player, [](bool success) {
|
||||
// Frame processing complete callback (optional)
|
||||
if (!success) {
|
||||
OutputDebugStringA("[VideoPlayerControl2] Frame processing failed\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace winrt::Vav2Player::implementation
|
||||
Reference in New Issue
Block a user