Files
video-v1/vav2/docs/completed/windows/VideoPlayerControl2_Refactoring_Plan_2025-10-01.md

19 KiB

VideoPlayerControl2 리팩토링 계획

날짜: 2025-10-01 상태: 완료 (Phase 1-4) 접근 방식: 모듈 분리 (Option 2) + 실용적 아키텍처


🎯 목표

  1. VideoPlayerControl2 생성 - 기존 VideoPlayerControl과 함께 새로운 구현 생성
  2. 모듈화 설계 - 과도한 엔지니어링 없이 책임 분리
  3. 성능 영향 없음 - 현재 성능 유지 또는 개선
  4. 점진적 마이그레이션 - 개발 중 VideoPlayerControl을 참조용으로 유지
  5. 확장성 - 주요 리팩토링 없이 기능 추가 용이

📊 현재 상태 분석

VideoPlayerControl (원본)

  • 크기: 1,671 lines (.cpp) + 219 lines (.h) = 1,890 lines
  • 책임: 7개 이상의 서로 다른 관심사가 혼재
    • VavCore 플레이어 관리
    • GPU/CPU 렌더링 전환
    • 재생 타이밍 제어
    • UI 이벤트 처리
    • 메모리 풀 관리 (미사용)
    • 성능 모니터링 (미사용)
    • D3D Surface 관리

해결할 문제

  1. 너무 많은 책임 - 단일 클래스가 모든 것을 처리
  2. 죽은 코드 - MemoryPool, AdvancedPerformanceMonitor, CPU 렌더링
  3. 복잡한 상태 - 전체에 분산된 11개의 atomic/bool 플래그
  4. 테스트 어려움 - WinUI3와의 강한 결합
  5. 확장 어려움 - 기능 추가 시 모든 것을 건드려야 함

🏗️ 새로운 아키텍처 (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

주요 동기화 지점

  1. 타이밍 스레드OnFrameReady() 매 33.3ms (30fps)
  2. 백그라운드 디코드 → VavCore에서 NV12 텍스처로 디코드
  3. UI 스레드 렌더 → D3D12 렌더 + Present
  4. 완료 콜백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시간)

  1. PlaybackController 스켈레톤 생성

    • 기본 클래스 구조
    • VavCore 생명주기 메서드
    • 타이밍 스레드 스텁
  2. FrameProcessor 스켈레톤 생성

    • 기본 클래스 구조
    • ProcessFrame() 스텁
    • 통계 추적
  3. VideoPlayerControl2.xaml 생성

    • VideoPlayerControl.xaml에서 복사
    • x:Class를 VideoPlayerControl2로 업데이트
  4. VideoPlayerControl2.xaml.h/cpp 생성

    • 기본 XAML UserControl 구조
    • PlaybackController + FrameProcessor 컴포지션
    • 빈 메서드 스텁

마일스톤: 모든 파일 컴파일, 아직 기능 없음


Phase 2: PlaybackController 구현 (1-2시간)

  1. VavCore 생명주기

    • LoadVideo() - vavcore_create_player, open_file
    • Unload() - vavcore_close_file, destroy_player
    • 메타데이터 추출
  2. 재생 제어

    • Play() - 타이밍 스레드 시작
    • Pause() - 타이밍 스레드 일시정지
    • Stop() - 타이밍 스레드 중지, 상태 리셋
  3. 타이밍 스레드

    • 고해상도 타이머로 30fps 루프
    • 프레임 준비 콜백 호출
    • 적절한 스레드 생명주기

마일스톤: 비디오 로드, 재생 시작/중지 가능 (아직 렌더링 없음)


Phase 3: FrameProcessor 구현 (1시간)

  1. ProcessFrame() 로직

    • m_frameProcessing atomic 플래그
    • vavcore_decode_to_surface() 호출
    • 렌더러 통합
  2. 비동기 렌더 완료

    • UI 스레드용 DispatcherQueue.TryEnqueue()
    • Present() 후 콜백
    • 오류 처리

마일스톤: 전체 디코드 → 렌더 파이프라인 작동


