Build unit test environment
This commit is contained in:
@@ -9,6 +9,29 @@
|
||||
- **방식**: "삭제 우선" - 고급 파이프라인 전면 제거
|
||||
- **이유**: 방어 코딩으로 증상만 치료하는 대신 근본 원인 해결
|
||||
|
||||
### 📋 **최우선 작업 순서 (2025-09-23 업데이트)**
|
||||
|
||||
#### **✅ MAJOR_REFACTORING_GUIDE.md 완료** (완료됨)
|
||||
- [x] 복잡한 파이프라인 파일들 삭제 (ThreadedDecoder, OverlappedProcessor, DependencyScheduler)
|
||||
- [x] VideoPlayerControl.xaml.h 멤버 변수 대폭 정리 (10개 이상 → 3-4개)
|
||||
- [x] VideoPlayerControl.xaml.cpp ProcessSingleFrame() 단순화 (1000줄 → 25줄)
|
||||
- [x] 빌드 테스트 및 기본 비디오 재생 동작 확인
|
||||
|
||||
#### **✅ GPU 파이프라인 재설계 완료** (완료됨)
|
||||
- [x] 단순 GPU 파이프라인 설계 (CPU Thread → GPU Thread)
|
||||
- [x] SimpleGPURenderer 구현
|
||||
- [x] CPU-GPU 하이브리드 fallback 구조
|
||||
- [x] 성능 최적화 및 안정성 확보
|
||||
|
||||
#### **🔥 현재 작업: Unit Test 구현** (최고 우선순위)
|
||||
- **참조 문서**: [UNIT_TEST_REFACTORING_PLAN.md](./UNIT_TEST_REFACTORING_PLAN.md)
|
||||
- **의사결정 필요**: Option A (인터페이스+Mock) vs Option B (직접 테스트)
|
||||
- [ ] 인터페이스 리팩토링 (Option A 선택 시)
|
||||
- [ ] Mock 시스템 구축 (MockWebMFileReader, MockVideoRenderer)
|
||||
- [ ] 핵심 컴포넌트 테스트 작성 (WebMFileReader, AV1Decoder, VideoDecoderFactory)
|
||||
- [ ] GPU 렌더링 테스트 (SimpleGPURenderer)
|
||||
- [ ] 통합 테스트 구현 (전체 파이프라인 검증)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 현재 작업 단계: 고급 파이프라인 디버깅 및 검증 (CRITICAL)
|
||||
|
||||
717
vav2/UNIT_TEST_REFACTORING_PLAN.md
Normal file
717
vav2/UNIT_TEST_REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,717 @@
|
||||
# 🧪 Unit Test 구현 계획 - 2025-09-23 개정판
|
||||
|
||||
**✅ 전제 조건**: MAJOR_REFACTORING_GUIDE 완료, GPU 파이프라인 재설계 완료
|
||||
**🎯 현재 목표**: 단순화된 아키텍처 (734줄)에 대한 포괄적 Unit Test 구현
|
||||
|
||||
---
|
||||
|
||||
## 🔍 **현재 상황 (2025-09-23)**
|
||||
|
||||
### **✅ 완료된 작업**
|
||||
- **MAJOR_REFACTORING_GUIDE**: 복잡한 파이프라인 제거, ProcessSingleFrame() 25줄 단순화
|
||||
- **GPU 파이프라인**: SimpleGPURenderer 구현, CPU-GPU 하이브리드 아키텍처
|
||||
- **기본 Unit Test**: VideoTypesTest.cpp 완료
|
||||
- **헤드리스 프로젝트**: 빌드 및 실행 성공
|
||||
|
||||
### **📋 현재 Unit Test 구조**
|
||||
```
|
||||
D:\Project\video-av1\vav2\Vav2Player\Vav2Player\
|
||||
├── Vav2UnitTest.vcxproj # ✅ 기존 테스트 프로젝트
|
||||
├── unit-test/
|
||||
│ ├── pch.h / pch.cpp # ✅ 테스트 전용 PCH
|
||||
│ └── VideoTypesTest.cpp # ✅ 기본 데이터 구조체 테스트
|
||||
└── x64/Debug/UnitTest/ # ✅ 빌드 출력
|
||||
```
|
||||
|
||||
### **🎯 추가 필요한 테스트**
|
||||
- **WebMFileReader**: 파일 파싱 로직 (실제 구체 클래스 테스트)
|
||||
- **AV1Decoder**: 디코딩 로직
|
||||
- **VideoDecoderFactory**: 팩토리 패턴
|
||||
- **SimpleGPURenderer**: GPU 렌더링 (스텁 모드)
|
||||
- **통합 테스트**: 전체 파이프라인
|
||||
|
||||
---
|
||||
|
||||
## 🔍 **인터페이스 분석 (Unit Test 전 필수 검토)**
|
||||
|
||||
### **현재 인터페이스 상황**
|
||||
```cpp
|
||||
✅ IVideoDecoder.h // 이미 인터페이스화 완료
|
||||
❌ WebMFileReader.h // 구체 클래스, 인터페이스 없음
|
||||
❌ SimpleGPURenderer.h // 구체 클래스, 인터페이스 없음
|
||||
✅ VideoDecoderFactory.h // 팩토리 패턴, 인터페이스 기반
|
||||
```
|
||||
|
||||
### **🔧 Option A: 인터페이스 추가 리팩토링 (권장)**
|
||||
|
||||
#### **A.1 IWebMFileReader 인터페이스 생성**
|
||||
```cpp
|
||||
// src/FileIO/IWebMFileReader.h (신규 생성)
|
||||
#pragma once
|
||||
#include "../Common/VideoTypes.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
class IWebMFileReader {
|
||||
public:
|
||||
virtual ~IWebMFileReader() = default;
|
||||
|
||||
// 파일 열기/닫기
|
||||
virtual bool OpenFile(const std::string& file_path) = 0;
|
||||
virtual void CloseFile() = 0;
|
||||
virtual bool IsFileOpen() const = 0;
|
||||
|
||||
// 파일 및 스트림 정보
|
||||
virtual const VideoMetadata& GetVideoMetadata() const = 0;
|
||||
virtual std::string GetFilePath() const = 0;
|
||||
|
||||
// 비디오 트랙 관리
|
||||
virtual std::vector<VideoTrackInfo> GetVideoTracks() const = 0;
|
||||
virtual bool SelectVideoTrack(int track_number) = 0;
|
||||
|
||||
// 패킷 읽기
|
||||
virtual bool ReadNextPacket(VideoPacket& packet) = 0;
|
||||
virtual bool SeekToTime(double time_seconds) = 0;
|
||||
virtual bool SeekToFrame(uint64_t frame_number) = 0;
|
||||
virtual bool Reset() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### **A.2 WebMFileReader 리팩토링**
|
||||
```cpp
|
||||
// src/FileIO/WebMFileReader.h 수정
|
||||
class WebMFileReader : public IWebMFileReader {
|
||||
// 기존 구현을 virtual 메서드로 변경
|
||||
// 인터페이스 상속으로 변경
|
||||
};
|
||||
```
|
||||
|
||||
#### **A.3 IVideoRenderer 인터페이스 생성**
|
||||
```cpp
|
||||
// src/Rendering/IVideoRenderer.h (신규 생성)
|
||||
#pragma once
|
||||
#include "../Common/VideoTypes.h"
|
||||
#include <windows.h>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
class IVideoRenderer {
|
||||
public:
|
||||
virtual ~IVideoRenderer() = default;
|
||||
|
||||
// 초기화
|
||||
virtual HRESULT Initialize(uint32_t width, uint32_t height) = 0;
|
||||
virtual bool IsInitialized() const = 0;
|
||||
virtual void Cleanup() = 0;
|
||||
|
||||
// 렌더링
|
||||
virtual bool TryRenderFrame(const VideoFrame& frame) = 0;
|
||||
virtual HRESULT RenderVideoFrame(const VideoFrame& frame) = 0;
|
||||
|
||||
// 설정
|
||||
virtual void SetAspectFitMode(bool enabled) = 0;
|
||||
virtual void UpdateContainerSize(uint32_t width, uint32_t height) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### **🚀 Option B: 인터페이스 없이 직접 테스트 (빠른 시작)**
|
||||
|
||||
#### **B.1 장점**
|
||||
- 즉시 테스트 작성 시작 가능
|
||||
- 기존 코드 수정 없음
|
||||
- 단순한 접근법
|
||||
|
||||
#### **B.2 단점**
|
||||
- Mock 생성 어려움
|
||||
- 의존성 주입 불가능
|
||||
- 테스트 격리 한계
|
||||
|
||||
---
|
||||
|
||||
## 📋 **단계별 구현 계획**
|
||||
|
||||
### **🔄 Phase 0: 인터페이스 리팩토링 (선택적)**
|
||||
|
||||
#### **0.1 인터페이스 필요성 평가**
|
||||
```cpp
|
||||
// 테스트 복잡도 vs 인터페이스 도입 비용
|
||||
Component | Mock 필요도 | 인터페이스 우선순위
|
||||
--------------------|----------|----------------
|
||||
WebMFileReader | 높음 | ⭐⭐⭐ 권장
|
||||
SimpleGPURenderer | 중간 | ⭐⭐ 선택적
|
||||
VideoDecoderFactory | 낮음 | ⭐ 이미 충분
|
||||
```
|
||||
|
||||
#### **0.2 인터페이스 리팩토링 (선택 시)**
|
||||
1. **IWebMFileReader.h 생성** (2시간)
|
||||
2. **WebMFileReader.h 수정** (1시간)
|
||||
3. **IVideoRenderer.h 생성** (1시간)
|
||||
4. **SimpleGPURenderer.h 수정** (1시간)
|
||||
5. **VideoPlayerControl.xaml.h 의존성 업데이트** (30분)
|
||||
6. **빌드 테스트** (30분)
|
||||
|
||||
**총 예상 시간: 6시간**
|
||||
|
||||
### **🔧 Phase 1: 핵심 컴포넌트 테스트 (우선순위 1)**
|
||||
|
||||
#### **1.1 WebMFileReader 테스트 (인터페이스 기반)**
|
||||
```cpp
|
||||
// unit-test/WebMFileReaderTest.cpp
|
||||
#include "pch.h"
|
||||
#include "../src/FileIO/WebMFileReader.h"
|
||||
|
||||
// Option A를 선택한 경우: Mock을 사용한 테스트
|
||||
#ifdef USE_INTERFACE_APPROACH
|
||||
#include "../unit-test/mocks/MockWebMFileReader.h"
|
||||
#endif
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
using namespace Vav2Player;
|
||||
|
||||
TEST_CLASS(WebMFileReaderTest)
|
||||
{
|
||||
public:
|
||||
// Option A: 인터페이스 기반 테스트 (Mock 사용 가능)
|
||||
TEST_METHOD(OpenFile_ValidFile_ShouldReturnTrue_WithMock)
|
||||
{
|
||||
#ifdef USE_INTERFACE_APPROACH
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.SetOpenFileResult(true);
|
||||
|
||||
// Act
|
||||
bool result = mockReader.OpenFile("test.webm");
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Option B: 직접 테스트 (인터페이스 없이)
|
||||
TEST_METHOD(Constructor_Default_ShouldInitializeCorrectly)
|
||||
{
|
||||
// Arrange & Act
|
||||
WebMFileReader reader;
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(reader.IsFileOpen());
|
||||
}
|
||||
|
||||
TEST_METHOD(OpenFile_NonExistentFile_ShouldReturnFalse)
|
||||
{
|
||||
// Arrange
|
||||
WebMFileReader reader;
|
||||
std::string nonExistentFile = "nonexistent_file.webm";
|
||||
|
||||
// Act
|
||||
bool result = reader.OpenFile(nonExistentFile);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
}
|
||||
|
||||
TEST_METHOD(OpenFile_EmptyFilename_ShouldReturnFalse)
|
||||
{
|
||||
// Arrange
|
||||
WebMFileReader reader;
|
||||
std::string emptyFile = "";
|
||||
|
||||
// Act
|
||||
bool result = reader.OpenFile(emptyFile);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### **1.2 AV1Decoder 테스트**
|
||||
```cpp
|
||||
// unit-test/AV1DecoderTest.cpp
|
||||
#include "pch.h"
|
||||
#include "../src/Decoder/AV1Decoder.h"
|
||||
|
||||
TEST_CLASS(AV1DecoderTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(Constructor_Default_ShouldInitializeCorrectly)
|
||||
{
|
||||
// Arrange & Act
|
||||
AV1Decoder decoder;
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(decoder.IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(Initialize_ValidMetadata_ShouldReturnTrue)
|
||||
{
|
||||
// Arrange
|
||||
AV1Decoder decoder;
|
||||
VideoMetadata metadata;
|
||||
metadata.width = 1920;
|
||||
metadata.height = 1080;
|
||||
metadata.frame_rate = 30.0;
|
||||
metadata.codec_type = VideoCodecType::AV1;
|
||||
|
||||
// Act
|
||||
bool result = decoder.Initialize(metadata);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::IsTrue(decoder.IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(Initialize_InvalidCodecType_ShouldReturnFalse)
|
||||
{
|
||||
// Arrange
|
||||
AV1Decoder decoder;
|
||||
VideoMetadata metadata;
|
||||
metadata.codec_type = VideoCodecType::VP9; // Wrong codec
|
||||
|
||||
// Act
|
||||
bool result = decoder.Initialize(metadata);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### **1.3 VideoDecoderFactory 테스트**
|
||||
```cpp
|
||||
// unit-test/VideoDecoderFactoryTest.cpp
|
||||
#include "pch.h"
|
||||
#include "../src/Decoder/VideoDecoderFactory.h"
|
||||
|
||||
TEST_CLASS(VideoDecoderFactoryTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(CreateDecoder_AV1Software_ShouldReturnAV1Decoder)
|
||||
{
|
||||
// Act
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(
|
||||
VideoCodecType::AV1,
|
||||
VideoDecoderFactory::DecoderType::SOFTWARE
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert::IsNotNull(decoder.get());
|
||||
}
|
||||
|
||||
TEST_METHOD(CreateDecoder_AV1Auto_ShouldReturnDecoder)
|
||||
{
|
||||
// Act
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(
|
||||
VideoCodecType::AV1,
|
||||
VideoDecoderFactory::DecoderType::AUTO
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert::IsNotNull(decoder.get());
|
||||
}
|
||||
|
||||
TEST_METHOD(IsCodecSupported_AV1_ShouldReturnTrue)
|
||||
{
|
||||
// Act
|
||||
bool isSupported = VideoDecoderFactory::IsCodecSupported(VideoCodecType::AV1);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(isSupported);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### **🔧 Phase 1.5: Mock 시스템 구현 (Option A 선택 시)**
|
||||
|
||||
#### **1.5.1 MockWebMFileReader 구현**
|
||||
```cpp
|
||||
// unit-test/mocks/MockWebMFileReader.h
|
||||
#pragma once
|
||||
#include "../../src/FileIO/IWebMFileReader.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
class MockWebMFileReader : public IWebMFileReader {
|
||||
private:
|
||||
bool m_openFileResult = true;
|
||||
bool m_isFileOpen = false;
|
||||
VideoMetadata m_metadata;
|
||||
std::vector<VideoPacket> m_testPackets;
|
||||
size_t m_currentPacketIndex = 0;
|
||||
|
||||
public:
|
||||
// Mock 설정 메서드들
|
||||
void SetOpenFileResult(bool result) { m_openFileResult = result; }
|
||||
void SetMetadata(const VideoMetadata& metadata) { m_metadata = metadata; }
|
||||
void AddTestPacket(const VideoPacket& packet) { m_testPackets.push_back(packet); }
|
||||
void ResetPackets() { m_testPackets.clear(); m_currentPacketIndex = 0; }
|
||||
|
||||
// IWebMFileReader 구현
|
||||
bool OpenFile(const std::string& file_path) override {
|
||||
m_isFileOpen = m_openFileResult;
|
||||
return m_openFileResult;
|
||||
}
|
||||
|
||||
void CloseFile() override {
|
||||
m_isFileOpen = false;
|
||||
m_currentPacketIndex = 0;
|
||||
}
|
||||
|
||||
bool IsFileOpen() const override {
|
||||
return m_isFileOpen;
|
||||
}
|
||||
|
||||
const VideoMetadata& GetVideoMetadata() const override {
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
std::string GetFilePath() const override {
|
||||
return "mock_file.webm";
|
||||
}
|
||||
|
||||
std::vector<VideoTrackInfo> GetVideoTracks() const override {
|
||||
std::vector<VideoTrackInfo> tracks;
|
||||
VideoTrackInfo track;
|
||||
track.track_number = 1;
|
||||
track.codec_type = VideoCodecType::AV1;
|
||||
track.width = 1920;
|
||||
track.height = 1080;
|
||||
tracks.push_back(track);
|
||||
return tracks;
|
||||
}
|
||||
|
||||
bool SelectVideoTrack(int track_number) override {
|
||||
return track_number == 1;
|
||||
}
|
||||
|
||||
bool ReadNextPacket(VideoPacket& packet) override {
|
||||
if (m_currentPacketIndex >= m_testPackets.size()) {
|
||||
return false;
|
||||
}
|
||||
packet = m_testPackets[m_currentPacketIndex++];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeekToTime(double time_seconds) override {
|
||||
m_currentPacketIndex = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeekToFrame(uint64_t frame_number) override {
|
||||
m_currentPacketIndex = static_cast<size_t>(frame_number);
|
||||
return m_currentPacketIndex < m_testPackets.size();
|
||||
}
|
||||
|
||||
bool Reset() override {
|
||||
m_currentPacketIndex = 0;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### **1.5.2 MockVideoRenderer 구현**
|
||||
```cpp
|
||||
// unit-test/mocks/MockVideoRenderer.h
|
||||
#pragma once
|
||||
#include "../../src/Rendering/IVideoRenderer.h"
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
class MockVideoRenderer : public IVideoRenderer {
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
bool m_tryRenderResult = true;
|
||||
HRESULT m_renderResult = S_OK;
|
||||
uint32_t m_width = 0;
|
||||
uint32_t m_height = 0;
|
||||
|
||||
public:
|
||||
// Mock 설정
|
||||
void SetTryRenderResult(bool result) { m_tryRenderResult = result; }
|
||||
void SetRenderResult(HRESULT result) { m_renderResult = result; }
|
||||
|
||||
// 테스트 검증용
|
||||
uint32_t GetLastWidth() const { return m_width; }
|
||||
uint32_t GetLastHeight() const { return m_height; }
|
||||
|
||||
// IVideoRenderer 구현
|
||||
HRESULT Initialize(uint32_t width, uint32_t height) override {
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_initialized = (width > 0 && height > 0);
|
||||
return m_initialized ? S_OK : E_INVALIDARG;
|
||||
}
|
||||
|
||||
bool IsInitialized() const override {
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
void Cleanup() override {
|
||||
m_initialized = false;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
}
|
||||
|
||||
bool TryRenderFrame(const VideoFrame& frame) override {
|
||||
return m_initialized && m_tryRenderResult;
|
||||
}
|
||||
|
||||
HRESULT RenderVideoFrame(const VideoFrame& frame) override {
|
||||
return m_initialized ? m_renderResult : E_NOT_VALID_STATE;
|
||||
}
|
||||
|
||||
void SetAspectFitMode(bool enabled) override {
|
||||
// Mock implementation - do nothing
|
||||
}
|
||||
|
||||
void UpdateContainerSize(uint32_t width, uint32_t height) override {
|
||||
// Mock implementation - store values
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### **⚡ Phase 2: GPU 렌더링 테스트**
|
||||
|
||||
#### **2.1 SimpleGPURenderer 테스트 (스텁 모드)**
|
||||
```cpp
|
||||
// unit-test/SimpleGPURendererTest.cpp
|
||||
#include "pch.h"
|
||||
#include "../src/Rendering/SimpleGPURenderer.h"
|
||||
|
||||
TEST_CLASS(SimpleGPURendererTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(Constructor_Default_ShouldInitializeCorrectly)
|
||||
{
|
||||
// Arrange & Act
|
||||
SimpleGPURenderer renderer;
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(renderer.IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(Initialize_ValidDimensions_ShouldReturnTrue)
|
||||
{
|
||||
// Arrange
|
||||
SimpleGPURenderer renderer;
|
||||
|
||||
// Act
|
||||
HRESULT hr = renderer.Initialize(1920, 1080);
|
||||
|
||||
// Assert
|
||||
// Note: May fail in headless environment, but should not crash
|
||||
// Success depends on D3D12 availability
|
||||
Assert::IsTrue(SUCCEEDED(hr) || hr == DXGI_ERROR_UNSUPPORTED);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### **🧪 Phase 3: 통합 테스트**
|
||||
|
||||
#### **3.1 간단한 통합 테스트**
|
||||
```cpp
|
||||
// unit-test/IntegrationTest.cpp
|
||||
#include "pch.h"
|
||||
#include "../src/FileIO/WebMFileReader.h"
|
||||
#include "../src/Decoder/VideoDecoderFactory.h"
|
||||
|
||||
TEST_CLASS(IntegrationTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(BasicPipeline_ComponentCreation_ShouldWork)
|
||||
{
|
||||
// Arrange & Act
|
||||
auto fileReader = std::make_unique<WebMFileReader>();
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(
|
||||
VideoCodecType::AV1,
|
||||
VideoDecoderFactory::DecoderType::SOFTWARE
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert::IsNotNull(fileReader.get());
|
||||
Assert::IsNotNull(decoder.get());
|
||||
}
|
||||
|
||||
TEST_METHOD(VideoFrame_AllocationAndDeallocation_ShouldNotLeak)
|
||||
{
|
||||
// Memory leak test
|
||||
for (int i = 0; i < 100; i++) {
|
||||
VideoFrame frame;
|
||||
bool result = frame.AllocateYUV420P(1920, 1080);
|
||||
Assert::IsTrue(result);
|
||||
}
|
||||
// Frames should automatically deallocate
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 **최종 테스트 프로젝트 구조**
|
||||
|
||||
### **목표 구조 (Phase 3 완료 후)**
|
||||
```
|
||||
D:\Project\video-av1\vav2\Vav2Player\Vav2Player\
|
||||
├── Vav2UnitTest.vcxproj
|
||||
├── unit-test/
|
||||
│ ├── pch.h / pch.cpp # ✅ 기존
|
||||
│ ├── VideoTypesTest.cpp # ✅ 기존
|
||||
│ ├── WebMFileReaderTest.cpp # 🆕 Phase 1
|
||||
│ ├── AV1DecoderTest.cpp # 🆕 Phase 1
|
||||
│ ├── VideoDecoderFactoryTest.cpp # 🆕 Phase 1
|
||||
│ ├── SimpleGPURendererTest.cpp # 🆕 Phase 2
|
||||
│ └── IntegrationTest.cpp # 🆕 Phase 3
|
||||
└── x64/Debug/UnitTest/
|
||||
```
|
||||
|
||||
### **프로젝트 파일 업데이트**
|
||||
```xml
|
||||
<!-- Vav2UnitTest.vcxproj에 추가할 항목들 -->
|
||||
<ClCompile Include="unit-test\WebMFileReaderTest.cpp" />
|
||||
<ClCompile Include="unit-test\AV1DecoderTest.cpp" />
|
||||
<ClCompile Include="unit-test\VideoDecoderFactoryTest.cpp" />
|
||||
<ClCompile Include="unit-test\SimpleGPURendererTest.cpp" />
|
||||
<ClCompile Include="unit-test\IntegrationTest.cpp" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ **구현 상세**
|
||||
|
||||
### **1. Mock 없는 실제 클래스 테스트**
|
||||
- **접근법**: 구체 클래스를 직접 테스트, Mock 복잡성 제거
|
||||
- **외부 의존성**: 실패 케이스 테스트로 격리 (존재하지 않는 파일 등)
|
||||
- **GPU 의존성**: 실패해도 크래시하지 않는지 검증
|
||||
|
||||
### **2. 테스트 데이터 최소화**
|
||||
- **파일 의존성**: 실제 파일 대신 존재하지 않는 파일명으로 에러 케이스 테스트
|
||||
- **메모리 테스트**: 동적 할당/해제 반복으로 메모리 누수 검증
|
||||
- **단순한 입력**: 기본값과 경계값을 활용한 테스트
|
||||
|
||||
### **3. CI/CD 통합**
|
||||
```bash
|
||||
# 자동 테스트 스크립트
|
||||
cd "D:\Project\video-av1\vav2\Vav2Player\Vav2Player"
|
||||
|
||||
# 1. 유닛 테스트 빌드
|
||||
MSBuild Vav2UnitTest.vcxproj //p:Configuration=Debug //p:Platform=x64 //v:minimal
|
||||
|
||||
# 2. 테스트 실행
|
||||
vstest.console.exe "x64\Debug\UnitTest\Vav2UnitTest.dll"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤔 **의사결정 가이드**
|
||||
|
||||
### **Option A vs Option B 선택 기준**
|
||||
|
||||
#### **Option A 선택 (인터페이스 + Mock)**
|
||||
**다음 조건 중 하나라도 해당하면 권장:**
|
||||
- 의존성 주입 패턴을 사용하고 싶은 경우
|
||||
- 복잡한 시나리오 테스트가 필요한 경우
|
||||
- 외부 의존성 격리가 중요한 경우
|
||||
- 장기적인 테스트 확장성이 필요한 경우
|
||||
|
||||
**예상 시간**: Phase 0 (6시간) + Phase 1-3 (4일) = **5일**
|
||||
|
||||
#### **Option B 선택 (직접 테스트)**
|
||||
**다음 조건에 해당하면 권장:**
|
||||
- 빠른 테스트 구현이 우선인 경우
|
||||
- 기존 코드 수정을 최소화하려는 경우
|
||||
- 단순한 기능 검증만 필요한 경우
|
||||
- 리소스(시간/인력)가 제한적인 경우
|
||||
|
||||
**예상 시간**: Phase 1-3 (4일) = **4일**
|
||||
|
||||
### **권장사항**
|
||||
```
|
||||
현재 상황 고려 시 🎯 Option A 권장
|
||||
|
||||
이유:
|
||||
1. WebMFileReader는 파일 I/O 의존성이 높음
|
||||
2. GPU 렌더링은 환경 의존성이 높음
|
||||
3. 장기적인 유지보수성 필요
|
||||
4. MAJOR_REFACTORING_GUIDE 완료로 안정적인 기반 확보됨
|
||||
```
|
||||
|
||||
## 🕐 **실행 스케줄**
|
||||
|
||||
### **Option A 선택 시 (권장)**
|
||||
- **Phase 0**: 1일 (인터페이스 리팩토링)
|
||||
- **Phase 1.5**: 1일 (Mock 시스템)
|
||||
- **Phase 1**: 2일 (핵심 컴포넌트 테스트)
|
||||
- **Phase 2**: 1일 (GPU 렌더링 테스트)
|
||||
- **Phase 3**: 1일 (통합 테스트)
|
||||
|
||||
**총 6일**
|
||||
|
||||
### **Option B 선택 시**
|
||||
- **Phase 1**: 2-3일 (핵심 컴포넌트 테스트)
|
||||
- **Phase 2**: 1일 (GPU 렌더링 테스트)
|
||||
- **Phase 3**: 1일 (통합 테스트)
|
||||
|
||||
**총 4-5일**
|
||||
|
||||
### **우선순위**
|
||||
1. **의사결정**: Option A vs B 선택
|
||||
2. **WebMFileReaderTest.cpp** (가장 핵심적)
|
||||
3. **AV1DecoderTest.cpp** (디코딩 로직)
|
||||
4. **VideoDecoderFactoryTest.cpp** (팩토리 패턴)
|
||||
5. **SimpleGPURendererTest.cpp** (GPU 안정성)
|
||||
6. **IntegrationTest.cpp** (전체 검증)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **성공 지표**
|
||||
|
||||
### **정량적 목표**
|
||||
- **테스트 커버리지**: 핵심 public 메서드 80% 이상
|
||||
- **테스트 실행 시간**: 30초 이내
|
||||
- **테스트 파일 수**: 6개 (기존 1개 + 신규 5개)
|
||||
- **빌드 성공률**: 100%
|
||||
|
||||
### **정성적 목표**
|
||||
- **크래시 방지**: 모든 테스트가 크래시 없이 완료
|
||||
- **회귀 방지**: 기존 기능 변경 시 테스트로 검증 가능
|
||||
- **유지보수성**: 새로운 컴포넌트 추가 시 테스트 패턴 재사용 가능
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ **주의사항**
|
||||
|
||||
### **1. 환경 의존성 최소화**
|
||||
- **D3D12**: GPU 없는 환경에서도 실패하지 않도록 처리
|
||||
- **dav1d**: 라이브러리 로드 실패 시에도 테스트 완료
|
||||
- **MediaFoundation**: 지원되지 않는 환경에서 graceful failure
|
||||
|
||||
### **2. 테스트 격리**
|
||||
- **파일 시스템**: 실제 파일에 의존하지 않는 테스트 우선
|
||||
- **전역 상태**: 테스트 간 상호 영향 최소화
|
||||
- **메모리**: 각 테스트 후 리소스 정리
|
||||
|
||||
### **3. 실용적 접근**
|
||||
- **완벽한 Coverage보다 핵심 기능 안정성**
|
||||
- **복잡한 Mock보다 간단한 실제 테스트**
|
||||
- **외부 의존성 Mock보다 에러 케이스 활용**
|
||||
|
||||
---
|
||||
|
||||
**🎯 핵심 메시지: "단순하고 실용적인 테스트로 안정성 확보"**
|
||||
|
||||
*문서 작성일: 2025-09-23*
|
||||
*이전 문서 대체: UNIT_TEST_REFACTORING_PLAN.md (구식)*
|
||||
@@ -63,7 +63,7 @@
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)unit-test;$(ProjectDir)src;D:\Project\video-av1\include\libwebm;D:\Project\video-av1\include\dav1d;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;UNIT_TEST_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<UseFullPaths>true</UseFullPaths>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
@@ -71,7 +71,7 @@
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<AdditionalLibraryDirectories>D:\Project\video-av1\lib\libwebm;D:\Project\video-av1\lib\dav1d;$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>webm.lib;dav1d.lib;mfplat.lib;mfuuid.lib;d3d12.lib;dxgi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>webm-debug.lib;dav1d-debug.lib;mfplat.lib;mfuuid.lib;d3d12.lib;dxgi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
@@ -83,7 +83,7 @@
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)unit-test;$(ProjectDir)src;D:\Project\video-av1\include\libwebm;D:\Project\video-av1\include\dav1d;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;UNIT_TEST_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<UseFullPaths>true</UseFullPaths>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
@@ -98,6 +98,8 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="unit-test\pch.h" />
|
||||
<ClInclude Include="unit-test\MockWebMFileReader.h" />
|
||||
<ClInclude Include="unit-test\MockVideoRenderer.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="unit-test\pch.cpp">
|
||||
@@ -105,20 +107,30 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="unit-test\VideoTypesTest.cpp" />
|
||||
<ClCompile Include="unit-test\MockWebMFileReader.cpp" />
|
||||
<ClCompile Include="unit-test\MockVideoRenderer.cpp" />
|
||||
<ClCompile Include="unit-test\WebMFileReaderTest.cpp" />
|
||||
<ClCompile Include="unit-test\AV1DecoderTest.cpp" />
|
||||
<ClCompile Include="unit-test\VideoRendererTest.cpp" />
|
||||
<ClCompile Include="unit-test\VideoPlayerControlTest.cpp" />
|
||||
</ItemGroup>
|
||||
<!-- Common source files (shared with main project) -->
|
||||
<!-- Only include basic files for unit testing -->
|
||||
<!-- Include files needed for Unit Testing -->
|
||||
<ItemGroup Label="Common Sources">
|
||||
<!-- Basic utility classes only -->
|
||||
<ClCompile Include="src\Common\FramePool.cpp" />
|
||||
<ClCompile Include="src\Common\PacketPool.cpp" />
|
||||
<!-- Skip all complex components with D3D12/WinUI dependencies for now -->
|
||||
<!-- <ClCompile Include="src\Common\PermissionUtils.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Decoder\VideoDecoderFactory.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\FileIO\WebMFileReader.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Output\FileOutput.cpp" /> -->
|
||||
<!-- Core components for testing -->
|
||||
<ClCompile Include="src\Decoder\VideoDecoderFactory.cpp" />
|
||||
<ClCompile Include="src\Decoder\AV1Decoder.cpp" />
|
||||
<ClCompile Include="src\Decoder\MediaFoundationAV1Decoder.cpp" />
|
||||
<ClCompile Include="src\FileIO\WebMFileReader.cpp" />
|
||||
|
||||
<!-- Skip utility classes that don't exist yet -->
|
||||
<!-- <ClCompile Include="src\Common\FramePool.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Common\PacketPool.cpp" /> -->
|
||||
|
||||
<!-- Skip complex pipeline components with WinUI dependencies for now -->
|
||||
<!-- <ClCompile Include="src\Rendering\SimpleGPURenderer.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Rendering\D3D12VideoRenderer.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Pipeline\DependencyScheduler.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Pipeline\FrameBuffer.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Pipeline\OverlappedProcessor.cpp" /> -->
|
||||
<!-- <ClCompile Include="src\Pipeline\StreamingPipeline.cpp" /> -->
|
||||
</ItemGroup>
|
||||
|
||||
@@ -588,7 +588,7 @@ namespace winrt::Vav2Player::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT hr = m_gpuRenderer->Initialize(VideoSwapChainPanel(), containerWidth, containerHeight);
|
||||
HRESULT hr = m_gpuRenderer->InitializeWithSwapChain(VideoSwapChainPanel(), containerWidth, containerHeight);
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
// GPU initialization failed - temporarily use CPU rendering
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoPlayerControl.g.h"
|
||||
#include "src/FileIO/IWebMFileReader.h"
|
||||
#include "src/FileIO/WebMFileReader.h"
|
||||
#include "src/Decoder/VideoDecoderFactory.h"
|
||||
#include "src/Common/VideoTypes.h"
|
||||
#include "src/Rendering/IVideoRenderer.h"
|
||||
#include "src/Rendering/SimpleGPURenderer.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -58,13 +60,13 @@ namespace winrt::Vav2Player::implementation
|
||||
|
||||
private:
|
||||
// Video processing components
|
||||
std::unique_ptr<WebMFileReader> m_fileReader;
|
||||
std::unique_ptr<IWebMFileReader> m_fileReader;
|
||||
std::unique_ptr<IVideoDecoder> m_decoder;
|
||||
|
||||
// Video rendering components
|
||||
winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap m_renderBitmap{ nullptr };
|
||||
std::vector<uint8_t> m_bgraBuffer;
|
||||
std::unique_ptr<SimpleGPURenderer> m_gpuRenderer;
|
||||
std::unique_ptr<IVideoRenderer> m_gpuRenderer;
|
||||
bool m_useHardwareRendering = true; // Default to GPU pipeline
|
||||
// Playback timer for continuous frame processing
|
||||
winrt::Microsoft::UI::Xaml::DispatcherTimer m_playbackTimer;
|
||||
|
||||
@@ -45,11 +45,14 @@ int main(int argc, char* argv[])
|
||||
auto metadata = fileReader->GetVideoMetadata();
|
||||
std::cout << "Video: " << metadata.width << "x" << metadata.height
|
||||
<< " @ " << metadata.frame_rate << " fps" << std::endl;
|
||||
std::cout << "Codec: " << (metadata.codec_type == VideoCodecType::AV1 ? "AV1" :
|
||||
metadata.codec_type == VideoCodecType::VP9 ? "VP9" : "Other") << std::endl;
|
||||
|
||||
// Test decoder creation
|
||||
auto decoder = Vav2Player::VideoDecoderFactory::CreateDecoder(Vav2Player::VideoCodecType::AV1, Vav2Player::VideoDecoderFactory::DecoderType::AUTO);
|
||||
// Test decoder creation using detected codec type
|
||||
auto decoder = Vav2Player::VideoDecoderFactory::CreateDecoder(metadata.codec_type, Vav2Player::VideoDecoderFactory::DecoderType::AUTO);
|
||||
if (!decoder) {
|
||||
std::cout << "Failed to create AV1 decoder" << std::endl;
|
||||
std::cout << "Failed to create " << (metadata.codec_type == VideoCodecType::AV1 ? "AV1" :
|
||||
metadata.codec_type == VideoCodecType::VP9 ? "VP9" : "Other") << " decoder" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,20 @@ enum class ColorSpace {
|
||||
YUV422P,
|
||||
YUV444P,
|
||||
RGB24,
|
||||
RGB32
|
||||
RGB32,
|
||||
BT709,
|
||||
BT2020
|
||||
};
|
||||
|
||||
// 픽셀 포맷 정의
|
||||
enum class PixelFormat {
|
||||
YUV420P,
|
||||
YUV422P,
|
||||
YUV444P,
|
||||
NV12,
|
||||
RGB24,
|
||||
RGB32,
|
||||
BGRA32
|
||||
};
|
||||
|
||||
// 비디오 메타데이터
|
||||
@@ -56,8 +69,10 @@ struct VideoFrame {
|
||||
// 프레임 메타데이터
|
||||
uint64_t frame_index = 0;
|
||||
double timestamp_seconds = 0.0;
|
||||
uint64_t timestamp_ns = 0; // For compatibility
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
PixelFormat format = PixelFormat::YUV420P; // Pixel format
|
||||
ColorSpace color_space = ColorSpace::YUV420P;
|
||||
|
||||
// YUV 데이터 (각 플레인별)
|
||||
|
||||
77
vav2/Vav2Player/Vav2Player/src/FileIO/IWebMFileReader.h
Normal file
77
vav2/Vav2Player/Vav2Player/src/FileIO/IWebMFileReader.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#include "../Common/VideoTypes.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// Forward declaration for VideoTrackInfo
|
||||
struct VideoTrackInfo {
|
||||
uint64_t track_number;
|
||||
VideoCodecType codec_type;
|
||||
std::string codec_id;
|
||||
std::string codec_name;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
double frame_rate;
|
||||
uint64_t frame_count;
|
||||
bool is_default;
|
||||
bool is_enabled = true; // For mock compatibility
|
||||
};
|
||||
|
||||
// Error codes for WebM file operations
|
||||
enum class WebMErrorCode {
|
||||
Success,
|
||||
FileNotFound,
|
||||
InvalidFormat,
|
||||
UnsupportedCodec,
|
||||
NoVideoTrack,
|
||||
ReadError,
|
||||
SeekError,
|
||||
FileNotOpen,
|
||||
InvalidTrack,
|
||||
SeekFailed,
|
||||
Unknown
|
||||
};
|
||||
|
||||
// Interface for WebM/MKV file readers
|
||||
// Provides common abstraction for parsing WebM containers and extracting AV1 video streams
|
||||
class IWebMFileReader {
|
||||
public:
|
||||
virtual ~IWebMFileReader() = default;
|
||||
|
||||
// File operations
|
||||
virtual bool OpenFile(const std::string& file_path) = 0;
|
||||
virtual void CloseFile() = 0;
|
||||
virtual bool IsFileOpen() const = 0;
|
||||
|
||||
// File and stream information
|
||||
virtual const VideoMetadata& GetVideoMetadata() const = 0;
|
||||
virtual std::string GetFilePath() const = 0;
|
||||
|
||||
// Video track management
|
||||
virtual std::vector<VideoTrackInfo> GetVideoTracks() const = 0;
|
||||
virtual bool SelectVideoTrack(uint64_t track_number) = 0;
|
||||
virtual uint64_t GetSelectedTrackNumber() const = 0;
|
||||
|
||||
// Packet reading
|
||||
virtual bool ReadNextPacket(VideoPacket& packet) = 0;
|
||||
virtual bool SeekToFrame(uint64_t frame_index) = 0;
|
||||
virtual bool SeekToTime(double timestamp_seconds) = 0;
|
||||
|
||||
// Position information
|
||||
virtual uint64_t GetCurrentFrameIndex() const = 0;
|
||||
virtual double GetCurrentTimestamp() const = 0;
|
||||
virtual bool IsEndOfFile() const = 0;
|
||||
|
||||
// File navigation and statistics
|
||||
virtual bool Reset() = 0;
|
||||
virtual uint64_t GetTotalFrames() const = 0;
|
||||
virtual double GetDuration() const = 0;
|
||||
|
||||
// Error handling
|
||||
virtual WebMErrorCode GetLastError() const = 0;
|
||||
virtual std::string GetLastErrorString() const = 0;
|
||||
};
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -97,7 +97,7 @@ struct WebMFileReader::InternalState {
|
||||
bool end_of_file = false;
|
||||
|
||||
// Error handling
|
||||
ErrorCode last_error = ErrorCode::Success;
|
||||
WebMErrorCode last_error = WebMErrorCode::Success;
|
||||
std::string last_error_message;
|
||||
|
||||
InternalState() : reader(std::make_unique<MkvReader>()) {}
|
||||
@@ -114,13 +114,13 @@ bool WebMFileReader::OpenFile(const std::string& file_path) {
|
||||
CloseFile();
|
||||
|
||||
if (file_path.empty()) {
|
||||
SetLastError(ErrorCode::FileNotFound, "File path is empty");
|
||||
SetLastError(WebMErrorCode::FileNotFound, "File path is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open file
|
||||
if (!m_state->reader->Open(file_path)) {
|
||||
SetLastError(ErrorCode::FileNotFound, "Cannot open file: " + file_path);
|
||||
SetLastError(WebMErrorCode::FileNotFound, "Cannot open file: " + file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ bool WebMFileReader::OpenFile(const std::string& file_path) {
|
||||
// Extract video tracks
|
||||
if (!ExtractVideoTracks()) {
|
||||
CloseFile();
|
||||
SetLastError(ErrorCode::NoVideoTrack, "No supported video tracks found");
|
||||
SetLastError(WebMErrorCode::NoVideoTrack, "No supported video tracks found");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ bool WebMFileReader::OpenFile(const std::string& file_path) {
|
||||
|
||||
if (m_state->selected_track_number == 0) {
|
||||
CloseFile();
|
||||
SetLastError(ErrorCode::UnsupportedCodec, "No supported video codecs found");
|
||||
SetLastError(WebMErrorCode::UnsupportedCodec, "No supported video codecs found");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ bool WebMFileReader::OpenFile(const std::string& file_path) {
|
||||
// Initialize reading position
|
||||
Reset();
|
||||
|
||||
SetLastError(ErrorCode::Success);
|
||||
SetLastError(WebMErrorCode::Success);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -193,13 +193,13 @@ std::string WebMFileReader::GetFilePath() const {
|
||||
return m_state ? m_state->file_path : "";
|
||||
}
|
||||
|
||||
std::vector<WebMFileReader::VideoTrackInfo> WebMFileReader::GetVideoTracks() const {
|
||||
std::vector<VideoTrackInfo> WebMFileReader::GetVideoTracks() const {
|
||||
return m_state ? m_state->video_tracks : std::vector<VideoTrackInfo>();
|
||||
}
|
||||
|
||||
bool WebMFileReader::SelectVideoTrack(uint64_t track_number) {
|
||||
if (!IsFileOpen()) {
|
||||
SetLastError(ErrorCode::ReadError, "File not open");
|
||||
SetLastError(WebMErrorCode::ReadError, "File not open");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -210,14 +210,14 @@ bool WebMFileReader::SelectVideoTrack(uint64_t track_number) {
|
||||
});
|
||||
|
||||
if (it == m_state->video_tracks.end()) {
|
||||
SetLastError(ErrorCode::ReadError, "Track not found: " + std::to_string(track_number));
|
||||
SetLastError(WebMErrorCode::ReadError, "Track not found: " + std::to_string(track_number));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_state->selected_track_number = track_number;
|
||||
Reset(); // Initialize reading position
|
||||
|
||||
SetLastError(ErrorCode::Success);
|
||||
SetLastError(WebMErrorCode::Success);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ bool WebMFileReader::ReadNextPacket(VideoPacket& packet) {
|
||||
|
||||
// Enhanced null safety check inside mutex
|
||||
if (!m_state) {
|
||||
SetLastError(ErrorCode::ReadError, "WebMFileReader state is null");
|
||||
SetLastError(WebMErrorCode::ReadError, "WebMFileReader state is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ bool WebMFileReader::ReadNextPacket(VideoPacket& packet) {
|
||||
|
||||
// Fail if no track selected
|
||||
if (m_state->selected_track_number == 0) {
|
||||
SetLastError(ErrorCode::ReadError, "No video track selected");
|
||||
SetLastError(WebMErrorCode::ReadError, "No video track selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -252,13 +252,13 @@ bool WebMFileReader::ReadNextPacket(VideoPacket& packet) {
|
||||
|
||||
// Read packet from current block
|
||||
if (!m_state->current_block_entry || !m_state->current_block_entry->GetBlock()) {
|
||||
SetLastError(ErrorCode::ReadError, "Invalid block entry");
|
||||
SetLastError(WebMErrorCode::ReadError, "Invalid block entry");
|
||||
return false;
|
||||
}
|
||||
|
||||
const mkvparser::Block* block = m_state->current_block_entry->GetBlock();
|
||||
if (!ReadPacketFromBlock(block, packet)) {
|
||||
SetLastError(ErrorCode::ReadError, "Failed to read packet from block");
|
||||
SetLastError(WebMErrorCode::ReadError, "Failed to read packet from block");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -281,20 +281,20 @@ bool WebMFileReader::ReadNextPacket(VideoPacket& packet) {
|
||||
packet.frame_index = m_state->current_frame_index - 1;
|
||||
packet.is_keyframe = block->IsKey();
|
||||
|
||||
SetLastError(ErrorCode::Success);
|
||||
SetLastError(WebMErrorCode::Success);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebMFileReader::SeekToFrame(uint64_t frame_index) {
|
||||
if (!IsFileOpen()) {
|
||||
SetLastError(ErrorCode::SeekError, "File not open");
|
||||
SetLastError(WebMErrorCode::SeekError, "File not open");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_access_mutex);
|
||||
|
||||
if (m_state->selected_track_number == 0) {
|
||||
SetLastError(ErrorCode::SeekError, "No video track selected");
|
||||
SetLastError(WebMErrorCode::SeekError, "No video track selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -319,31 +319,31 @@ bool WebMFileReader::SeekToFrame(uint64_t frame_index) {
|
||||
while (m_state->current_frame_index < frame_index && !m_state->end_of_file) {
|
||||
if (!AdvanceToNextFrame()) {
|
||||
m_state->end_of_file = true;
|
||||
SetLastError(ErrorCode::SeekError, "Cannot reach target frame");
|
||||
SetLastError(WebMErrorCode::SeekError, "Cannot reach target frame");
|
||||
return false;
|
||||
}
|
||||
m_state->current_frame_index++;
|
||||
}
|
||||
|
||||
SetLastError(ErrorCode::Success);
|
||||
SetLastError(WebMErrorCode::Success);
|
||||
return m_state->current_frame_index == frame_index;
|
||||
}
|
||||
|
||||
bool WebMFileReader::SeekToTime(double timestamp_seconds) {
|
||||
if (!IsFileOpen()) {
|
||||
SetLastError(ErrorCode::SeekError, "File not open");
|
||||
SetLastError(WebMErrorCode::SeekError, "File not open");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_access_mutex);
|
||||
|
||||
if (m_state->selected_track_number == 0) {
|
||||
SetLastError(ErrorCode::SeekError, "No video track selected");
|
||||
SetLastError(WebMErrorCode::SeekError, "No video track selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (timestamp_seconds < 0.0) {
|
||||
SetLastError(ErrorCode::SeekError, "Invalid timestamp");
|
||||
SetLastError(WebMErrorCode::SeekError, "Invalid timestamp");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -355,13 +355,13 @@ bool WebMFileReader::SeekToTime(double timestamp_seconds) {
|
||||
// 세그먼트 정보에서 타임코드 스케일 가져오기
|
||||
const mkvparser::SegmentInfo* info = m_state->segment->GetInfo();
|
||||
if (!info) {
|
||||
SetLastError(ErrorCode::SeekError, "No segment info available");
|
||||
SetLastError(WebMErrorCode::SeekError, "No segment info available");
|
||||
return false;
|
||||
}
|
||||
|
||||
long long timecode_scale = info->GetTimeCodeScale();
|
||||
if (timecode_scale <= 0) {
|
||||
SetLastError(ErrorCode::SeekError, "Invalid timecode scale");
|
||||
SetLastError(WebMErrorCode::SeekError, "Invalid timecode scale");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ bool WebMFileReader::SeekToTime(double timestamp_seconds) {
|
||||
// 타겟 시간에 가장 가까운 클러스터 찾기
|
||||
const mkvparser::Cluster* target_cluster = FindClusterByTime(timestamp_seconds);
|
||||
if (!target_cluster) {
|
||||
SetLastError(ErrorCode::SeekError, "Cannot find target cluster");
|
||||
SetLastError(WebMErrorCode::SeekError, "Cannot find target cluster");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ bool WebMFileReader::SeekToTime(double timestamp_seconds) {
|
||||
|
||||
if (current_time >= timestamp_seconds) {
|
||||
m_state->current_timestamp = current_time;
|
||||
SetLastError(ErrorCode::Success);
|
||||
SetLastError(WebMErrorCode::Success);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -405,7 +405,7 @@ bool WebMFileReader::SeekToTime(double timestamp_seconds) {
|
||||
m_state->current_frame_index++;
|
||||
}
|
||||
|
||||
SetLastError(ErrorCode::SeekError, "Cannot reach target time");
|
||||
SetLastError(WebMErrorCode::SeekError, "Cannot reach target time");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -429,7 +429,7 @@ bool WebMFileReader::Reset() {
|
||||
// 첫 번째 클러스터로 이동
|
||||
const mkvparser::Cluster* cluster = m_state->segment->GetFirst();
|
||||
if (!cluster) {
|
||||
SetLastError(ErrorCode::ReadError, "No clusters found");
|
||||
SetLastError(WebMErrorCode::ReadError, "No clusters found");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -450,8 +450,8 @@ double WebMFileReader::GetDuration() const {
|
||||
return m_state ? m_state->metadata.duration_seconds : 0.0;
|
||||
}
|
||||
|
||||
WebMFileReader::ErrorCode WebMFileReader::GetLastError() const {
|
||||
return m_state ? m_state->last_error : ErrorCode::Unknown;
|
||||
WebMErrorCode WebMFileReader::GetLastError() const {
|
||||
return m_state ? m_state->last_error : WebMErrorCode::Unknown;
|
||||
}
|
||||
|
||||
std::string WebMFileReader::GetLastErrorString() const {
|
||||
@@ -500,7 +500,7 @@ bool WebMFileReader::InitializeParser() {
|
||||
detailed_error += hex_dump;
|
||||
}
|
||||
|
||||
SetLastError(ErrorCode::InvalidFormat, detailed_error);
|
||||
SetLastError(WebMErrorCode::InvalidFormat, detailed_error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -513,7 +513,7 @@ bool WebMFileReader::InitializeParser() {
|
||||
error_msg += ". EBML header details: version=" + std::to_string(ebml_header.m_version) +
|
||||
", docTypeVersion=" + std::to_string(ebml_header.m_docTypeVersion);
|
||||
|
||||
SetLastError(ErrorCode::InvalidFormat, error_msg);
|
||||
SetLastError(WebMErrorCode::InvalidFormat, error_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -523,21 +523,21 @@ bool WebMFileReader::InitializeParser() {
|
||||
m_state->reader.get(), pos, segment_ptr);
|
||||
|
||||
if (create_status < 0 || !segment_ptr) {
|
||||
SetLastError(ErrorCode::InvalidFormat, "Cannot create segment");
|
||||
SetLastError(WebMErrorCode::InvalidFormat, "Cannot create segment");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_state->segment.reset(segment_ptr);
|
||||
|
||||
if (!m_state->segment) {
|
||||
SetLastError(ErrorCode::InvalidFormat, "Cannot create segment");
|
||||
SetLastError(WebMErrorCode::InvalidFormat, "Cannot create segment");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Segment 정보 로드
|
||||
long load_status = m_state->segment->Load();
|
||||
if (load_status < 0) {
|
||||
SetLastError(ErrorCode::InvalidFormat, "Cannot load segment");
|
||||
SetLastError(WebMErrorCode::InvalidFormat, "Cannot load segment");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -574,7 +574,7 @@ bool WebMFileReader::ExtractVideoTracks() {
|
||||
// 디버깅: 실제 코덱 ID를 파일에 출력
|
||||
std::string debug_msg = "Found video track #" + std::to_string(info.track_number) +
|
||||
" with codec_id: '" + info.codec_id + "'";
|
||||
SetLastError(ErrorCode::Success, debug_msg); // 임시로 이 메시지를 상태에 표시
|
||||
SetLastError(WebMErrorCode::Success, debug_msg); // 임시로 이 메시지를 상태에 표시
|
||||
|
||||
// 파일에 코덱 ID 기록
|
||||
std::ofstream codec_debug("codec_debug.log", std::ios::app);
|
||||
@@ -667,22 +667,22 @@ double WebMFileReader::CalculateFrameRate(const mkvparser::VideoTrack* video_tra
|
||||
return 30.0; // 임시 기본값
|
||||
}
|
||||
|
||||
void WebMFileReader::SetLastError(ErrorCode error, const std::string& message) {
|
||||
void WebMFileReader::SetLastError(WebMErrorCode error, const std::string& message) {
|
||||
if (m_state) {
|
||||
m_state->last_error = error;
|
||||
m_state->last_error_message = message;
|
||||
}
|
||||
}
|
||||
|
||||
std::string WebMFileReader::ErrorCodeToString(ErrorCode error) const {
|
||||
std::string WebMFileReader::ErrorCodeToString(WebMErrorCode error) const {
|
||||
switch (error) {
|
||||
case ErrorCode::Success: return "Success";
|
||||
case ErrorCode::FileNotFound: return "File not found";
|
||||
case ErrorCode::InvalidFormat: return "Invalid format";
|
||||
case ErrorCode::UnsupportedCodec: return "Unsupported codec";
|
||||
case ErrorCode::NoVideoTrack: return "No video track";
|
||||
case ErrorCode::ReadError: return "Read error";
|
||||
case ErrorCode::SeekError: return "Seek error";
|
||||
case WebMErrorCode::Success: return "Success";
|
||||
case WebMErrorCode::FileNotFound: return "File not found";
|
||||
case WebMErrorCode::InvalidFormat: return "Invalid format";
|
||||
case WebMErrorCode::UnsupportedCodec: return "Unsupported codec";
|
||||
case WebMErrorCode::NoVideoTrack: return "No video track";
|
||||
case WebMErrorCode::ReadError: return "Read error";
|
||||
case WebMErrorCode::SeekError: return "Seek error";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "../Common/VideoTypes.h"
|
||||
#include "IWebMFileReader.h"
|
||||
#include <mkvparser.hpp>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -10,7 +11,7 @@ namespace Vav2Player {
|
||||
|
||||
// WebM/MKV 파일을 파싱하여 AV1 비디오 스트림을 추출하는 클래스
|
||||
// libwebm의 mkvparser를 사용하여 구현
|
||||
class WebMFileReader {
|
||||
class WebMFileReader : public IWebMFileReader {
|
||||
public:
|
||||
WebMFileReader();
|
||||
~WebMFileReader();
|
||||
@@ -19,61 +20,38 @@ public:
|
||||
WebMFileReader(const WebMFileReader&) = delete;
|
||||
WebMFileReader& operator=(const WebMFileReader&) = delete;
|
||||
|
||||
// 파일 열기/닫기
|
||||
bool OpenFile(const std::string& file_path);
|
||||
void CloseFile();
|
||||
bool IsFileOpen() const;
|
||||
// 파일 열기/닫기 (interface implementation)
|
||||
bool OpenFile(const std::string& file_path) override;
|
||||
void CloseFile() override;
|
||||
bool IsFileOpen() const override;
|
||||
|
||||
// 파일 및 스트림 정보
|
||||
const VideoMetadata& GetVideoMetadata() const;
|
||||
std::string GetFilePath() const;
|
||||
// 파일 및 스트림 정보 (interface implementation)
|
||||
const VideoMetadata& GetVideoMetadata() const override;
|
||||
std::string GetFilePath() const override;
|
||||
|
||||
// 지원되는 비디오 트랙들 (AV1, VP9 등)
|
||||
struct VideoTrackInfo {
|
||||
uint64_t track_number;
|
||||
VideoCodecType codec_type;
|
||||
std::string codec_id;
|
||||
std::string codec_name;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
double frame_rate;
|
||||
uint64_t frame_count;
|
||||
bool is_default;
|
||||
};
|
||||
// 비디오 트랙 관리 (interface implementation)
|
||||
std::vector<VideoTrackInfo> GetVideoTracks() const override;
|
||||
bool SelectVideoTrack(uint64_t track_number) override;
|
||||
uint64_t GetSelectedTrackNumber() const override;
|
||||
|
||||
std::vector<VideoTrackInfo> GetVideoTracks() const;
|
||||
bool SelectVideoTrack(uint64_t track_number);
|
||||
uint64_t GetSelectedTrackNumber() const;
|
||||
// 패킷 읽기 (interface implementation)
|
||||
bool ReadNextPacket(VideoPacket& packet) override;
|
||||
bool SeekToFrame(uint64_t frame_index) override;
|
||||
bool SeekToTime(double timestamp_seconds) override;
|
||||
|
||||
// 프레임별 패킷 읽기
|
||||
bool ReadNextPacket(VideoPacket& packet);
|
||||
bool SeekToFrame(uint64_t frame_index);
|
||||
bool SeekToTime(double timestamp_seconds);
|
||||
// 위치 정보 (interface implementation)
|
||||
uint64_t GetCurrentFrameIndex() const override;
|
||||
double GetCurrentTimestamp() const override;
|
||||
bool IsEndOfFile() const override;
|
||||
|
||||
// 현재 위치 정보
|
||||
uint64_t GetCurrentFrameIndex() const;
|
||||
double GetCurrentTimestamp() const;
|
||||
bool IsEndOfFile() const;
|
||||
// 파일 탐색 및 통계 (interface implementation)
|
||||
bool Reset() override;
|
||||
uint64_t GetTotalFrames() const override;
|
||||
double GetDuration() const override;
|
||||
|
||||
// 파일 탐색 및 통계
|
||||
bool Reset(); // 처음으로 되돌아가기
|
||||
uint64_t GetTotalFrames() const;
|
||||
double GetDuration() const;
|
||||
|
||||
// 에러 처리
|
||||
enum class ErrorCode {
|
||||
Success,
|
||||
FileNotFound,
|
||||
InvalidFormat,
|
||||
UnsupportedCodec,
|
||||
NoVideoTrack,
|
||||
ReadError,
|
||||
SeekError,
|
||||
Unknown
|
||||
};
|
||||
|
||||
ErrorCode GetLastError() const;
|
||||
std::string GetLastErrorString() const;
|
||||
// 에러 처리 (interface implementation)
|
||||
WebMErrorCode GetLastError() const override;
|
||||
std::string GetLastErrorString() const override;
|
||||
|
||||
// libwebm 관련 정보
|
||||
static std::string GetLibWebMVersion();
|
||||
@@ -112,8 +90,8 @@ private:
|
||||
const mkvparser::Block* FindBlockByFrame(uint64_t frame_index);
|
||||
|
||||
// 에러 처리
|
||||
void SetLastError(ErrorCode error, const std::string& message = "");
|
||||
std::string ErrorCodeToString(ErrorCode error) const;
|
||||
void SetLastError(WebMErrorCode error, const std::string& message = "");
|
||||
std::string ErrorCodeToString(WebMErrorCode error) const;
|
||||
|
||||
// 유틸리티 함수들
|
||||
static bool IsVideoCodecSupported(const std::string& codec_id);
|
||||
|
||||
45
vav2/Vav2Player/Vav2Player/src/Rendering/IVideoRenderer.h
Normal file
45
vav2/Vav2Player/Vav2Player/src/Rendering/IVideoRenderer.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "../Common/VideoTypes.h"
|
||||
#include <windows.h>
|
||||
|
||||
// Conditional WinUI includes for unit testing compatibility
|
||||
#ifndef UNIT_TEST_BUILD
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#endif
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// Interface for video renderers
|
||||
// Provides common abstraction for rendering video frames to display
|
||||
class IVideoRenderer {
|
||||
public:
|
||||
virtual ~IVideoRenderer() = default;
|
||||
|
||||
// Core lifecycle
|
||||
virtual HRESULT Initialize(uint32_t width, uint32_t height) = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual bool IsInitialized() const = 0;
|
||||
|
||||
// Video rendering
|
||||
virtual HRESULT RenderVideoFrame(const VideoFrame& frame) = 0;
|
||||
virtual bool TryRenderFrame(const VideoFrame& frame) = 0; // Returns true if successful
|
||||
virtual HRESULT Present() = 0;
|
||||
|
||||
// Size management
|
||||
virtual HRESULT Resize(uint32_t width, uint32_t height) = 0;
|
||||
virtual uint32_t GetWidth() const = 0;
|
||||
virtual uint32_t GetHeight() const = 0;
|
||||
|
||||
// Additional configuration (optional for implementations)
|
||||
virtual void SetAspectFitMode(bool enabled) {}
|
||||
virtual void UpdateContainerSize(uint32_t container_width, uint32_t container_height) {}
|
||||
|
||||
// WinUI-specific methods (optional implementation)
|
||||
#ifndef UNIT_TEST_BUILD
|
||||
virtual HRESULT InitializeWithSwapChain(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel,
|
||||
uint32_t width, uint32_t height) { return E_NOTIMPL; }
|
||||
virtual void SetSwapChainPanel(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel) { }
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -31,7 +31,33 @@ SimpleGPURenderer::~SimpleGPURenderer()
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer::Initialize(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel,
|
||||
// Interface implementation - for headless/testing scenarios
|
||||
HRESULT SimpleGPURenderer::Initialize(uint32_t width, uint32_t height) {
|
||||
// Store dimensions
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
// Try to initialize D3D12 without SwapChain (for testing)
|
||||
HRESULT hr = CreateDevice();
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = CreateCommandQueue();
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Mark as initialized (SwapChain can be set later)
|
||||
m_initialized = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void SimpleGPURenderer::SetSwapChainPanel(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel) {
|
||||
m_swapChainPanel = panel;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer::InitializeWithSwapChain(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
if (m_initialized)
|
||||
@@ -1499,4 +1525,21 @@ HRESULT SimpleGPURenderer::RenderWithAspectFitInternal()
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer::Resize(uint32_t width, uint32_t height)
|
||||
{
|
||||
if (!m_initialized)
|
||||
return E_FAIL;
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
// Recreate render targets if swap chain exists
|
||||
if (m_swapChain)
|
||||
{
|
||||
return CreateRenderTargets();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <microsoft.ui.xaml.media.dxinterop.h>
|
||||
#include "../Common/VideoTypes.h"
|
||||
#include "IVideoRenderer.h"
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
@@ -27,27 +28,29 @@ struct AspectFitConstants
|
||||
|
||||
// Simple, clean GPU renderer for AV1 video playback
|
||||
// Phase 3: Designed from scratch with proper architecture
|
||||
class SimpleGPURenderer
|
||||
class SimpleGPURenderer : public IVideoRenderer
|
||||
{
|
||||
public:
|
||||
SimpleGPURenderer();
|
||||
~SimpleGPURenderer();
|
||||
|
||||
// Core lifecycle
|
||||
HRESULT Initialize(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel,
|
||||
uint32_t width, uint32_t height);
|
||||
void Shutdown();
|
||||
bool IsInitialized() const { return m_initialized; }
|
||||
// IVideoRenderer interface implementation
|
||||
HRESULT Initialize(uint32_t width, uint32_t height) override;
|
||||
void Shutdown() override;
|
||||
bool IsInitialized() const override { return m_initialized; }
|
||||
|
||||
// Video rendering
|
||||
HRESULT RenderVideoFrame(const VideoFrame& frame);
|
||||
bool TryRenderFrame(const VideoFrame& frame); // Returns true if successful
|
||||
HRESULT Present();
|
||||
HRESULT RenderVideoFrame(const VideoFrame& frame) override;
|
||||
bool TryRenderFrame(const VideoFrame& frame) override;
|
||||
HRESULT Present() override;
|
||||
|
||||
// Size management
|
||||
HRESULT Resize(uint32_t width, uint32_t height);
|
||||
uint32_t GetWidth() const { return m_width; }
|
||||
uint32_t GetHeight() const { return m_height; }
|
||||
HRESULT Resize(uint32_t width, uint32_t height) override;
|
||||
uint32_t GetWidth() const override { return m_width; }
|
||||
uint32_t GetHeight() const override { return m_height; }
|
||||
|
||||
// SimpleGPURenderer specific methods
|
||||
HRESULT InitializeWithSwapChain(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel,
|
||||
uint32_t width, uint32_t height);
|
||||
void SetSwapChainPanel(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel);
|
||||
|
||||
private:
|
||||
// D3D12 core objects
|
||||
@@ -107,6 +110,9 @@ private:
|
||||
UINT m_srvUavDescriptorSize = 0;
|
||||
uint64_t m_totalFramesRendered = 0;
|
||||
|
||||
// WinUI integration
|
||||
winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel m_swapChainPanel{ nullptr };
|
||||
|
||||
// Helper methods
|
||||
HRESULT CreateDevice();
|
||||
HRESULT CreateCommandQueue();
|
||||
|
||||
214
vav2/Vav2Player/Vav2Player/unit-test/AV1DecoderTest.cpp
Normal file
214
vav2/Vav2Player/Vav2Player/unit-test/AV1DecoderTest.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include "pch.h"
|
||||
#include "MockWebMFileReader.h"
|
||||
#include "../src/Decoder/AV1Decoder.h"
|
||||
#include "../src/Decoder/VideoDecoderFactory.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
using namespace Vav2Player;
|
||||
using namespace Vav2PlayerUnitTests;
|
||||
|
||||
namespace Vav2PlayerUnitTests
|
||||
{
|
||||
TEST_CLASS(AV1DecoderTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(AV1Decoder_Initialize_ValidMetadata_ShouldReturnTrue)
|
||||
{
|
||||
// Arrange
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::SOFTWARE);
|
||||
|
||||
VideoMetadata metadata;
|
||||
metadata.width = 1920;
|
||||
metadata.height = 1080;
|
||||
metadata.codec_type = VideoCodecType::AV1;
|
||||
metadata.frame_rate = 30.0;
|
||||
|
||||
// Act
|
||||
bool result = decoder->Initialize(metadata);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::IsTrue(decoder->IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(AV1Decoder_Initialize_InvalidMetadata_ShouldReturnFalse)
|
||||
{
|
||||
// Arrange
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::SOFTWARE);
|
||||
|
||||
VideoMetadata metadata;
|
||||
metadata.width = 0; // Invalid width
|
||||
metadata.height = 1080;
|
||||
metadata.codec_type = VideoCodecType::AV1;
|
||||
|
||||
// Act
|
||||
bool result = decoder->Initialize(metadata);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
Assert::IsFalse(decoder->IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(VideoDecoderFactory_CreateAV1Decoder_ShouldReturnValidDecoder)
|
||||
{
|
||||
// Act
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::SOFTWARE);
|
||||
|
||||
// Assert
|
||||
Assert::IsNotNull(decoder.get());
|
||||
Assert::IsFalse(decoder->IsInitialized()); // Should not be initialized until Initialize() is called
|
||||
}
|
||||
|
||||
TEST_METHOD(VideoDecoderFactory_CreateAutoDecoder_ShouldReturnValidDecoder)
|
||||
{
|
||||
// Act
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::AUTO);
|
||||
|
||||
// Assert
|
||||
Assert::IsNotNull(decoder.get());
|
||||
Assert::IsFalse(decoder->IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(AV1Decoder_Reset_AfterInitialization_ShouldSucceed)
|
||||
{
|
||||
// Arrange
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::SOFTWARE);
|
||||
|
||||
VideoMetadata metadata;
|
||||
metadata.width = 1920;
|
||||
metadata.height = 1080;
|
||||
metadata.codec_type = VideoCodecType::AV1;
|
||||
metadata.frame_rate = 30.0;
|
||||
|
||||
decoder->Initialize(metadata);
|
||||
|
||||
// Act
|
||||
bool result = decoder->Reset();
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::IsTrue(decoder->IsInitialized()); // Should remain initialized after reset
|
||||
}
|
||||
|
||||
TEST_METHOD(AV1Decoder_Cleanup_ShouldSetNotInitialized)
|
||||
{
|
||||
// Arrange
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::SOFTWARE);
|
||||
|
||||
VideoMetadata metadata;
|
||||
metadata.width = 1920;
|
||||
metadata.height = 1080;
|
||||
metadata.codec_type = VideoCodecType::AV1;
|
||||
metadata.frame_rate = 30.0;
|
||||
|
||||
decoder->Initialize(metadata);
|
||||
Assert::IsTrue(decoder->IsInitialized());
|
||||
|
||||
// Act
|
||||
decoder->Cleanup();
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(decoder->IsInitialized());
|
||||
}
|
||||
|
||||
// Note: GetSupportedFormats and GetAvailableDecoders methods don't exist yet
|
||||
// Commenting out until these methods are implemented
|
||||
/*
|
||||
TEST_METHOD(AV1Decoder_GetSupportedFormats_ShouldReturnAV1)
|
||||
{
|
||||
// Arrange
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::SOFTWARE);
|
||||
|
||||
// Act
|
||||
auto formats = decoder->GetSupportedFormats();
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(formats.size() > 0);
|
||||
|
||||
// Check if AV1 is in the supported formats
|
||||
bool foundAV1 = false;
|
||||
for (const auto& format : formats) {
|
||||
if (format == VideoCodecType::AV1) {
|
||||
foundAV1 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert::IsTrue(foundAV1);
|
||||
}
|
||||
|
||||
TEST_METHOD(VideoDecoderFactory_GetAvailableDecoders_ShouldIncludeAV1)
|
||||
{
|
||||
// Act
|
||||
auto decoders = VideoDecoderFactory::GetAvailableDecoders();
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(decoders.size() > 0);
|
||||
|
||||
// Check if AV1 decoder info is present
|
||||
bool foundAV1 = false;
|
||||
for (const auto& decoderInfo : decoders) {
|
||||
if (decoderInfo.codec_type == VideoCodecType::AV1) {
|
||||
foundAV1 = true;
|
||||
Assert::IsFalse(decoderInfo.name.empty());
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert::IsTrue(foundAV1);
|
||||
}
|
||||
*/
|
||||
|
||||
TEST_METHOD(AV1Decoder_HardwareDecoder_CreationTest)
|
||||
{
|
||||
// Act - Try to create hardware decoder (may fail on systems without hardware support)
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::HARDWARE_MF);
|
||||
|
||||
// Assert - Should either succeed or return nullptr (graceful failure)
|
||||
// This test mainly verifies that factory doesn't crash
|
||||
if (decoder) {
|
||||
// If hardware decoder is available, test basic functionality
|
||||
VideoMetadata metadata;
|
||||
metadata.width = 1920;
|
||||
metadata.height = 1080;
|
||||
metadata.codec_type = VideoCodecType::AV1;
|
||||
metadata.frame_rate = 30.0;
|
||||
|
||||
// Hardware decoder may fail initialization on systems without support
|
||||
// This is expected behavior, not a test failure
|
||||
decoder->Initialize(metadata);
|
||||
}
|
||||
|
||||
// Test passes if we reach here without crashing
|
||||
Assert::IsTrue(true);
|
||||
}
|
||||
|
||||
TEST_METHOD(AV1Decoder_Integration_WithMockReader)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::SOFTWARE);
|
||||
|
||||
mockReader.OpenFile("test.webm");
|
||||
mockReader.SelectVideoTrack(1);
|
||||
|
||||
const auto& metadata = mockReader.GetVideoMetadata();
|
||||
decoder->Initialize(metadata);
|
||||
|
||||
// Act - Try to decode first packet
|
||||
VideoPacket packet;
|
||||
bool readResult = mockReader.ReadNextPacket(packet);
|
||||
|
||||
VideoFrame frame;
|
||||
bool decodeResult = false;
|
||||
if (readResult && packet.data && packet.size > 0) {
|
||||
// Note: This will likely fail with mock data since it's not real AV1
|
||||
// But the test verifies the integration doesn't crash
|
||||
decodeResult = decoder->DecodeFrame(packet, frame);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(readResult); // Mock reader should succeed
|
||||
Assert::IsTrue(decoder->IsInitialized());
|
||||
// decodeResult may be false since mock data isn't real AV1 - this is expected
|
||||
}
|
||||
};
|
||||
}
|
||||
198
vav2/Vav2Player/Vav2Player/unit-test/MockVideoRenderer.cpp
Normal file
198
vav2/Vav2Player/Vav2Player/unit-test/MockVideoRenderer.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "pch.h"
|
||||
#include "MockVideoRenderer.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <cstring>
|
||||
|
||||
namespace Vav2PlayerUnitTests {
|
||||
|
||||
MockVideoRenderer::MockVideoRenderer() {
|
||||
}
|
||||
|
||||
HRESULT MockVideoRenderer::Initialize(uint32_t width, uint32_t height) {
|
||||
m_initializeCallCount++;
|
||||
m_lastInitializeWidth = width;
|
||||
m_lastInitializeHeight = height;
|
||||
|
||||
if (FAILED(m_initializeResult)) {
|
||||
return m_initializeResult;
|
||||
}
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_initialized = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::Shutdown() {
|
||||
m_initialized = false;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_renderedFrames.clear();
|
||||
}
|
||||
|
||||
bool MockVideoRenderer::IsInitialized() const {
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
HRESULT MockVideoRenderer::RenderVideoFrame(const Vav2Player::VideoFrame& frame) {
|
||||
m_renderFrameCallCount++;
|
||||
|
||||
if (!m_initialized) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
if (FAILED(m_renderFrameResult)) {
|
||||
return m_renderFrameResult;
|
||||
}
|
||||
|
||||
// Store frame for test verification
|
||||
Vav2Player::VideoFrame frameCopy;
|
||||
CopyFrame(frame, frameCopy);
|
||||
m_renderedFrames.push_back(std::move(frameCopy));
|
||||
|
||||
// Simulate rendering delay if configured
|
||||
SimulateRenderDelay();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool MockVideoRenderer::TryRenderFrame(const Vav2Player::VideoFrame& frame) {
|
||||
m_tryRenderFrameCallCount++;
|
||||
|
||||
if (!m_initialized || !m_tryRenderFrameResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store frame for test verification
|
||||
Vav2Player::VideoFrame frameCopy;
|
||||
CopyFrame(frame, frameCopy);
|
||||
m_renderedFrames.push_back(std::move(frameCopy));
|
||||
|
||||
// Simulate rendering delay if configured
|
||||
SimulateRenderDelay();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HRESULT MockVideoRenderer::Present() {
|
||||
m_presentCallCount++;
|
||||
|
||||
if (!m_initialized) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
return m_presentResult;
|
||||
}
|
||||
|
||||
HRESULT MockVideoRenderer::Resize(uint32_t width, uint32_t height) {
|
||||
m_resizeCallCount++;
|
||||
|
||||
if (!m_initialized) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
if (FAILED(m_resizeResult)) {
|
||||
return m_resizeResult;
|
||||
}
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
uint32_t MockVideoRenderer::GetWidth() const {
|
||||
return m_width;
|
||||
}
|
||||
|
||||
uint32_t MockVideoRenderer::GetHeight() const {
|
||||
return m_height;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SetAspectFitMode(bool enabled) {
|
||||
m_aspectFitEnabled = enabled;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::UpdateContainerSize(uint32_t container_width, uint32_t container_height) {
|
||||
m_containerWidth = container_width;
|
||||
m_containerHeight = container_height;
|
||||
}
|
||||
|
||||
// Mock control methods
|
||||
void MockVideoRenderer::SetInitializeResult(HRESULT result) {
|
||||
m_initializeResult = result;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SetRenderFrameResult(HRESULT result) {
|
||||
m_renderFrameResult = result;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SetTryRenderFrameResult(bool success) {
|
||||
m_tryRenderFrameResult = success;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SetPresentResult(HRESULT result) {
|
||||
m_presentResult = result;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SetResizeResult(HRESULT result) {
|
||||
m_resizeResult = result;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SimulateInitializationFailure(bool fail) {
|
||||
m_initializeResult = fail ? E_FAIL : S_OK;
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SimulateRenderingFailure(bool fail) {
|
||||
m_renderFrameResult = fail ? E_FAIL : S_OK;
|
||||
m_tryRenderFrameResult = !fail;
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
void MockVideoRenderer::CopyFrame(const Vav2Player::VideoFrame& source, Vav2Player::VideoFrame& dest) {
|
||||
dest.width = source.width;
|
||||
dest.height = source.height;
|
||||
dest.format = source.format;
|
||||
dest.color_space = source.color_space;
|
||||
dest.timestamp_ns = source.timestamp_ns;
|
||||
dest.frame_index = source.frame_index;
|
||||
|
||||
// Copy stride information
|
||||
dest.y_stride = source.y_stride;
|
||||
dest.u_stride = source.u_stride;
|
||||
dest.v_stride = source.v_stride;
|
||||
|
||||
// Copy Y plane
|
||||
if (source.y_plane && source.y_stride > 0 && source.height > 0) {
|
||||
size_t y_size = static_cast<size_t>(source.y_stride) * source.height;
|
||||
dest.y_plane = std::make_unique<uint8_t[]>(y_size);
|
||||
std::memcpy(dest.y_plane.get(), source.y_plane.get(), y_size);
|
||||
}
|
||||
|
||||
// Copy U plane
|
||||
if (source.u_plane && source.u_stride > 0 && source.height > 0) {
|
||||
size_t u_size = static_cast<size_t>(source.u_stride) * (source.height / 2); // YUV420 assumption
|
||||
dest.u_plane = std::make_unique<uint8_t[]>(u_size);
|
||||
std::memcpy(dest.u_plane.get(), source.u_plane.get(), u_size);
|
||||
}
|
||||
|
||||
// Copy V plane
|
||||
if (source.v_plane && source.v_stride > 0 && source.height > 0) {
|
||||
size_t v_size = static_cast<size_t>(source.v_stride) * (source.height / 2); // YUV420 assumption
|
||||
dest.v_plane = std::make_unique<uint8_t[]>(v_size);
|
||||
std::memcpy(dest.v_plane.get(), source.v_plane.get(), v_size);
|
||||
}
|
||||
}
|
||||
|
||||
void MockVideoRenderer::SimulateRenderDelay() {
|
||||
if (m_renderDelayMs > 0) {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(m_renderDelayMs));
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
m_totalRenderTimeMs += static_cast<uint64_t>(duration.count());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vav2PlayerUnitTests
|
||||
98
vav2/Vav2Player/Vav2Player/unit-test/MockVideoRenderer.h
Normal file
98
vav2/Vav2Player/Vav2Player/unit-test/MockVideoRenderer.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include "../src/Rendering/IVideoRenderer.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Vav2PlayerUnitTests {
|
||||
|
||||
// Mock implementation of IVideoRenderer for unit testing
|
||||
// Provides controllable rendering behavior without actual D3D12/GPU operations
|
||||
class MockVideoRenderer : public Vav2Player::IVideoRenderer {
|
||||
public:
|
||||
MockVideoRenderer();
|
||||
virtual ~MockVideoRenderer() = default;
|
||||
|
||||
// IVideoRenderer interface implementation
|
||||
HRESULT Initialize(uint32_t width, uint32_t height) override;
|
||||
void Shutdown() override;
|
||||
bool IsInitialized() const override;
|
||||
|
||||
HRESULT RenderVideoFrame(const Vav2Player::VideoFrame& frame) override;
|
||||
bool TryRenderFrame(const Vav2Player::VideoFrame& frame) override;
|
||||
HRESULT Present() override;
|
||||
|
||||
HRESULT Resize(uint32_t width, uint32_t height) override;
|
||||
uint32_t GetWidth() const override;
|
||||
uint32_t GetHeight() const override;
|
||||
|
||||
void SetAspectFitMode(bool enabled) override;
|
||||
void UpdateContainerSize(uint32_t container_width, uint32_t container_height) override;
|
||||
|
||||
// Mock-specific control methods for testing
|
||||
void SetInitializeResult(HRESULT result);
|
||||
void SetRenderFrameResult(HRESULT result);
|
||||
void SetTryRenderFrameResult(bool success);
|
||||
void SetPresentResult(HRESULT result);
|
||||
void SetResizeResult(HRESULT result);
|
||||
|
||||
void SimulateInitializationFailure(bool fail = true);
|
||||
void SimulateRenderingFailure(bool fail = true);
|
||||
|
||||
// Test verification methods
|
||||
uint32_t GetInitializeCallCount() const { return m_initializeCallCount; }
|
||||
uint32_t GetRenderFrameCallCount() const { return m_renderFrameCallCount; }
|
||||
uint32_t GetTryRenderFrameCallCount() const { return m_tryRenderFrameCallCount; }
|
||||
uint32_t GetPresentCallCount() const { return m_presentCallCount; }
|
||||
uint32_t GetResizeCallCount() const { return m_resizeCallCount; }
|
||||
|
||||
uint32_t GetLastInitializeWidth() const { return m_lastInitializeWidth; }
|
||||
uint32_t GetLastInitializeHeight() const { return m_lastInitializeHeight; }
|
||||
|
||||
const std::vector<Vav2Player::VideoFrame>& GetRenderedFrames() const { return m_renderedFrames; }
|
||||
uint32_t GetTotalRenderedFrames() const { return static_cast<uint32_t>(m_renderedFrames.size()); }
|
||||
|
||||
bool GetAspectFitMode() const { return m_aspectFitEnabled; }
|
||||
uint32_t GetContainerWidth() const { return m_containerWidth; }
|
||||
uint32_t GetContainerHeight() const { return m_containerHeight; }
|
||||
|
||||
// Performance simulation
|
||||
void SetRenderDelay(uint32_t milliseconds) { m_renderDelayMs = milliseconds; }
|
||||
uint64_t GetTotalRenderTime() const { return m_totalRenderTimeMs; }
|
||||
|
||||
private:
|
||||
// Mock state
|
||||
bool m_initialized = false;
|
||||
uint32_t m_width = 0;
|
||||
uint32_t m_height = 0;
|
||||
bool m_aspectFitEnabled = false;
|
||||
uint32_t m_containerWidth = 0;
|
||||
uint32_t m_containerHeight = 0;
|
||||
|
||||
// Mock behavior control
|
||||
HRESULT m_initializeResult = S_OK;
|
||||
HRESULT m_renderFrameResult = S_OK;
|
||||
bool m_tryRenderFrameResult = true;
|
||||
HRESULT m_presentResult = S_OK;
|
||||
HRESULT m_resizeResult = S_OK;
|
||||
uint32_t m_renderDelayMs = 0;
|
||||
|
||||
// Call tracking for test verification
|
||||
mutable uint32_t m_initializeCallCount = 0;
|
||||
mutable uint32_t m_renderFrameCallCount = 0;
|
||||
mutable uint32_t m_tryRenderFrameCallCount = 0;
|
||||
mutable uint32_t m_presentCallCount = 0;
|
||||
mutable uint32_t m_resizeCallCount = 0;
|
||||
|
||||
uint32_t m_lastInitializeWidth = 0;
|
||||
uint32_t m_lastInitializeHeight = 0;
|
||||
|
||||
// Frame storage for test verification
|
||||
std::vector<Vav2Player::VideoFrame> m_renderedFrames;
|
||||
uint64_t m_totalRenderTimeMs = 0;
|
||||
|
||||
// Helper methods
|
||||
void CopyFrame(const Vav2Player::VideoFrame& source, Vav2Player::VideoFrame& dest);
|
||||
void SimulateRenderDelay();
|
||||
};
|
||||
|
||||
} // namespace Vav2PlayerUnitTests
|
||||
247
vav2/Vav2Player/Vav2Player/unit-test/MockWebMFileReader.cpp
Normal file
247
vav2/Vav2Player/Vav2Player/unit-test/MockWebMFileReader.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include "pch.h"
|
||||
#include "MockWebMFileReader.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace Vav2PlayerUnitTests {
|
||||
|
||||
MockWebMFileReader::MockWebMFileReader() {
|
||||
CreateDefaultMockData();
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::OpenFile(const std::string& file_path) {
|
||||
m_openFileCallCount++;
|
||||
m_lastOpenedFile = file_path;
|
||||
|
||||
if (!m_openFileResult) {
|
||||
m_mockError = Vav2Player::WebMErrorCode::FileNotFound;
|
||||
m_mockErrorMessage = "Mock file not found: " + file_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isOpen = true;
|
||||
m_currentFilePath = file_path;
|
||||
m_currentFrame = 0;
|
||||
m_mockError = Vav2Player::WebMErrorCode::Success;
|
||||
m_mockErrorMessage.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MockWebMFileReader::CloseFile() {
|
||||
m_isOpen = false;
|
||||
m_currentFilePath.clear();
|
||||
m_currentFrame = 0;
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::IsFileOpen() const {
|
||||
return m_isOpen;
|
||||
}
|
||||
|
||||
const Vav2Player::VideoMetadata& MockWebMFileReader::GetVideoMetadata() const {
|
||||
return m_mockMetadata;
|
||||
}
|
||||
|
||||
std::string MockWebMFileReader::GetFilePath() const {
|
||||
return m_currentFilePath;
|
||||
}
|
||||
|
||||
std::vector<Vav2Player::VideoTrackInfo> MockWebMFileReader::GetVideoTracks() const {
|
||||
return m_mockTracks;
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::SelectVideoTrack(uint64_t track_number) {
|
||||
if (!m_isOpen) {
|
||||
m_mockError = Vav2Player::WebMErrorCode::FileNotOpen;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate track exists
|
||||
for (const auto& track : m_mockTracks) {
|
||||
if (track.track_number == track_number) {
|
||||
m_selectedTrack = track_number;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_mockError = Vav2Player::WebMErrorCode::InvalidTrack;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t MockWebMFileReader::GetSelectedTrackNumber() const {
|
||||
return m_selectedTrack;
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::ReadNextPacket(Vav2Player::VideoPacket& packet) {
|
||||
m_readPacketCallCount++;
|
||||
|
||||
if (!m_isOpen) {
|
||||
m_mockError = Vav2Player::WebMErrorCode::FileNotOpen;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_currentFrame >= m_endOfFileFrame || m_currentFrame >= m_mockPackets.size()) {
|
||||
return false; // End of file
|
||||
}
|
||||
|
||||
// Create packet from mock data
|
||||
const auto& mockData = m_mockPackets[m_currentFrame];
|
||||
packet.data = std::make_unique<uint8_t[]>(mockData.size());
|
||||
packet.size = mockData.size();
|
||||
std::memcpy(packet.data.get(), mockData.data(), mockData.size());
|
||||
|
||||
packet.timestamp_seconds = static_cast<double>(m_currentFrame) / m_mockMetadata.frame_rate;
|
||||
packet.frame_index = m_currentFrame;
|
||||
packet.is_keyframe = (m_currentFrame % 30 == 0); // Every 30th frame is keyframe
|
||||
|
||||
m_currentFrame++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::SeekToFrame(uint64_t frame_index) {
|
||||
if (!m_isOpen) {
|
||||
m_mockError = Vav2Player::WebMErrorCode::FileNotOpen;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frame_index >= m_mockPackets.size()) {
|
||||
m_mockError = Vav2Player::WebMErrorCode::SeekFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_currentFrame = frame_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::SeekToTime(double timestamp_seconds) {
|
||||
if (!m_isOpen) {
|
||||
m_mockError = Vav2Player::WebMErrorCode::FileNotOpen;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t frame_index = static_cast<uint64_t>(timestamp_seconds * m_mockMetadata.frame_rate);
|
||||
return SeekToFrame(frame_index);
|
||||
}
|
||||
|
||||
uint64_t MockWebMFileReader::GetCurrentFrameIndex() const {
|
||||
return m_currentFrame;
|
||||
}
|
||||
|
||||
double MockWebMFileReader::GetCurrentTimestamp() const {
|
||||
return static_cast<double>(m_currentFrame) / m_mockMetadata.frame_rate;
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::IsEndOfFile() const {
|
||||
return m_currentFrame >= m_endOfFileFrame || m_currentFrame >= m_mockPackets.size();
|
||||
}
|
||||
|
||||
bool MockWebMFileReader::Reset() {
|
||||
if (!m_isOpen) {
|
||||
m_mockError = Vav2Player::WebMErrorCode::FileNotOpen;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_currentFrame = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t MockWebMFileReader::GetTotalFrames() const {
|
||||
return m_mockPackets.size();
|
||||
}
|
||||
|
||||
double MockWebMFileReader::GetDuration() const {
|
||||
return m_mockMetadata.duration_seconds;
|
||||
}
|
||||
|
||||
Vav2Player::WebMErrorCode MockWebMFileReader::GetLastError() const {
|
||||
return m_mockError;
|
||||
}
|
||||
|
||||
std::string MockWebMFileReader::GetLastErrorString() const {
|
||||
return m_mockErrorMessage;
|
||||
}
|
||||
|
||||
// Mock control methods
|
||||
void MockWebMFileReader::SetMockVideoMetadata(const Vav2Player::VideoMetadata& metadata) {
|
||||
m_mockMetadata = metadata;
|
||||
}
|
||||
|
||||
void MockWebMFileReader::SetMockVideoTracks(const std::vector<Vav2Player::VideoTrackInfo>& tracks) {
|
||||
m_mockTracks = tracks;
|
||||
}
|
||||
|
||||
void MockWebMFileReader::SetMockPackets(const std::vector<std::vector<uint8_t>>& packets) {
|
||||
m_mockPackets = packets;
|
||||
// Update metadata to match packet count
|
||||
m_mockMetadata.total_frames = packets.size();
|
||||
m_mockMetadata.duration_seconds = static_cast<double>(packets.size()) / m_mockMetadata.frame_rate;
|
||||
}
|
||||
|
||||
void MockWebMFileReader::SetMockError(Vav2Player::WebMErrorCode error, const std::string& message) {
|
||||
m_mockError = error;
|
||||
m_mockErrorMessage = message;
|
||||
}
|
||||
|
||||
void MockWebMFileReader::SetOpenFileResult(bool success) {
|
||||
m_openFileResult = success;
|
||||
}
|
||||
|
||||
void MockWebMFileReader::SetEndOfFileAtFrame(uint64_t frame_index) {
|
||||
m_endOfFileFrame = frame_index;
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
void MockWebMFileReader::CreateDefaultMockData() {
|
||||
// Default metadata for 1920x1080 30fps AV1 video
|
||||
m_mockMetadata.width = 1920;
|
||||
m_mockMetadata.height = 1080;
|
||||
m_mockMetadata.frame_rate = 30.0;
|
||||
m_mockMetadata.codec_type = Vav2Player::VideoCodecType::AV1;
|
||||
m_mockMetadata.color_space = Vav2Player::ColorSpace::BT709;
|
||||
m_mockMetadata.total_frames = 100; // 100 frames = ~3.33 seconds
|
||||
m_mockMetadata.duration_seconds = 100.0 / 30.0;
|
||||
|
||||
// Default video track
|
||||
Vav2Player::VideoTrackInfo track;
|
||||
track.track_number = 1;
|
||||
track.codec_id = "V_AV01";
|
||||
track.codec_name = "AV1";
|
||||
track.codec_type = Vav2Player::VideoCodecType::AV1;
|
||||
track.width = 1920;
|
||||
track.height = 1080;
|
||||
track.frame_rate = 30.0;
|
||||
track.is_enabled = true;
|
||||
m_mockTracks.push_back(track);
|
||||
|
||||
// Create 100 mock AV1 packets
|
||||
m_mockPackets.clear();
|
||||
for (uint64_t i = 0; i < 100; ++i) {
|
||||
m_mockPackets.push_back(CreateMockAV1Packet(i));
|
||||
}
|
||||
|
||||
m_selectedTrack = 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> MockWebMFileReader::CreateMockAV1Packet(uint64_t frame_index) {
|
||||
// Create a minimal mock AV1 packet
|
||||
// Real AV1 packets are complex, but for testing we just need valid data
|
||||
std::vector<uint8_t> packet;
|
||||
|
||||
// AV1 OBU header pattern (simplified)
|
||||
packet.push_back(0x0A); // OBU type (frame)
|
||||
packet.push_back(0x00); // OBU extension flag
|
||||
|
||||
// Mock frame size (varies by frame index for realism)
|
||||
uint32_t frame_size = 1000 + (frame_index % 500); // 1000-1500 bytes
|
||||
|
||||
// Add frame size in LEB128 format (simplified)
|
||||
packet.push_back(static_cast<uint8_t>(frame_size & 0x7F));
|
||||
packet.push_back(static_cast<uint8_t>((frame_size >> 7) & 0x7F));
|
||||
|
||||
// Fill with mock data
|
||||
for (uint32_t i = 0; i < frame_size; ++i) {
|
||||
packet.push_back(static_cast<uint8_t>((frame_index + i) & 0xFF));
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
} // namespace Vav2PlayerUnitTests
|
||||
84
vav2/Vav2Player/Vav2Player/unit-test/MockWebMFileReader.h
Normal file
84
vav2/Vav2Player/Vav2Player/unit-test/MockWebMFileReader.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include "../src/FileIO/IWebMFileReader.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace Vav2PlayerUnitTests {
|
||||
|
||||
// Mock implementation of IWebMFileReader for unit testing
|
||||
// Provides controllable test data without actual file I/O
|
||||
class MockWebMFileReader : public Vav2Player::IWebMFileReader {
|
||||
public:
|
||||
MockWebMFileReader();
|
||||
virtual ~MockWebMFileReader() = default;
|
||||
|
||||
// IWebMFileReader interface implementation
|
||||
bool OpenFile(const std::string& file_path) override;
|
||||
void CloseFile() override;
|
||||
bool IsFileOpen() const override;
|
||||
|
||||
const Vav2Player::VideoMetadata& GetVideoMetadata() const override;
|
||||
std::string GetFilePath() const override;
|
||||
|
||||
std::vector<Vav2Player::VideoTrackInfo> GetVideoTracks() const override;
|
||||
bool SelectVideoTrack(uint64_t track_number) override;
|
||||
uint64_t GetSelectedTrackNumber() const override;
|
||||
|
||||
bool ReadNextPacket(Vav2Player::VideoPacket& packet) override;
|
||||
bool SeekToFrame(uint64_t frame_index) override;
|
||||
bool SeekToTime(double timestamp_seconds) override;
|
||||
|
||||
uint64_t GetCurrentFrameIndex() const override;
|
||||
double GetCurrentTimestamp() const override;
|
||||
bool IsEndOfFile() const override;
|
||||
|
||||
bool Reset() override;
|
||||
uint64_t GetTotalFrames() const override;
|
||||
double GetDuration() const override;
|
||||
|
||||
Vav2Player::WebMErrorCode GetLastError() const override;
|
||||
std::string GetLastErrorString() const override;
|
||||
|
||||
// Mock-specific control methods for testing
|
||||
void SetMockVideoMetadata(const Vav2Player::VideoMetadata& metadata);
|
||||
void SetMockVideoTracks(const std::vector<Vav2Player::VideoTrackInfo>& tracks);
|
||||
void SetMockPackets(const std::vector<std::vector<uint8_t>>& packets);
|
||||
void SetMockError(Vav2Player::WebMErrorCode error, const std::string& message = "");
|
||||
void SetOpenFileResult(bool success);
|
||||
void SetEndOfFileAtFrame(uint64_t frame_index);
|
||||
|
||||
// Test verification methods
|
||||
uint32_t GetOpenFileCallCount() const { return m_openFileCallCount; }
|
||||
uint32_t GetReadPacketCallCount() const { return m_readPacketCallCount; }
|
||||
std::string GetLastOpenedFile() const { return m_lastOpenedFile; }
|
||||
|
||||
private:
|
||||
// Mock state
|
||||
bool m_isOpen = false;
|
||||
bool m_openFileResult = true;
|
||||
std::string m_currentFilePath;
|
||||
std::string m_lastOpenedFile;
|
||||
uint64_t m_selectedTrack = 0;
|
||||
uint64_t m_currentFrame = 0;
|
||||
uint64_t m_endOfFileFrame = UINT64_MAX;
|
||||
|
||||
// Mock data
|
||||
Vav2Player::VideoMetadata m_mockMetadata;
|
||||
std::vector<Vav2Player::VideoTrackInfo> m_mockTracks;
|
||||
std::vector<std::vector<uint8_t>> m_mockPackets;
|
||||
|
||||
// Error simulation
|
||||
Vav2Player::WebMErrorCode m_mockError = Vav2Player::WebMErrorCode::Success;
|
||||
std::string m_mockErrorMessage;
|
||||
|
||||
// Call tracking for test verification
|
||||
mutable uint32_t m_openFileCallCount = 0;
|
||||
mutable uint32_t m_readPacketCallCount = 0;
|
||||
|
||||
// Helper methods
|
||||
void CreateDefaultMockData();
|
||||
std::vector<uint8_t> CreateMockAV1Packet(uint64_t frame_index);
|
||||
};
|
||||
|
||||
} // namespace Vav2PlayerUnitTests
|
||||
245
vav2/Vav2Player/Vav2Player/unit-test/VideoPlayerControlTest.cpp
Normal file
245
vav2/Vav2Player/Vav2Player/unit-test/VideoPlayerControlTest.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
#include "pch.h"
|
||||
#include "MockWebMFileReader.h"
|
||||
#include "MockVideoRenderer.h"
|
||||
// Note: VideoPlayerControl integration testing would require WinUI3 mocking
|
||||
// For now, this focuses on testing the integration patterns and dependency injection
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
using namespace Vav2Player;
|
||||
using namespace Vav2PlayerUnitTests;
|
||||
|
||||
namespace Vav2PlayerUnitTests
|
||||
{
|
||||
TEST_CLASS(VideoPlayerControlTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(DependencyInjection_MockComponents_ShouldWorkTogether)
|
||||
{
|
||||
// Arrange
|
||||
auto mockReader = std::make_unique<MockWebMFileReader>();
|
||||
auto mockRenderer = std::make_unique<MockVideoRenderer>();
|
||||
|
||||
MockWebMFileReader* readerPtr = mockReader.get();
|
||||
MockVideoRenderer* rendererPtr = mockRenderer.get();
|
||||
|
||||
// Set up mock data
|
||||
VideoMetadata metadata;
|
||||
metadata.width = 1920;
|
||||
metadata.height = 1080;
|
||||
metadata.frame_rate = 30.0;
|
||||
metadata.codec_type = VideoCodecType::AV1;
|
||||
readerPtr->SetMockVideoMetadata(metadata);
|
||||
|
||||
// Act - Simulate VideoPlayerControl workflow
|
||||
bool openResult = readerPtr->OpenFile("test_video.webm");
|
||||
auto tracks = readerPtr->GetVideoTracks();
|
||||
bool selectResult = readerPtr->SelectVideoTrack(tracks[0].track_number);
|
||||
|
||||
HRESULT initResult = rendererPtr->Initialize(metadata.width, metadata.height);
|
||||
|
||||
// Read and "render" first frame
|
||||
VideoPacket packet;
|
||||
bool readResult = readerPtr->ReadNextPacket(packet);
|
||||
|
||||
// Create mock video frame for rendering test
|
||||
VideoFrame frame;
|
||||
frame.width = metadata.width;
|
||||
frame.height = metadata.height;
|
||||
frame.format = PixelFormat::YUV420P;
|
||||
frame.frame_index = 0;
|
||||
|
||||
bool renderResult = rendererPtr->TryRenderFrame(frame);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(openResult);
|
||||
Assert::IsTrue(selectResult);
|
||||
Assert::AreEqual(S_OK, initResult);
|
||||
Assert::IsTrue(readResult);
|
||||
Assert::IsTrue(renderResult);
|
||||
|
||||
// Verify call counts
|
||||
Assert::AreEqual(1u, readerPtr->GetOpenFileCallCount());
|
||||
Assert::AreEqual(1u, readerPtr->GetReadPacketCallCount());
|
||||
Assert::AreEqual(1u, rendererPtr->GetInitializeCallCount());
|
||||
Assert::AreEqual(1u, rendererPtr->GetTryRenderFrameCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(ErrorHandling_FileNotFound_ShouldPropagateError)
|
||||
{
|
||||
// Arrange
|
||||
auto mockReader = std::make_unique<MockWebMFileReader>();
|
||||
mockReader->SetOpenFileResult(false);
|
||||
|
||||
// Act
|
||||
bool result = mockReader->OpenFile("nonexistent.webm");
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
Assert::AreEqual(WebMErrorCode::FileNotFound, mockReader->GetLastError());
|
||||
Assert::IsFalse(mockReader->GetLastErrorString().empty());
|
||||
}
|
||||
|
||||
TEST_METHOD(ErrorHandling_RendererInitializationFailure_ShouldBeHandled)
|
||||
{
|
||||
// Arrange
|
||||
auto mockRenderer = std::make_unique<MockVideoRenderer>();
|
||||
mockRenderer->SimulateInitializationFailure(true);
|
||||
|
||||
// Act
|
||||
HRESULT result = mockRenderer->Initialize(1920, 1080);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(E_FAIL, result);
|
||||
Assert::IsFalse(mockRenderer->IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(PlaybackSimulation_MultipleFrames_ShouldMaintainState)
|
||||
{
|
||||
// Arrange
|
||||
auto mockReader = std::make_unique<MockWebMFileReader>();
|
||||
auto mockRenderer = std::make_unique<MockVideoRenderer>();
|
||||
|
||||
mockReader->OpenFile("test.webm");
|
||||
mockReader->SelectVideoTrack(1);
|
||||
mockRenderer->Initialize(1920, 1080);
|
||||
|
||||
// Act - Simulate playing 10 frames
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
VideoPacket packet;
|
||||
bool readResult = mockReader->ReadNextPacket(packet);
|
||||
|
||||
if (readResult) {
|
||||
// Create frame from packet (simplified)
|
||||
VideoFrame frame;
|
||||
frame.width = 1920;
|
||||
frame.height = 1080;
|
||||
frame.frame_index = packet.frame_index;
|
||||
frame.timestamp_ns = static_cast<uint64_t>(packet.timestamp_seconds * 1000000000.0);
|
||||
|
||||
mockRenderer->TryRenderFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(10u, mockReader->GetReadPacketCallCount());
|
||||
Assert::AreEqual(10u, mockRenderer->GetTryRenderFrameCallCount());
|
||||
Assert::AreEqual(static_cast<uint64_t>(10u), mockReader->GetCurrentFrameIndex());
|
||||
Assert::AreEqual(10u, mockRenderer->GetTotalRenderedFrames());
|
||||
}
|
||||
|
||||
TEST_METHOD(SeekOperation_ShouldUpdateReaderState)
|
||||
{
|
||||
// Arrange
|
||||
auto mockReader = std::make_unique<MockWebMFileReader>();
|
||||
mockReader->OpenFile("test.webm");
|
||||
mockReader->SelectVideoTrack(1);
|
||||
|
||||
// Read some packets first
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
VideoPacket packet;
|
||||
mockReader->ReadNextPacket(packet);
|
||||
}
|
||||
|
||||
// Act - Seek to frame 50
|
||||
bool seekResult = mockReader->SeekToFrame(50);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(seekResult);
|
||||
Assert::AreEqual(50u, static_cast<uint32_t>(mockReader->GetCurrentFrameIndex()));
|
||||
|
||||
// Next packet should be frame 50
|
||||
VideoPacket packet;
|
||||
bool readResult = mockReader->ReadNextPacket(packet);
|
||||
Assert::IsTrue(readResult);
|
||||
Assert::AreEqual(50u, static_cast<uint32_t>(packet.frame_index));
|
||||
}
|
||||
|
||||
TEST_METHOD(AspectRatioHandling_ShouldUpdateRenderer)
|
||||
{
|
||||
// Arrange
|
||||
auto mockRenderer = std::make_unique<MockVideoRenderer>();
|
||||
mockRenderer->Initialize(1920, 1080);
|
||||
|
||||
// Act - Simulate container size changes (AspectFit scenario)
|
||||
mockRenderer->UpdateContainerSize(1280, 720); // 16:9 container
|
||||
mockRenderer->SetAspectFitMode(true);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(1280u, mockRenderer->GetContainerWidth());
|
||||
Assert::AreEqual(720u, mockRenderer->GetContainerHeight());
|
||||
Assert::IsTrue(mockRenderer->GetAspectFitMode());
|
||||
}
|
||||
|
||||
TEST_METHOD(ResourceCleanup_ShouldReleaseResources)
|
||||
{
|
||||
// Arrange
|
||||
auto mockReader = std::make_unique<MockWebMFileReader>();
|
||||
auto mockRenderer = std::make_unique<MockVideoRenderer>();
|
||||
|
||||
mockReader->OpenFile("test.webm");
|
||||
mockRenderer->Initialize(1920, 1080);
|
||||
|
||||
// Render some frames
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
VideoFrame frame;
|
||||
frame.width = 1920;
|
||||
frame.height = 1080;
|
||||
frame.frame_index = i;
|
||||
mockRenderer->RenderVideoFrame(frame);
|
||||
}
|
||||
|
||||
// Act - Cleanup
|
||||
mockReader->CloseFile();
|
||||
mockRenderer->Shutdown();
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(mockReader->IsFileOpen());
|
||||
Assert::IsFalse(mockRenderer->IsInitialized());
|
||||
Assert::AreEqual(0u, mockRenderer->GetTotalRenderedFrames()); // Should be cleared
|
||||
}
|
||||
|
||||
TEST_METHOD(PerformanceMetrics_ShouldBeTracked)
|
||||
{
|
||||
// Arrange
|
||||
auto mockRenderer = std::make_unique<MockVideoRenderer>();
|
||||
mockRenderer->Initialize(1920, 1080);
|
||||
mockRenderer->SetRenderDelay(5); // 5ms per frame
|
||||
|
||||
// Act - Render frames with timing
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
VideoFrame frame;
|
||||
frame.width = 1920;
|
||||
frame.height = 1080;
|
||||
frame.frame_index = i;
|
||||
mockRenderer->RenderVideoFrame(frame);
|
||||
}
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto totalTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(totalTime.count() >= 25); // At least 25ms (5ms * 5 frames)
|
||||
Assert::IsTrue(mockRenderer->GetTotalRenderTime() >= 25);
|
||||
Assert::AreEqual(5u, mockRenderer->GetTotalRenderedFrames());
|
||||
}
|
||||
|
||||
TEST_METHOD(InterfaceCompatibility_ShouldWorkWithBasePointers)
|
||||
{
|
||||
// Arrange - Test polymorphism through interfaces
|
||||
std::unique_ptr<IWebMFileReader> reader = std::make_unique<MockWebMFileReader>();
|
||||
std::unique_ptr<IVideoRenderer> renderer = std::make_unique<MockVideoRenderer>();
|
||||
|
||||
// Act - Use through interface pointers
|
||||
bool openResult = reader->OpenFile("test.webm");
|
||||
HRESULT initResult = renderer->Initialize(1920, 1080);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(openResult);
|
||||
Assert::AreEqual(S_OK, initResult);
|
||||
Assert::IsTrue(reader->IsFileOpen());
|
||||
Assert::IsTrue(renderer->IsInitialized());
|
||||
}
|
||||
};
|
||||
}
|
||||
288
vav2/Vav2Player/Vav2Player/unit-test/VideoRendererTest.cpp
Normal file
288
vav2/Vav2Player/Vav2Player/unit-test/VideoRendererTest.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#include "pch.h"
|
||||
#include "MockVideoRenderer.h"
|
||||
#include "../src/Common/VideoTypes.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
using namespace Vav2Player;
|
||||
using namespace Vav2PlayerUnitTests;
|
||||
|
||||
namespace Vav2PlayerUnitTests
|
||||
{
|
||||
TEST_CLASS(VideoRendererTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(MockVideoRenderer_Initialize_Success_ShouldReturnSOK)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
|
||||
// Act
|
||||
HRESULT result = mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(S_OK, result);
|
||||
Assert::IsTrue(mockRenderer.IsInitialized());
|
||||
Assert::AreEqual(1920u, mockRenderer.GetWidth());
|
||||
Assert::AreEqual(1080u, mockRenderer.GetHeight());
|
||||
Assert::AreEqual(1u, mockRenderer.GetInitializeCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_Initialize_Failure_ShouldReturnError)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.SetInitializeResult(E_FAIL);
|
||||
|
||||
// Act
|
||||
HRESULT result = mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(E_FAIL, result);
|
||||
Assert::IsFalse(mockRenderer.IsInitialized());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_RenderVideoFrame_WhenInitialized_ShouldSucceed)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
VideoFrame testFrame;
|
||||
testFrame.width = 1920;
|
||||
testFrame.height = 1080;
|
||||
testFrame.format = PixelFormat::YUV420P;
|
||||
testFrame.frame_index = 0;
|
||||
testFrame.timestamp_ns = 0;
|
||||
|
||||
// Create test YUV data
|
||||
testFrame.y_stride = 1920;
|
||||
testFrame.u_stride = 960;
|
||||
testFrame.v_stride = 960;
|
||||
|
||||
size_t y_size = testFrame.y_stride * testFrame.height;
|
||||
size_t uv_size = testFrame.u_stride * (testFrame.height / 2);
|
||||
|
||||
testFrame.y_plane = std::make_unique<uint8_t[]>(y_size);
|
||||
testFrame.u_plane = std::make_unique<uint8_t[]>(uv_size);
|
||||
testFrame.v_plane = std::make_unique<uint8_t[]>(uv_size);
|
||||
|
||||
// Fill with test pattern
|
||||
std::memset(testFrame.y_plane.get(), 128, y_size);
|
||||
std::memset(testFrame.u_plane.get(), 64, uv_size);
|
||||
std::memset(testFrame.v_plane.get(), 192, uv_size);
|
||||
|
||||
// Act
|
||||
HRESULT result = mockRenderer.RenderVideoFrame(testFrame);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(S_OK, result);
|
||||
Assert::AreEqual(1u, mockRenderer.GetRenderFrameCallCount());
|
||||
Assert::AreEqual(1u, mockRenderer.GetTotalRenderedFrames());
|
||||
|
||||
const auto& renderedFrames = mockRenderer.GetRenderedFrames();
|
||||
Assert::AreEqual(1u, static_cast<uint32_t>(renderedFrames.size()));
|
||||
Assert::AreEqual(1920u, renderedFrames[0].width);
|
||||
Assert::AreEqual(1080u, renderedFrames[0].height);
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_TryRenderFrame_WhenInitialized_ShouldReturnTrue)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
VideoFrame testFrame;
|
||||
testFrame.width = 1920;
|
||||
testFrame.height = 1080;
|
||||
testFrame.format = PixelFormat::YUV420P;
|
||||
|
||||
// Act
|
||||
bool result = mockRenderer.TryRenderFrame(testFrame);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::AreEqual(1u, mockRenderer.GetTryRenderFrameCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_TryRenderFrame_WhenNotInitialized_ShouldReturnFalse)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
|
||||
VideoFrame testFrame;
|
||||
testFrame.width = 1920;
|
||||
testFrame.height = 1080;
|
||||
|
||||
// Act
|
||||
bool result = mockRenderer.TryRenderFrame(testFrame);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_Present_WhenInitialized_ShouldReturnSOK)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
// Act
|
||||
HRESULT result = mockRenderer.Present();
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(S_OK, result);
|
||||
Assert::AreEqual(1u, mockRenderer.GetPresentCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_Resize_ValidSize_ShouldUpdateDimensions)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
// Act
|
||||
HRESULT result = mockRenderer.Resize(3840, 2160);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(S_OK, result);
|
||||
Assert::AreEqual(3840u, mockRenderer.GetWidth());
|
||||
Assert::AreEqual(2160u, mockRenderer.GetHeight());
|
||||
Assert::AreEqual(1u, mockRenderer.GetResizeCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_Shutdown_ShouldResetState)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
VideoFrame testFrame;
|
||||
testFrame.width = 1920;
|
||||
testFrame.height = 1080;
|
||||
mockRenderer.RenderVideoFrame(testFrame);
|
||||
|
||||
// Act
|
||||
mockRenderer.Shutdown();
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(mockRenderer.IsInitialized());
|
||||
Assert::AreEqual(0u, mockRenderer.GetWidth());
|
||||
Assert::AreEqual(0u, mockRenderer.GetHeight());
|
||||
Assert::AreEqual(0u, mockRenderer.GetTotalRenderedFrames());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_SetAspectFitMode_ShouldUpdateState)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
|
||||
// Act
|
||||
mockRenderer.SetAspectFitMode(true);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(mockRenderer.GetAspectFitMode());
|
||||
|
||||
// Act
|
||||
mockRenderer.SetAspectFitMode(false);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(mockRenderer.GetAspectFitMode());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_UpdateContainerSize_ShouldUpdateDimensions)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
|
||||
// Act
|
||||
mockRenderer.UpdateContainerSize(1280, 720);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(1280u, mockRenderer.GetContainerWidth());
|
||||
Assert::AreEqual(720u, mockRenderer.GetContainerHeight());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_SimulateFailures_ShouldBehaveCorrectly)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
|
||||
// Test initialization failure
|
||||
mockRenderer.SimulateInitializationFailure(true);
|
||||
HRESULT initResult = mockRenderer.Initialize(1920, 1080);
|
||||
Assert::AreEqual(E_FAIL, initResult);
|
||||
Assert::IsFalse(mockRenderer.IsInitialized());
|
||||
|
||||
// Reset and test rendering failure
|
||||
mockRenderer.SimulateInitializationFailure(false);
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
mockRenderer.SimulateRenderingFailure(true);
|
||||
|
||||
VideoFrame testFrame;
|
||||
testFrame.width = 1920;
|
||||
testFrame.height = 1080;
|
||||
|
||||
// Act
|
||||
HRESULT renderResult = mockRenderer.RenderVideoFrame(testFrame);
|
||||
bool tryRenderResult = mockRenderer.TryRenderFrame(testFrame);
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(E_FAIL, renderResult);
|
||||
Assert::IsFalse(tryRenderResult);
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_PerformanceSimulation_ShouldTrackTime)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
mockRenderer.SetRenderDelay(10); // 10ms delay
|
||||
|
||||
VideoFrame testFrame;
|
||||
testFrame.width = 1920;
|
||||
testFrame.height = 1080;
|
||||
|
||||
// Act
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
mockRenderer.RenderVideoFrame(testFrame);
|
||||
auto endTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto actualDuration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(actualDuration.count() >= 10); // Should take at least 10ms
|
||||
Assert::IsTrue(mockRenderer.GetTotalRenderTime() >= 10); // Should track the time
|
||||
}
|
||||
|
||||
TEST_METHOD(MockVideoRenderer_MultipleFrames_ShouldTrackAll)
|
||||
{
|
||||
// Arrange
|
||||
MockVideoRenderer mockRenderer;
|
||||
mockRenderer.Initialize(1920, 1080);
|
||||
|
||||
// Act - Render 5 frames
|
||||
for (uint32_t i = 0; i < 5; ++i) {
|
||||
VideoFrame testFrame;
|
||||
testFrame.width = 1920;
|
||||
testFrame.height = 1080;
|
||||
testFrame.frame_index = i;
|
||||
testFrame.timestamp_ns = i * 33333333; // ~30fps
|
||||
|
||||
mockRenderer.RenderVideoFrame(testFrame);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(5u, mockRenderer.GetRenderFrameCallCount());
|
||||
Assert::AreEqual(5u, mockRenderer.GetTotalRenderedFrames());
|
||||
|
||||
const auto& renderedFrames = mockRenderer.GetRenderedFrames();
|
||||
Assert::AreEqual(5u, static_cast<uint32_t>(renderedFrames.size()));
|
||||
|
||||
// Verify frame indices are correct
|
||||
for (uint32_t i = 0; i < 5; ++i) {
|
||||
Assert::AreEqual(static_cast<uint64_t>(i), renderedFrames[i].frame_index);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
220
vav2/Vav2Player/Vav2Player/unit-test/WebMFileReaderTest.cpp
Normal file
220
vav2/Vav2Player/Vav2Player/unit-test/WebMFileReaderTest.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
#include "pch.h"
|
||||
#include "MockWebMFileReader.h"
|
||||
#include "../src/FileIO/WebMFileReader.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
using namespace Vav2Player;
|
||||
using namespace Vav2PlayerUnitTests;
|
||||
|
||||
namespace Vav2PlayerUnitTests
|
||||
{
|
||||
TEST_CLASS(WebMFileReaderTest)
|
||||
{
|
||||
public:
|
||||
TEST_METHOD(MockWebMFileReader_OpenFile_Success_ShouldReturnTrue)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
std::string testFile = "test_video.webm";
|
||||
|
||||
// Act
|
||||
bool result = mockReader.OpenFile(testFile);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::IsTrue(mockReader.IsFileOpen());
|
||||
Assert::AreEqual(testFile, mockReader.GetFilePath());
|
||||
Assert::AreEqual(1u, mockReader.GetOpenFileCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_OpenFile_Failure_ShouldReturnFalse)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.SetOpenFileResult(false);
|
||||
std::string testFile = "nonexistent.webm";
|
||||
|
||||
// Act
|
||||
bool result = mockReader.OpenFile(testFile);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
Assert::IsFalse(mockReader.IsFileOpen());
|
||||
Assert::AreEqual(WebMErrorCode::FileNotFound, mockReader.GetLastError());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_GetVideoTracks_ShouldReturnDefaultTrack)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
|
||||
// Act
|
||||
auto tracks = mockReader.GetVideoTracks();
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(1u, static_cast<uint32_t>(tracks.size()));
|
||||
Assert::AreEqual(1u, static_cast<uint32_t>(tracks[0].track_number));
|
||||
Assert::AreEqual(VideoCodecType::AV1, tracks[0].codec_type);
|
||||
Assert::AreEqual(1920u, tracks[0].width);
|
||||
Assert::AreEqual(1080u, tracks[0].height);
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_SelectVideoTrack_ValidTrack_ShouldReturnTrue)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
|
||||
// Act
|
||||
bool result = mockReader.SelectVideoTrack(1);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::AreEqual(1u, static_cast<uint32_t>(mockReader.GetSelectedTrackNumber()));
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_SelectVideoTrack_InvalidTrack_ShouldReturnFalse)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
|
||||
// Act
|
||||
bool result = mockReader.SelectVideoTrack(999);
|
||||
|
||||
// Assert
|
||||
Assert::IsFalse(result);
|
||||
Assert::AreEqual(WebMErrorCode::InvalidTrack, mockReader.GetLastError());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_ReadNextPacket_ShouldReturnValidPackets)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
mockReader.SelectVideoTrack(1);
|
||||
|
||||
// Act
|
||||
VideoPacket packet;
|
||||
bool result = mockReader.ReadNextPacket(packet);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::IsNotNull(packet.data.get());
|
||||
Assert::IsTrue(packet.size > 0);
|
||||
Assert::AreEqual(0u, static_cast<uint32_t>(packet.frame_index));
|
||||
// Note: track_number field doesn't exist in VideoPacket, skipping this assertion
|
||||
Assert::AreEqual(1u, mockReader.GetReadPacketCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_ReadMultiplePackets_ShouldIncrementFrameIndex)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
mockReader.SelectVideoTrack(1);
|
||||
|
||||
// Act & Assert
|
||||
for (uint64_t i = 0; i < 5; ++i) {
|
||||
VideoPacket packet;
|
||||
bool result = mockReader.ReadNextPacket(packet);
|
||||
|
||||
Assert::IsTrue(result);
|
||||
Assert::AreEqual(i, packet.frame_index);
|
||||
Assert::AreEqual(i + 1, mockReader.GetCurrentFrameIndex());
|
||||
}
|
||||
|
||||
Assert::AreEqual(5u, mockReader.GetReadPacketCallCount());
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_SeekToFrame_ValidFrame_ShouldUpdatePosition)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
mockReader.SelectVideoTrack(1);
|
||||
|
||||
// Act
|
||||
bool result = mockReader.SeekToFrame(50);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::AreEqual(50u, static_cast<uint32_t>(mockReader.GetCurrentFrameIndex()));
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_SeekToTime_ValidTime_ShouldUpdatePosition)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
mockReader.SelectVideoTrack(1);
|
||||
|
||||
// Act (Seek to 1 second = frame 30 at 30fps)
|
||||
bool result = mockReader.SeekToTime(1.0);
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::AreEqual(30u, static_cast<uint32_t>(mockReader.GetCurrentFrameIndex()));
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_Reset_ShouldReturnToBeginning)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
mockReader.SelectVideoTrack(1);
|
||||
mockReader.SeekToFrame(50);
|
||||
|
||||
// Act
|
||||
bool result = mockReader.Reset();
|
||||
|
||||
// Assert
|
||||
Assert::IsTrue(result);
|
||||
Assert::AreEqual(0u, static_cast<uint32_t>(mockReader.GetCurrentFrameIndex()));
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_GetVideoMetadata_ShouldReturnValidData)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.OpenFile("test.webm");
|
||||
|
||||
// Act
|
||||
const VideoMetadata& metadata = mockReader.GetVideoMetadata();
|
||||
|
||||
// Assert
|
||||
Assert::AreEqual(1920u, metadata.width);
|
||||
Assert::AreEqual(1080u, metadata.height);
|
||||
Assert::AreEqual(30.0, metadata.frame_rate, 0.01);
|
||||
Assert::AreEqual(VideoCodecType::AV1, metadata.codec_type);
|
||||
Assert::AreEqual(ColorSpace::BT709, metadata.color_space);
|
||||
Assert::AreEqual(100u, static_cast<uint32_t>(metadata.total_frames));
|
||||
}
|
||||
|
||||
TEST_METHOD(MockWebMFileReader_EndOfFile_ShouldDetectCorrectly)
|
||||
{
|
||||
// Arrange
|
||||
MockWebMFileReader mockReader;
|
||||
mockReader.SetEndOfFileAtFrame(3); // Set EOF at frame 3
|
||||
mockReader.OpenFile("test.webm");
|
||||
mockReader.SelectVideoTrack(1);
|
||||
|
||||
// Act & Assert
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
VideoPacket packet;
|
||||
bool result = mockReader.ReadNextPacket(packet);
|
||||
Assert::IsTrue(result);
|
||||
Assert::IsFalse(mockReader.IsEndOfFile());
|
||||
}
|
||||
|
||||
// Should now be at EOF
|
||||
Assert::IsTrue(mockReader.IsEndOfFile());
|
||||
|
||||
// Next read should fail
|
||||
VideoPacket packet;
|
||||
bool result = mockReader.ReadNextPacket(packet);
|
||||
Assert::IsFalse(result);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -32,4 +32,53 @@ extern "C" {
|
||||
|
||||
// Project headers (common types and interfaces)
|
||||
#include "../src/Common/VideoTypes.h"
|
||||
#include "../src/Decoder/IVideoDecoder.h"
|
||||
#include "../src/Decoder/IVideoDecoder.h"
|
||||
#include "../src/FileIO/IWebMFileReader.h"
|
||||
|
||||
// ToString specializations for custom types
|
||||
namespace Microsoft::VisualStudio::CppUnitTestFramework
|
||||
{
|
||||
template<> inline std::wstring ToString<Vav2Player::WebMErrorCode>(const Vav2Player::WebMErrorCode& t)
|
||||
{
|
||||
switch (t) {
|
||||
case Vav2Player::WebMErrorCode::Success: return L"Success";
|
||||
case Vav2Player::WebMErrorCode::FileNotFound: return L"FileNotFound";
|
||||
case Vav2Player::WebMErrorCode::InvalidFormat: return L"InvalidFormat";
|
||||
case Vav2Player::WebMErrorCode::UnsupportedCodec: return L"UnsupportedCodec";
|
||||
case Vav2Player::WebMErrorCode::NoVideoTrack: return L"NoVideoTrack";
|
||||
case Vav2Player::WebMErrorCode::ReadError: return L"ReadError";
|
||||
case Vav2Player::WebMErrorCode::SeekError: return L"SeekError";
|
||||
case Vav2Player::WebMErrorCode::FileNotOpen: return L"FileNotOpen";
|
||||
case Vav2Player::WebMErrorCode::InvalidTrack: return L"InvalidTrack";
|
||||
case Vav2Player::WebMErrorCode::SeekFailed: return L"SeekFailed";
|
||||
case Vav2Player::WebMErrorCode::Unknown: return L"Unknown";
|
||||
default: return L"Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
template<> inline std::wstring ToString<Vav2Player::VideoCodecType>(const Vav2Player::VideoCodecType& t)
|
||||
{
|
||||
switch (t) {
|
||||
case Vav2Player::VideoCodecType::AV1: return L"AV1";
|
||||
case Vav2Player::VideoCodecType::VP9: return L"VP9";
|
||||
case Vav2Player::VideoCodecType::VP8: return L"VP8";
|
||||
case Vav2Player::VideoCodecType::H264: return L"H264";
|
||||
case Vav2Player::VideoCodecType::H265: return L"H265";
|
||||
default: return L"Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
template<> inline std::wstring ToString<Vav2Player::ColorSpace>(const Vav2Player::ColorSpace& t)
|
||||
{
|
||||
switch (t) {
|
||||
case Vav2Player::ColorSpace::YUV420P: return L"YUV420P";
|
||||
case Vav2Player::ColorSpace::YUV422P: return L"YUV422P";
|
||||
case Vav2Player::ColorSpace::YUV444P: return L"YUV444P";
|
||||
case Vav2Player::ColorSpace::RGB24: return L"RGB24";
|
||||
case Vav2Player::ColorSpace::RGB32: return L"RGB32";
|
||||
case Vav2Player::ColorSpace::BT709: return L"BT709";
|
||||
case Vav2Player::ColorSpace::BT2020: return L"BT2020";
|
||||
default: return L"Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user