666 lines
20 KiB
Markdown
666 lines
20 KiB
Markdown
|
|
# Intel VPL AV1 디코더 설계 문서
|
||
|
|
|
||
|
|
## 📋 개요
|
||
|
|
|
||
|
|
Intel Video Processing Library (VPL)를 사용하여 VavCore에 AV1 디코더를 추가하는 설계 문서입니다.
|
||
|
|
|
||
|
|
**목적**: Intel QuickSync 하드웨어 가속을 활용한 고성능 AV1 디코딩
|
||
|
|
**대상 플랫폼**: Intel GPU 지원 시스템 (소프트웨어 fallback 포함)
|
||
|
|
**통합 위치**: VavCore 디코더 팩토리 시스템
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🏗️ 라이브러리 분석
|
||
|
|
|
||
|
|
### **Intel VPL 구조**
|
||
|
|
- **소스 위치**: `D:\Project\video-av1\oss\libvpl`
|
||
|
|
- **주요 헤더**:
|
||
|
|
- `api/vpl/mfxvideo.h` - 비디오 디코딩 API
|
||
|
|
- `api/vpl/mfxstructures.h` - 데이터 구조체
|
||
|
|
- **참고 문서**: https://intel.github.io/libvpl/latest/API_ref/VPL_func_vid_decode.html
|
||
|
|
|
||
|
|
### **핵심 API 워크플로우**
|
||
|
|
1. **MFXLoad()** - VPL 라이브러리 로더 생성
|
||
|
|
2. **MFXCreateConfig()** - 디코더 구성 설정 (HW/SW, 코덱 타입)
|
||
|
|
3. **MFXCreateSession()** - 디코딩 세션 생성
|
||
|
|
4. **MFXVideoDECODE_DecodeHeader()** - 비트스트림 헤더 파싱
|
||
|
|
5. **MFXVideoDECODE_QueryIOSurf()** - 필요한 표면 개수 계산
|
||
|
|
6. **MFXVideoDECODE_Init()** - 디코더 초기화
|
||
|
|
7. **MFXVideoDECODE_DecodeFrameAsync()** - 비동기 프레임 디코딩
|
||
|
|
8. **MFXVideoCORE_SyncOperation()** - 디코딩 완료 대기
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 클래스 설계
|
||
|
|
|
||
|
|
### **VPLAV1Decoder 클래스 구조**
|
||
|
|
```cpp
|
||
|
|
namespace VavCore {
|
||
|
|
|
||
|
|
class VPLAV1Decoder : public IVideoDecoder {
|
||
|
|
public:
|
||
|
|
VPLAV1Decoder();
|
||
|
|
~VPLAV1Decoder() override;
|
||
|
|
|
||
|
|
// Prevent copying
|
||
|
|
VPLAV1Decoder(const VPLAV1Decoder&) = delete;
|
||
|
|
VPLAV1Decoder& operator=(const VPLAV1Decoder&) = delete;
|
||
|
|
|
||
|
|
// IVideoDecoder 인터페이스 구현
|
||
|
|
bool Initialize(const VideoMetadata& metadata) override;
|
||
|
|
void Cleanup() override;
|
||
|
|
bool IsInitialized() const override;
|
||
|
|
|
||
|
|
bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) override;
|
||
|
|
bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override;
|
||
|
|
|
||
|
|
bool Reset() override;
|
||
|
|
bool Flush() override;
|
||
|
|
|
||
|
|
// IVideoDecoder 인터페이스 - 추가 메서드
|
||
|
|
std::string GetCodecName() const override { return "AV1 (Intel VPL)"; }
|
||
|
|
VideoCodecType GetCodecType() const override { return VideoCodecType::AV1; }
|
||
|
|
std::string GetVersion() const override;
|
||
|
|
DecoderStats GetStats() const override;
|
||
|
|
void ResetStats() override;
|
||
|
|
|
||
|
|
// VPL 전용 메서드
|
||
|
|
bool IsVPLAvailable() const;
|
||
|
|
bool InitializeVPL();
|
||
|
|
|
||
|
|
protected:
|
||
|
|
// Protected members for inheritance (AdaptiveVPLDecoder)
|
||
|
|
mfxSession m_session = nullptr;
|
||
|
|
mfxVideoParam m_videoParams = {};
|
||
|
|
uint32_t m_width = 0;
|
||
|
|
uint32_t m_height = 0;
|
||
|
|
uint32_t m_maxWidth = 4096;
|
||
|
|
uint32_t m_maxHeight = 4096;
|
||
|
|
|
||
|
|
// Protected helper methods
|
||
|
|
void LogVPLError(mfxStatus status, const std::string& operation) const;
|
||
|
|
|
||
|
|
private:
|
||
|
|
// VPL 객체
|
||
|
|
mfxLoader m_loader = nullptr;
|
||
|
|
mfxConfig m_config = nullptr;
|
||
|
|
|
||
|
|
// 표면 관리
|
||
|
|
std::vector<mfxFrameSurface1> m_surfaces;
|
||
|
|
mfxU16 m_numSurfaces = 0;
|
||
|
|
|
||
|
|
// 비트스트림 버퍼
|
||
|
|
std::unique_ptr<mfxU8[]> m_bitstreamBuffer;
|
||
|
|
mfxU32 m_bitstreamBufferSize = 2 * 1024 * 1024; // 2MB 기본값
|
||
|
|
|
||
|
|
// 통계
|
||
|
|
uint64_t m_framesDecoded = 0;
|
||
|
|
uint64_t m_decodeErrors = 0;
|
||
|
|
double m_avgDecodeTime = 0.0;
|
||
|
|
uint64_t m_bytesProcessed = 0;
|
||
|
|
|
||
|
|
// 상태
|
||
|
|
bool m_initialized = false;
|
||
|
|
bool m_headerParsed = false;
|
||
|
|
|
||
|
|
// 헬퍼 메서드
|
||
|
|
bool CheckVPLCapability();
|
||
|
|
bool CreateSession();
|
||
|
|
bool SetupDecoder();
|
||
|
|
bool AllocateSurfaces();
|
||
|
|
void CleanupVPL();
|
||
|
|
|
||
|
|
// 프레임 변환
|
||
|
|
bool ConvertVPLSurface(mfxFrameSurface1* surface, VideoFrame& output_frame);
|
||
|
|
mfxFrameSurface1* GetFreeSurface();
|
||
|
|
|
||
|
|
// 비트스트림 처리
|
||
|
|
bool PrepareDataForDecode(const uint8_t* packet_data, size_t packet_size, mfxBitstream& bitstream);
|
||
|
|
|
||
|
|
// 에러 처리
|
||
|
|
void LogError(const std::string& message) const;
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace VavCore
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ⚙️ 주요 구현 단계
|
||
|
|
|
||
|
|
### **1. 초기화 단계 (Initialize)**
|
||
|
|
```cpp
|
||
|
|
bool VPLAV1Decoder::Initialize(const VideoMetadata& metadata) {
|
||
|
|
if (m_initialized) {
|
||
|
|
LogError("Decoder already initialized");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Store video dimensions
|
||
|
|
m_width = metadata.width;
|
||
|
|
m_height = metadata.height;
|
||
|
|
|
||
|
|
// Initialize VPL
|
||
|
|
if (!InitializeVPL()) {
|
||
|
|
LogError("Failed to initialize VPL");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create VPL session
|
||
|
|
if (!CreateSession()) {
|
||
|
|
LogError("Failed to create VPL session");
|
||
|
|
CleanupVPL();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Setup decoder parameters
|
||
|
|
if (!SetupDecoder()) {
|
||
|
|
LogError("Failed to setup decoder parameters");
|
||
|
|
CleanupVPL();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_initialized = true;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// InitializeVPL 구현
|
||
|
|
bool VPLAV1Decoder::InitializeVPL() {
|
||
|
|
// Create loader
|
||
|
|
m_loader = MFXLoad();
|
||
|
|
if (!m_loader) {
|
||
|
|
LogError("Failed to create VPL loader");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create config
|
||
|
|
m_config = MFXCreateConfig(m_loader);
|
||
|
|
if (!m_config) {
|
||
|
|
LogError("Failed to create VPL config");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set AV1 codec filter
|
||
|
|
mfxVariant codecId;
|
||
|
|
codecId.Type = MFX_VARIANT_TYPE_U32;
|
||
|
|
codecId.Data.U32 = MFX_CODEC_AV1;
|
||
|
|
|
||
|
|
mfxStatus status = MFXSetConfigFilterProperty(m_config,
|
||
|
|
(const mfxU8*)"mfxImplDescription.mfxDecoderDescription.decoder.CodecID", codecId);
|
||
|
|
|
||
|
|
if (status != MFX_ERR_NONE) {
|
||
|
|
LogVPLError(status, "SetConfigFilterProperty");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return CheckVPLCapability();
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### **2. 프레임 디코딩 (DecodeFrame)**
|
||
|
|
```cpp
|
||
|
|
bool VPLAV1Decoder::DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) {
|
||
|
|
if (!m_initialized) {
|
||
|
|
LogError("Decoder not initialized");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!packet_data || packet_size == 0) {
|
||
|
|
LogError("Invalid packet data");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto start_time = high_resolution_clock::now();
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Prepare bitstream for decoding
|
||
|
|
mfxBitstream bitstream = {};
|
||
|
|
if (!PrepareDataForDecode(packet_data, packet_size, bitstream)) {
|
||
|
|
LogError("Failed to prepare data for decode");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse header if not done yet
|
||
|
|
if (!m_headerParsed) {
|
||
|
|
mfxStatus status = MFXVideoDECODE_DecodeHeader(m_session, &bitstream, &m_videoParams);
|
||
|
|
if (status != MFX_ERR_NONE) {
|
||
|
|
LogVPLError(status, "DecodeHeader");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Allocate surfaces after header parsing
|
||
|
|
if (!AllocateSurfaces()) {
|
||
|
|
LogError("Failed to allocate surfaces");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_headerParsed = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get free surface for decoding
|
||
|
|
mfxFrameSurface1* work_surface = GetFreeSurface();
|
||
|
|
if (!work_surface) {
|
||
|
|
LogError("No free surface available");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Perform async decoding
|
||
|
|
mfxFrameSurface1* output_surface = nullptr;
|
||
|
|
mfxSyncPoint sync_point = nullptr;
|
||
|
|
|
||
|
|
mfxStatus status = MFXVideoDECODE_DecodeFrameAsync(
|
||
|
|
m_session, &bitstream, work_surface, &output_surface, &sync_point);
|
||
|
|
|
||
|
|
if (status == MFX_ERR_MORE_DATA) {
|
||
|
|
// Need more data - not an error for AV1 stream
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (status != MFX_ERR_NONE && status != MFX_WRN_VIDEO_PARAM_CHANGED) {
|
||
|
|
LogVPLError(status, "DecodeFrameAsync");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wait for decoding to complete
|
||
|
|
if (sync_point) {
|
||
|
|
status = MFXVideoCORE_SyncOperation(m_session, sync_point, MFX_INFINITE);
|
||
|
|
if (status != MFX_ERR_NONE) {
|
||
|
|
LogVPLError(status, "SyncOperation");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Convert output surface to VideoFrame
|
||
|
|
if (output_surface) {
|
||
|
|
if (!ConvertVPLSurface(output_surface, output_frame)) {
|
||
|
|
LogError("Failed to convert VPL surface");
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update statistics
|
||
|
|
++m_framesDecoded;
|
||
|
|
m_bytesProcessed += packet_size;
|
||
|
|
|
||
|
|
auto end_time = high_resolution_clock::now();
|
||
|
|
auto decode_time = duration_cast<microseconds>(end_time - start_time).count() / 1000.0;
|
||
|
|
m_avgDecodeTime = (m_avgDecodeTime * (m_framesDecoded - 1) + decode_time) / m_framesDecoded;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
|
||
|
|
} catch (const std::exception& e) {
|
||
|
|
LogError("Exception in DecodeFrame: " + string(e.what()));
|
||
|
|
++m_decodeErrors;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### **3. 표면 관리 (Surface Management)**
|
||
|
|
```cpp
|
||
|
|
bool VPLAV1Decoder::AllocateSurfaces() {
|
||
|
|
if (!m_session) {
|
||
|
|
LogError("Session not created");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Query required number of surfaces
|
||
|
|
mfxFrameAllocRequest allocRequest = {};
|
||
|
|
mfxStatus status = MFXVideoDECODE_QueryIOSurf(m_session, &m_videoParams, &allocRequest);
|
||
|
|
if (status != MFX_ERR_NONE) {
|
||
|
|
LogVPLError(status, "QueryIOSurf");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Allocate surfaces (suggested number + buffer)
|
||
|
|
m_numSurfaces = allocRequest.NumFrameSuggested + 4; // Extra buffer
|
||
|
|
m_surfaces.resize(m_numSurfaces);
|
||
|
|
|
||
|
|
for (mfxU16 i = 0; i < m_numSurfaces; i++) {
|
||
|
|
memset(&m_surfaces[i], 0, sizeof(mfxFrameSurface1));
|
||
|
|
|
||
|
|
// Copy frame info from decoder parameters
|
||
|
|
m_surfaces[i].Info = m_videoParams.mfx.FrameInfo;
|
||
|
|
|
||
|
|
// For simplicity, we use system memory allocation
|
||
|
|
// In production, you might want to use video memory
|
||
|
|
if (!AllocateSystemMemoryForSurface(&m_surfaces[i])) {
|
||
|
|
LogError("Failed to allocate system memory for surface " + to_string(i));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
mfxFrameSurface1* VPLAV1Decoder::GetFreeSurface() {
|
||
|
|
// Find a free surface (not locked by VPL)
|
||
|
|
for (auto& surface : m_surfaces) {
|
||
|
|
if (surface.Data.Locked == 0) {
|
||
|
|
return &surface;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// All surfaces are busy - this might indicate a pipeline bottleneck
|
||
|
|
LogError("All surfaces are locked - decoder pipeline bottleneck");
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool VPLAV1Decoder::AllocateSystemMemoryForSurface(mfxFrameSurface1* surface) {
|
||
|
|
mfxFrameInfo& info = surface->Info;
|
||
|
|
|
||
|
|
// Calculate required buffer size for YUV420 format
|
||
|
|
mfxU32 surfaceSize = info.Width * info.Height * 3 / 2; // YUV420P
|
||
|
|
|
||
|
|
// For NV12, we might need different calculations
|
||
|
|
if (info.FourCC == MFX_FOURCC_NV12) {
|
||
|
|
surfaceSize = info.Width * info.Height + (info.Width * info.Height) / 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Allocate buffer
|
||
|
|
mfxU8* buffer = new (std::nothrow) mfxU8[surfaceSize];
|
||
|
|
if (!buffer) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set up surface data pointers
|
||
|
|
surface->Data.Y = buffer;
|
||
|
|
surface->Data.U = buffer + info.Width * info.Height;
|
||
|
|
surface->Data.V = surface->Data.U + (info.Width * info.Height) / 4;
|
||
|
|
surface->Data.Pitch = info.Width;
|
||
|
|
|
||
|
|
// For NV12, UV is interleaved
|
||
|
|
if (info.FourCC == MFX_FOURCC_NV12) {
|
||
|
|
surface->Data.UV = surface->Data.U;
|
||
|
|
surface->Data.V = surface->Data.UV + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### **4. 데이터 변환 (VPL → VideoFrame)**
|
||
|
|
```cpp
|
||
|
|
bool VPLAV1Decoder::ConvertVPLSurface(mfxFrameSurface1* surface, VideoFrame& output_frame) {
|
||
|
|
if (!surface || !surface->Data.Y) {
|
||
|
|
LogError("Invalid surface for conversion");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
mfxFrameInfo& info = surface->Info;
|
||
|
|
|
||
|
|
// Set output frame metadata
|
||
|
|
output_frame.width = info.CropW ? info.CropW : info.Width;
|
||
|
|
output_frame.height = info.CropH ? info.CropH : info.Height;
|
||
|
|
output_frame.timestamp = 0; // VPL doesn't provide timestamp directly
|
||
|
|
|
||
|
|
// Determine and set color space
|
||
|
|
switch (info.FourCC) {
|
||
|
|
case MFX_FOURCC_NV12:
|
||
|
|
output_frame.color_space = ColorSpace::NV12;
|
||
|
|
break;
|
||
|
|
case MFX_FOURCC_I420:
|
||
|
|
case MFX_FOURCC_IYUV:
|
||
|
|
output_frame.color_space = ColorSpace::YUV420P;
|
||
|
|
break;
|
||
|
|
case MFX_FOURCC_YUY2:
|
||
|
|
output_frame.color_space = ColorSpace::YUV422P;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
LogError("Unsupported pixel format: " + to_string(info.FourCC));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate plane sizes for the new VideoFrame structure
|
||
|
|
output_frame.y_size = output_frame.width * output_frame.height;
|
||
|
|
output_frame.u_size = output_frame.y_size / 4;
|
||
|
|
output_frame.v_size = output_frame.y_size / 4;
|
||
|
|
|
||
|
|
// Allocate memory for each plane
|
||
|
|
output_frame.y_plane = std::make_unique<uint8_t[]>(output_frame.y_size);
|
||
|
|
output_frame.u_plane = std::make_unique<uint8_t[]>(output_frame.u_size);
|
||
|
|
output_frame.v_plane = std::make_unique<uint8_t[]>(output_frame.v_size);
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (info.FourCC == MFX_FOURCC_NV12) {
|
||
|
|
// NV12: Y plane + interleaved UV
|
||
|
|
return ConvertNV12ToYUV420P(surface, output_frame);
|
||
|
|
} else {
|
||
|
|
// I420/IYUV: Separate Y, U, V planes
|
||
|
|
return ConvertI420ToYUV420P(surface, output_frame);
|
||
|
|
}
|
||
|
|
} catch (const std::exception& e) {
|
||
|
|
LogError("Exception during surface conversion: " + string(e.what()));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool VPLAV1Decoder::ConvertNV12ToYUV420P(mfxFrameSurface1* surface, VideoFrame& output_frame) {
|
||
|
|
mfxFrameData& src = surface->Data;
|
||
|
|
mfxFrameInfo& info = surface->Info;
|
||
|
|
|
||
|
|
// Copy Y plane
|
||
|
|
uint8_t* dst_y = output_frame.y_plane.get();
|
||
|
|
uint8_t* src_y = src.Y;
|
||
|
|
|
||
|
|
for (uint32_t y = 0; y < output_frame.height; y++) {
|
||
|
|
memcpy(dst_y, src_y, output_frame.width);
|
||
|
|
dst_y += output_frame.width;
|
||
|
|
src_y += src.Pitch;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Convert interleaved UV to separate U and V planes
|
||
|
|
uint8_t* dst_u = output_frame.u_plane.get();
|
||
|
|
uint8_t* dst_v = output_frame.v_plane.get();
|
||
|
|
uint8_t* src_uv = src.UV;
|
||
|
|
|
||
|
|
uint32_t uv_width = output_frame.width / 2;
|
||
|
|
uint32_t uv_height = output_frame.height / 2;
|
||
|
|
|
||
|
|
for (uint32_t y = 0; y < uv_height; y++) {
|
||
|
|
for (uint32_t x = 0; x < uv_width; x++) {
|
||
|
|
dst_u[x] = src_uv[x * 2]; // U (even indices)
|
||
|
|
dst_v[x] = src_uv[x * 2 + 1]; // V (odd indices)
|
||
|
|
}
|
||
|
|
dst_u += uv_width;
|
||
|
|
dst_v += uv_width;
|
||
|
|
src_uv += src.Pitch;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool VPLAV1Decoder::ConvertI420ToYUV420P(mfxFrameSurface1* surface, VideoFrame& output_frame) {
|
||
|
|
mfxFrameData& src = surface->Data;
|
||
|
|
|
||
|
|
// Copy Y plane
|
||
|
|
uint8_t* dst_y = output_frame.y_plane.get();
|
||
|
|
uint8_t* src_y = src.Y;
|
||
|
|
|
||
|
|
for (uint32_t y = 0; y < output_frame.height; y++) {
|
||
|
|
memcpy(dst_y, src_y, output_frame.width);
|
||
|
|
dst_y += output_frame.width;
|
||
|
|
src_y += src.Pitch;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copy U plane
|
||
|
|
uint8_t* dst_u = output_frame.u_plane.get();
|
||
|
|
uint8_t* src_u = src.U;
|
||
|
|
uint32_t uv_width = output_frame.width / 2;
|
||
|
|
uint32_t uv_height = output_frame.height / 2;
|
||
|
|
|
||
|
|
for (uint32_t y = 0; y < uv_height; y++) {
|
||
|
|
memcpy(dst_u, src_u, uv_width);
|
||
|
|
dst_u += uv_width;
|
||
|
|
src_u += src.Pitch / 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copy V plane
|
||
|
|
uint8_t* dst_v = output_frame.v_plane.get();
|
||
|
|
uint8_t* src_v = src.V;
|
||
|
|
|
||
|
|
for (uint32_t y = 0; y < uv_height; y++) {
|
||
|
|
memcpy(dst_v, src_v, uv_width);
|
||
|
|
dst_v += uv_width;
|
||
|
|
src_v += src.Pitch / 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 VideoDecoderFactory 통합
|
||
|
|
|
||
|
|
### **팩토리 클래스 수정**
|
||
|
|
```cpp
|
||
|
|
// VideoDecoderFactory.h
|
||
|
|
enum class DecoderType {
|
||
|
|
AUTO = 0,
|
||
|
|
ADAPTIVE_NVDEC,
|
||
|
|
ADAPTIVE_DAV1D,
|
||
|
|
NVDEC,
|
||
|
|
DAV1D,
|
||
|
|
MEDIA_FOUNDATION,
|
||
|
|
VPL, // Intel VPL 추가
|
||
|
|
AMF // AMD AMF 추가
|
||
|
|
};
|
||
|
|
|
||
|
|
// VideoDecoderFactory.cpp 주요 부분
|
||
|
|
std::unique_ptr<IVideoDecoder> VideoDecoderFactory::CreateDecoder(DecoderType type) {
|
||
|
|
switch (type) {
|
||
|
|
case DecoderType::AUTO:
|
||
|
|
// 우선순위: Adaptive NVDEC > VPL > AMF > Adaptive dav1d > NVDEC > dav1d > MediaFoundation
|
||
|
|
if (s_nvdec_available) {
|
||
|
|
OutputDebugStringA("[VideoDecoderFactory] Auto mode: trying Adaptive NVDEC AV1 decoder first\n");
|
||
|
|
auto decoder = std::make_unique<AdaptiveNVDECDecoder>();
|
||
|
|
if (decoder) {
|
||
|
|
return decoder;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (s_vpl_available) {
|
||
|
|
OutputDebugStringA("[VideoDecoderFactory] Auto mode: trying Intel VPL AV1 decoder\n");
|
||
|
|
auto decoder = std::make_unique<VPLAV1Decoder>();
|
||
|
|
if (decoder) {
|
||
|
|
return decoder;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (s_amf_available) {
|
||
|
|
OutputDebugStringA("[VideoDecoderFactory] Auto mode: trying AMD AMF AV1 decoder\n");
|
||
|
|
auto decoder = std::make_unique<AMFAV1Decoder>();
|
||
|
|
if (decoder) {
|
||
|
|
return decoder;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Continue with other decoders...
|
||
|
|
break;
|
||
|
|
|
||
|
|
case DecoderType::VPL:
|
||
|
|
if (s_vpl_available) {
|
||
|
|
OutputDebugStringA("[VideoDecoderFactory] Creating Intel VPL AV1 decoder\n");
|
||
|
|
return std::make_unique<VPLAV1Decoder>();
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
// ... 기존 케이스들
|
||
|
|
}
|
||
|
|
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 가용성 검사 구현
|
||
|
|
bool VideoDecoderFactory::CheckVPLAvailability() {
|
||
|
|
try {
|
||
|
|
// Try creating a temporary VPL decoder instance
|
||
|
|
auto temp_decoder = std::make_unique<VPLAV1Decoder>();
|
||
|
|
if (temp_decoder && temp_decoder->IsVPLAvailable()) {
|
||
|
|
OutputDebugStringA("[VideoDecoderFactory] Intel VPL AV1 decoder available\n");
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
} catch (...) {
|
||
|
|
// VPL not available or initialization failed
|
||
|
|
}
|
||
|
|
|
||
|
|
OutputDebugStringA("[VideoDecoderFactory] Intel VPL AV1 decoder not available\n");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 구현 특징 및 장점
|
||
|
|
|
||
|
|
### **주요 장점**
|
||
|
|
- ✅ **Intel QuickSync 하드웨어 가속**: Intel GPU 전용 최적화
|
||
|
|
- ✅ **표준화된 API**: 업계 표준 VPL 라이브러리 사용
|
||
|
|
- ✅ **비동기 처리**: 높은 처리량과 성능
|
||
|
|
- ✅ **자동 Fallback**: 하드웨어 → 소프트웨어 자동 전환
|
||
|
|
- ✅ **메모리 효율성**: VPL 내부 표면 관리 활용
|
||
|
|
|
||
|
|
### **성능 특성**
|
||
|
|
- **대상 플랫폼**: Intel GPU (8세대 이후 권장)
|
||
|
|
- **예상 성능**: 4K AV1 실시간 디코딩 (60fps+)
|
||
|
|
- **메모리 사용**: 하드웨어 가속 시 GPU 메모리 활용
|
||
|
|
|
||
|
|
### **호환성**
|
||
|
|
- **Intel GPU**: 최고 성능
|
||
|
|
- **Non-Intel GPU**: 소프트웨어 구현 fallback
|
||
|
|
- **AMD/NVIDIA**: 기존 디코더 우선 사용 (AUTO 모드)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚧 구현 고려사항
|
||
|
|
|
||
|
|
### **에러 처리**
|
||
|
|
- VPL 특정 에러 코드 매핑
|
||
|
|
- 하드웨어 실패 시 graceful fallback
|
||
|
|
- 메모리 부족 상황 처리
|
||
|
|
|
||
|
|
### **성능 최적화**
|
||
|
|
- 표면 풀링으로 할당 오버헤드 최소화
|
||
|
|
- 비동기 처리로 CPU-GPU 병렬 실행
|
||
|
|
- Zero-copy 메모리 전송 (가능한 경우)
|
||
|
|
|
||
|
|
### **테스트 계획**
|
||
|
|
- Intel GPU 환경에서 성능 벤치마크
|
||
|
|
- 다양한 AV1 비트스트림 호환성 테스트
|
||
|
|
- 메모리 누수 및 안정성 검증
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 구현 우선순위
|
||
|
|
|
||
|
|
### **Phase 1: 기본 구현**
|
||
|
|
1. VPLAV1Decoder 클래스 기본 구조
|
||
|
|
2. Initialize/DecodeFrame 핵심 로직
|
||
|
|
3. VideoDecoderFactory 통합
|
||
|
|
|
||
|
|
### **Phase 2: 최적화**
|
||
|
|
1. 표면 관리 최적화
|
||
|
|
2. 에러 처리 강화
|
||
|
|
3. 성능 모니터링 추가
|
||
|
|
|
||
|
|
### **Phase 3: 고급 기능**
|
||
|
|
1. 적응형 품질 조정 지원
|
||
|
|
2. 멀티스레드 디코딩
|
||
|
|
3. GPU 메모리 최적화
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*작성일: 2025-09-26*
|
||
|
|
*Intel VPL 버전: 2.x API 기준*
|