19 KiB
VideoPlayerControl2 리팩토링 계획
날짜: 2025-10-01 상태: ✅ 완료 (Phase 1-4) 접근 방식: 모듈 분리 (Option 2) + 실용적 아키텍처
🎯 목표
- VideoPlayerControl2 생성 - 기존 VideoPlayerControl과 함께 새로운 구현 생성
- 모듈화 설계 - 과도한 엔지니어링 없이 책임 분리
- 성능 영향 없음 - 현재 성능 유지 또는 개선
- 점진적 마이그레이션 - 개발 중 VideoPlayerControl을 참조용으로 유지
- 확장성 - 주요 리팩토링 없이 기능 추가 용이
📊 현재 상태 분석
VideoPlayerControl (원본)
- 크기: 1,671 lines (.cpp) + 219 lines (.h) = 1,890 lines
- 책임: 7개 이상의 서로 다른 관심사가 혼재
- VavCore 플레이어 관리
- GPU/CPU 렌더링 전환
- 재생 타이밍 제어
- UI 이벤트 처리
- 메모리 풀 관리 (미사용)
- 성능 모니터링 (미사용)
- D3D Surface 관리
해결할 문제
- ❌ 너무 많은 책임 - 단일 클래스가 모든 것을 처리
- ❌ 죽은 코드 - MemoryPool, AdvancedPerformanceMonitor, CPU 렌더링
- ❌ 복잡한 상태 - 전체에 분산된 11개의 atomic/bool 플래그
- ❌ 테스트 어려움 - WinUI3와의 강한 결합
- ❌ 확장 어려움 - 기능 추가 시 모든 것을 건드려야 함
🏗️ 새로운 아키텍처 (VideoPlayerControl2)
설계 원칙
- ✅ 완벽보다 실용 - 분리가 필요한 것만 분리
- ✅ 상속보다 컴포지션 - 컴포지션 사용, 깊은 계층 구조 회피
- ✅ 단일 책임 - 각 클래스는 하나의 명확한 목적
- ✅ 간결하게 유지 - 최대 3-4개 클래스, 10개 이상 과도한 분리 방지
클래스 구조
VideoPlayerControl2.xaml.h/.cpp (UI 레이어 - ~400 lines)
├── PlaybackController (재생 로직 - ~300 lines)
│ ├── 타이밍 스레드 관리
│ ├── Play/Pause/Stop 상태 머신
│ ├── VavCore 플레이어 생명주기
│ └── 프레임 디코드 조정
│
├── FrameProcessor (프레임 처리 - ~250 lines)
│ ├── 백그라운드 디코드 스레드
│ ├── 프레임 처리 조절
│ ├── 디코드 → 렌더 파이프라인
│ └── 오류 처리
│
└── SimpleGPURenderer (렌더링 - 기존, ~2,000 lines)
└── NV12 파이프라인 (별도로 정리 예정)
총 예상 크기: ~950 lines (vs 1,890 = 50% 감소)
📝 상세 설계
1. VideoPlayerControl2.xaml.h (~150 lines)
책임:
- WinUI3 XAML UserControl 통합
- UI 이벤트 처리 (Click, SizeChanged, Loaded/Unloaded)
- 프로퍼티 바인딩 (VideoSource, ShowControls, AutoPlay)
- PlaybackController로 상태 쿼리 전달
- UI 스레드 업데이트 (DispatcherQueue)
주요 멤버:
namespace winrt::Vav2Player::implementation
{
struct VideoPlayerControl2 : VideoPlayerControl2T<VideoPlayerControl2>
{
VideoPlayerControl2();
~VideoPlayerControl2();
// XAML 이벤트
void UserControl_Loaded(...);
void UserControl_Unloaded(...);
void UserControl_SizeChanged(...);
void HoverDetector_PointerEntered(...);
void HoverDetector_PointerExited(...);
// 공개 프로퍼티 (XAML 바인딩)
winrt::hstring VideoSource();
void VideoSource(winrt::hstring const& value);
bool ShowControls();
void ShowControls(bool value);
bool AutoPlay();
void AutoPlay(bool value);
// 공개 메서드
void LoadVideo(winrt::hstring const& filePath);
void Play();
void Pause();
void Stop();
void Seek(double timeSeconds);
// 상태 쿼리
bool IsVideoPlaying();
bool IsVideoLoaded();
double CurrentTime();
double Duration();
winrt::hstring Status();
private:
// 핵심 컴포넌트 (컴포지션)
std::unique_ptr<PlaybackController> m_playbackController;
std::unique_ptr<FrameProcessor> m_frameProcessor;
std::unique_ptr<SimpleGPURenderer> m_gpuRenderer;
// UI 상태만
winrt::hstring m_videoSource;
bool m_showControls = true;
bool m_autoPlay = false;
winrt::hstring m_status = L"Ready";
// WinUI 컴포넌트
winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel m_swapChainPanel{ nullptr };
// UI 헬퍼
void InitializeRenderer();
void UpdateStatus(winrt::hstring const& message);
void UpdateVideoImageAspectFit(int videoWidth, int videoHeight);
};
}
더 이상 포함하지 않음:
- ❌ 타이밍 스레드 로직
- ❌ VavCore 플레이어 관리
- ❌ 프레임 처리 로직
- ❌ 디코더 타입 관리
- ❌ 메모리 풀
- ❌ 성능 모니터링
2. PlaybackController.h/.cpp (~300 lines)
책임:
- VavCore 플레이어 생명주기 (create, open, close, destroy)
- 재생 상태 머신 (Stopped → Playing → Paused)
- 타이밍 스레드 관리 (30fps/60fps)
- 디코더 구성
- 비디오 메타데이터 (duration, resolution, FPS)
주요 멤버:
class PlaybackController
{
public:
PlaybackController();
~PlaybackController();
// 생명주기
bool LoadVideo(const std::wstring& filePath);
void Unload();
// 재생 제어
void Play(std::function<void()> onFrameReady);
void Pause();
void Stop();
void Seek(double timeSeconds);
// 상태 쿼리
bool IsPlaying() const { return m_isPlaying; }
bool IsLoaded() const { return m_isLoaded; }
double GetCurrentTime() const { return m_currentTime; }
double GetDuration() const { return m_duration; }
// 비디오 정보
uint32_t GetVideoWidth() const { return m_videoWidth; }
uint32_t GetVideoHeight() const { return m_videoHeight; }
double GetFrameRate() const { return m_frameRate; }
// VavCore 접근 (FrameProcessor용)
VavCorePlayer* GetVavCorePlayer() const { return m_vavCorePlayer; }
// 디코더 구성
void SetDecoderType(VavCoreDecoderType type);
VavCoreDecoderType GetDecoderType() const { return m_decoderType; }
private:
// VavCore 플레이어
VavCorePlayer* m_vavCorePlayer = nullptr;
// 재생 상태
std::atomic<bool> m_isPlaying{false};
std::atomic<bool> m_isLoaded{false};
std::atomic<bool> m_shouldStopTiming{false};
// 비디오 메타데이터
uint32_t m_videoWidth = 0;
uint32_t m_videoHeight = 0;
double m_frameRate = 30.0;
double m_duration = 0.0;
double m_currentTime = 0.0;
uint64_t m_currentFrame = 0;
uint64_t m_totalFrames = 0;
// 구성
VavCoreDecoderType m_decoderType = VAVCORE_DECODER_AUTO;
std::wstring m_currentFilePath;
// 타이밍 스레드
std::unique_ptr<std::thread> m_timingThread;
std::function<void()> m_frameReadyCallback;
// 헬퍼 메서드
bool InitializeVavCore();
void CleanupVavCore();
void StartTimingThread();
void StopTimingThread();
void TimingThreadLoop();
};
주요 설계 결정:
- ✅ 콜백 기반 -
Play(onFrameReady)가 프레임 처리 트리거 - ✅ 렌더링 로직 없음 - 순수한 재생 제어만
- ✅ VavCore 캡슐화 - 모든 vavcore_* 호출을 한 곳에
- ✅ 스레드 안전 - 상태를 위한 Atomic 플래그
3. FrameProcessor.h/.cpp (~250 lines)
책임:
- 백그라운드 프레임 디코딩 (UI 스레드 밖)
- 프레임 처리 조절 (m_frameProcessing 플래그)
- 디코드 → 렌더 파이프라인 조정
- 오류 처리 및 복구
- 렌더 완료 동기화
주요 멤버:
class FrameProcessor
{
public:
FrameProcessor();
~FrameProcessor();
// 렌더러로 초기화
void SetRenderer(SimpleGPURenderer* renderer);
void SetDispatcherQueue(winrt::Microsoft::UI::Dispatching::DispatcherQueue queue);
// 단일 프레임 처리 (PlaybackController 타이밍 스레드에서 호출)
// 반환: 프레임이 처리되면 true, 스킵되면 false (이전 프레임이 아직 렌더링 중)
void ProcessFrame(VavCorePlayer* player,
std::function<void(bool success)> onComplete);
// 현재 처리 중인지 확인
bool IsProcessing() const { return m_frameProcessing; }
// 통계
uint64_t GetFramesDecoded() const { return m_framesDecoded; }
uint64_t GetFramesDropped() const { return m_framesDropped; }
uint64_t GetDecodeErrors() const { return m_decodeErrors; }
private:
SimpleGPURenderer* m_renderer = nullptr; // 비소유
winrt::Microsoft::UI::Dispatching::DispatcherQueue m_dispatcherQueue{ nullptr };
// 처리 상태
std::atomic<bool> m_frameProcessing{false};
// 통계
std::atomic<uint64_t> m_framesDecoded{0};
std::atomic<uint64_t> m_framesDropped{0};
std::atomic<uint64_t> m_decodeErrors{0};
// 헬퍼 메서드
bool DecodeFrame(VavCorePlayer* player, VavCoreVideoFrame& frame);
bool RenderFrame(const VavCoreVideoFrame& frame);
};
주요 설계 결정:
- ✅ 상태 없음 - 내부 상태 없이 처리 로직만
- ✅ 논블로킹 - 이미 처리 중이면 즉시 반환
- ✅ 콜백 기반 완료 - 비동기 렌더 완료
- ✅ 간단한 인터페이스 - 하나의 주 메서드
ProcessFrame()
4. SimpleGPURenderer (기존, 별도로 정리 예정)
현재 상태: 여러 파이프라인이 있는 2,083 lines 미래: NV12 전용 파이프라인으로 정리 (~800 lines) 현재로서는: 있는 그대로 사용, VideoPlayerControl2 통합에 집중
🔄 데이터 플로우
초기화 플로우
VideoPlayerControl2::UserControl_Loaded()
├─> InitializeRenderer()
│ └─> m_gpuRenderer->InitializeWithSwapChain(...)
│
└─> m_playbackController = std::make_unique<PlaybackController>()
└─> m_frameProcessor = std::make_unique<FrameProcessor>()
└─> m_frameProcessor->SetRenderer(m_gpuRenderer.get())
비디오 로드 플로우
VideoPlayerControl2::LoadVideo(filePath)
└─> m_playbackController->LoadVideo(filePath)
├─> vavcore_create_player()
├─> vavcore_open_file()
├─> vavcore_set_decoder_type()
└─> 메타데이터 추출 (width, height, fps, duration)
재생 플로우
VideoPlayerControl2::Play()
└─> m_playbackController->Play(onFrameReady)
└─> 타이밍 스레드 시작 (30fps 루프)
└─> 콜백: VideoPlayerControl2::OnFrameReady()
└─> m_frameProcessor->ProcessFrame(player, onComplete)
├─> [백그라운드] vavcore_decode_to_surface()
│ └─> NV12 텍스처로 디코드
│
└─> [UI 스레드] DispatcherQueue.TryEnqueue()
└─> m_gpuRenderer->RenderNV12TextureToBackBuffer()
├─> D3D12 NV12 → RGB 변환
└─> Present()
└─> 콜백: onComplete(true)
└─> m_frameProcessing = false
주요 동기화 지점
- 타이밍 스레드 →
OnFrameReady()매 33.3ms (30fps) - 백그라운드 디코드 → VavCore에서 NV12 텍스처로 디코드
- UI 스레드 렌더 → D3D12 렌더 + Present
- 완료 콜백 →
m_frameProcessing플래그 해제
📂 파일 구조
D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\
├── VideoPlayerControl2.xaml (XAML UI 정의 - VideoPlayerControl.xaml에서 복사)
├── VideoPlayerControl2.xaml.h (150 lines - UI 레이어)
├── VideoPlayerControl2.xaml.cpp (400 lines - UI 구현)
├── VideoPlayerControl2.idl (WinRT 인터페이스 정의)
│
├── src\Playback\
│ ├── PlaybackController.h (80 lines)
│ └── PlaybackController.cpp (300 lines)
│
└── src\Playback\
├── FrameProcessor.h (60 lines)
└── FrameProcessor.cpp (250 lines)
총 새 코드: ~1,240 lines (잘 구조화됨)
🚀 구현 계획
Phase 1: 핵심 클래스 생성 (1-2시간)
-
PlaybackController 스켈레톤 생성
- 기본 클래스 구조
- VavCore 생명주기 메서드
- 타이밍 스레드 스텁
-
FrameProcessor 스켈레톤 생성
- 기본 클래스 구조
- ProcessFrame() 스텁
- 통계 추적
-
VideoPlayerControl2.xaml 생성
- VideoPlayerControl.xaml에서 복사
- x:Class를 VideoPlayerControl2로 업데이트
-
VideoPlayerControl2.xaml.h/cpp 생성
- 기본 XAML UserControl 구조
- PlaybackController + FrameProcessor 컴포지션
- 빈 메서드 스텁
마일스톤: 모든 파일 컴파일, 아직 기능 없음
Phase 2: PlaybackController 구현 (1-2시간)
-
VavCore 생명주기
LoadVideo()- vavcore_create_player, open_fileUnload()- vavcore_close_file, destroy_player- 메타데이터 추출
-
재생 제어
Play()- 타이밍 스레드 시작Pause()- 타이밍 스레드 일시정지Stop()- 타이밍 스레드 중지, 상태 리셋
-
타이밍 스레드
- 고해상도 타이머로 30fps 루프
- 프레임 준비 콜백 호출
- 적절한 스레드 생명주기
마일스톤: 비디오 로드, 재생 시작/중지 가능 (아직 렌더링 없음)
Phase 3: FrameProcessor 구현 (1시간)
-
ProcessFrame() 로직
- m_frameProcessing atomic 플래그
- vavcore_decode_to_surface() 호출
- 렌더러 통합
-
비동기 렌더 완료
- UI 스레드용 DispatcherQueue.TryEnqueue()
- Present() 후 콜백
- 오류 처리
마일스톤: 전체 디코드 → 렌더 파이프라인 작동
Phase 4: VideoPlayerControl2 UI 구현 (1시간)
-
UI 이벤트 핸들러
- Loaded/Unloaded 생명주기
- Play/Pause 버튼 핸들러
- SizeChanged → 렌더러 리사이즈
-
프로퍼티 구현
- VideoSource setter → LoadVideo()
- 상태 쿼리 전달
- AutoPlay 로직
-
UI 업데이트
- 상태 텍스트 업데이트
- AspectFit 렌더링
- 컨트롤 가시성
마일스톤: 완전히 작동하는 VideoPlayerControl2
Phase 5: 통합 테스트 (완료되지 않음 - 향후 작업)
-
테스트 페이지 생성
- MainVideoPage.xaml에 VideoPlayerControl2 추가
- VideoPlayerControl과 나란히 배치 (선택사항)
-
기능 테스트
- 비디오 로드
- Play/Pause/Stop
- Seek
- 윈도우 리사이즈
-
성능 테스트
- VideoPlayerControl과 FPS 비교
- 메모리 사용량 확인
- 프레임 드롭 검증
마일스톤: VideoPlayerControl2가 VideoPlayerControl 성능과 일치
Phase 6: 문서화 & 마이그레이션 (완료되지 않음 - 향후 작업)
-
코드 문서화
- 클래스 레벨 주석 추가
- 주요 메서드 문서화
- 사용 예제 추가
-
마이그레이션 가이드
- VideoPlayerControl과의 차이점 문서화
- 마이그레이션 체크리스트 제공
마일스톤: 프로덕션 사용 준비 완료
✅ 성공 기준
- 기능: VideoPlayerControl2가 VideoPlayerControl과 기능 동등
- 성능: 성능 저하 없음 (≤5% FPS 차이)
- 코드 품질: 50% 라인 감소, 명확한 관심사 분리
- 유지보수성: 이해, 수정, 확장 용이
- 안정성: 새로운 크래시나 버그 발생 없음
🔍 테스트 전략
단위 테스트 (선택사항, 하지만 권장)
- PlaybackController: VavCore 생명주기, 상태 전환
- FrameProcessor: 프레임 처리 로직, 조절
- 독립적 테스트를 위한 Mock VavCore 플레이어
통합 테스트
- 전체 재생 파이프라인 (로드 → 재생 → 렌더)
- 상태 전환 (재생 → 일시정지 → 중지)
- 오류 처리 (잘못된 파일, 디코드 오류)
성능 테스트
- FPS 측정 (30fps 지속)
- 프레임 드롭 수
- 메모리 사용량 프로파일링
- CPU/GPU 활용
🎯 향후 확장 (리팩토링 이후)
VideoPlayerControl2가 안정화되면, 쉬운 확장:
- 다중 디코더 지원 - 디코더 선택 UI 추가
- 성능 오버레이 - 실시간 FPS/통계 표시
- Seek 바 - 비주얼 타임라인 스크러빙
- 재생 목록 - 다중 비디오 큐
- Picture-in-Picture - 분리 가능한 비디오 윈도우
- 녹화 - 디코딩된 프레임을 디스크에 저장
- 효과 - 실시간 비디오 필터 (밝기, 대비 등)
모든 확장은 핵심 아키텍처를 건드리지 않고 추가 가능.
📚 참고
원본 코드 (개발 중 항상 사용 가능):
VideoPlayerControl.xaml.h(219 lines)VideoPlayerControl.xaml.cpp(1,671 lines)- 비교를 위해 나란히 실행 가능
사용된 디자인 패턴:
- 상속보다 컴포지션 - 컴포넌트 컴포지션, 상속 없음
- 단일 책임 원칙 - 각 클래스는 하나의 명확한 작업
- 의존성 주입 - 렌더러를 FrameProcessor로 전달
- 콜백 패턴 - 비동기 완료 콜백
- RAII - 스마트 포인터를 통한 리소스 관리
🚨 위험 완화
- VideoPlayerControl 그대로 유지 - 원본 수정 없음
- 나란히 테스트 - 동작 직접 비교 가능
- 점진적 개발 - 각 Phase는 테스트 가능
- 성능 벤치마크 - 각 Phase에서 측정
- 쉬운 롤백 - 필요시 새 파일 삭제
📊 완료 상태
Phase 1: 핵심 클래스 생성 ✅
- PlaybackController 스켈레톤 생성 완료
- FrameProcessor 스켈레톤 생성 완료
- VideoPlayerControl2.xaml 생성 완료
- VideoPlayerControl2.xaml.h/cpp 생성 완료
Phase 2: vcxproj 통합 ✅
- VideoPlayerControl2 파일들을 vcxproj에 추가
- ItemGroup Label="VideoPlayerControl2"로 구조화
- Headers, Sources, XAML, IDL 모두 추가
Phase 3: 빌드 성공 ✅
- 모든 컴파일 오류 해결
- LogManager 싱글톤 패턴 적용
- WinRT enum if-else 체인 구현
- Clean build 성공
Phase 4: 생성된 파일 검증 ✅
- VideoPlayerControl2.g.h 생성 확인
- VideoPlayerControl2.g.cpp 생성 확인
- VideoPlayerControl2.xaml.g.h 생성 확인
- IVideoPlayerControl2 인터페이스 검증
- WinRT 런타임 클래스 "Vav2Player.VideoPlayerControl2" 확인
향후 작업 (Phase 5-6)
- Phase 5: 통합 테스트 (실제 비디오 재생 검증)
- Phase 6: 문서화 & 마이그레이션 가이드
상태: ✅ Phase 1-4 완료 - 빌드 및 WinRT 생성 성공
다음 단계: Phase 5 - 통합 테스트 (MainVideoPage.xaml에 VideoPlayerControl2 추가 및 실제 비디오 재생 테스트)