Phase 4: VideoPlayerControl2 UI 구현 (1시간)

  1. UI 이벤트 핸들러

    • Loaded/Unloaded 생명주기
    • Play/Pause 버튼 핸들러
    • SizeChanged → 렌더러 리사이즈
  2. 프로퍼티 구현

    • VideoSource setter → LoadVideo()
    • 상태 쿼리 전달
    • AutoPlay 로직
  3. UI 업데이트

    • 상태 텍스트 업데이트
    • AspectFit 렌더링
    • 컨트롤 가시성

마일스톤: 완전히 작동하는 VideoPlayerControl2


Phase 5: 통합 테스트 (완료되지 않음 - 향후 작업)

  1. 테스트 페이지 생성

    • MainVideoPage.xaml에 VideoPlayerControl2 추가
    • VideoPlayerControl과 나란히 배치 (선택사항)
  2. 기능 테스트

    • 비디오 로드
    • Play/Pause/Stop
    • Seek
    • 윈도우 리사이즈
  3. 성능 테스트

    • VideoPlayerControl과 FPS 비교
    • 메모리 사용량 확인
    • 프레임 드롭 검증

마일스톤: VideoPlayerControl2가 VideoPlayerControl 성능과 일치


Phase 6: 문서화 & 마이그레이션 (완료되지 않음 - 향후 작업)

  1. 코드 문서화

    • 클래스 레벨 주석 추가
    • 주요 메서드 문서화
    • 사용 예제 추가
  2. 마이그레이션 가이드

    • VideoPlayerControl과의 차이점 문서화
    • 마이그레이션 체크리스트 제공

마일스톤: 프로덕션 사용 준비 완료


성공 기준

  1. 기능: VideoPlayerControl2가 VideoPlayerControl과 기능 동등
  2. 성능: 성능 저하 없음 (≤5% FPS 차이)
  3. 코드 품질: 50% 라인 감소, 명확한 관심사 분리
  4. 유지보수성: 이해, 수정, 확장 용이
  5. 안정성: 새로운 크래시나 버그 발생 없음

🔍 테스트 전략

단위 테스트 (선택사항, 하지만 권장)

  • PlaybackController: VavCore 생명주기, 상태 전환
  • FrameProcessor: 프레임 처리 로직, 조절
  • 독립적 테스트를 위한 Mock VavCore 플레이어

통합 테스트

  • 전체 재생 파이프라인 (로드 → 재생 → 렌더)
  • 상태 전환 (재생 → 일시정지 → 중지)
  • 오류 처리 (잘못된 파일, 디코드 오류)

성능 테스트

  • FPS 측정 (30fps 지속)
  • 프레임 드롭 수
  • 메모리 사용량 프로파일링
  • CPU/GPU 활용

🎯 향후 확장 (리팩토링 이후)

VideoPlayerControl2가 안정화되면, 쉬운 확장:

  1. 다중 디코더 지원 - 디코더 선택 UI 추가
  2. 성능 오버레이 - 실시간 FPS/통계 표시
  3. Seek 바 - 비주얼 타임라인 스크러빙
  4. 재생 목록 - 다중 비디오 큐
  5. Picture-in-Picture - 분리 가능한 비디오 윈도우
  6. 녹화 - 디코딩된 프레임을 디스크에 저장
  7. 효과 - 실시간 비디오 필터 (밝기, 대비 등)

모든 확장은 핵심 아키텍처를 건드리지 않고 추가 가능.


📚 참고

원본 코드 (개발 중 항상 사용 가능):

  • VideoPlayerControl.xaml.h (219 lines)
  • VideoPlayerControl.xaml.cpp (1,671 lines)
  • 비교를 위해 나란히 실행 가능

사용된 디자인 패턴:

  • 상속보다 컴포지션 - 컴포넌트 컴포지션, 상속 없음
  • 단일 책임 원칙 - 각 클래스는 하나의 명확한 작업
  • 의존성 주입 - 렌더러를 FrameProcessor로 전달
  • 콜백 패턴 - 비동기 완료 콜백
  • RAII - 스마트 포인터를 통한 리소스 관리

🚨 위험 완화

  1. VideoPlayerControl 그대로 유지 - 원본 수정 없음
  2. 나란히 테스트 - 동작 직접 비교 가능
  3. 점진적 개발 - 각 Phase는 테스트 가능
  4. 성능 벤치마크 - 각 Phase에서 측정
  5. 쉬운 롤백 - 필요시 새 파일 삭제

📊 완료 상태

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 추가 및 실제 비디오 재생 테스트)