Implement demo player using Godot
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -387,6 +387,8 @@ output.mp4
|
||||
/vav2/platforms/android/applications/vav2player/vavcore/build/
|
||||
/vav2/platforms/android/applications/vav2player/.gradle/
|
||||
/vav2/platforms/android/applications/vav2player/build/
|
||||
/vav2/platforms/android/vavcore/build/
|
||||
|
||||
# Symbolic links and junctions (platform-specific src directories)
|
||||
# Git will track symlinks as special files, which is the desired behavior
|
||||
.godot
|
||||
|
||||
112
godot-projects/vavcore-demo/README.md
Normal file
112
godot-projects/vavcore-demo/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# VavCore Demo - Godot 4.4.1 AV1 Video Player
|
||||
|
||||
## 📋 프로젝트 개요
|
||||
|
||||
VavCore Extension을 사용하여 Godot 4.4.1에서 AV1 비디오를 재생하는 데모 프로젝트입니다.
|
||||
|
||||
## 🚀 주요 기능
|
||||
|
||||
- ✅ VavCore Extension 통합 테스트
|
||||
- ✅ AV1 비디오 파일 로드 및 재생
|
||||
- ✅ GPU Surface 바인딩 (Zero-Copy Pipeline)
|
||||
- ✅ CPU Fallback 렌더링 지원
|
||||
- ✅ 기본 플레이어 컨트롤 (Play/Pause/Stop)
|
||||
|
||||
## 📁 프로젝트 구조
|
||||
|
||||
```
|
||||
vavcore-demo/
|
||||
├── project.godot # Godot 프로젝트 설정
|
||||
├── scenes/
|
||||
│ └── Main.tscn # 메인 씬
|
||||
├── scripts/
|
||||
│ └── Main.cs # 메인 스크립트 (C#)
|
||||
├── addons/
|
||||
│ └── VavCoreGodot/ # VavCore Extension
|
||||
│ ├── plugin.cfg
|
||||
│ ├── bin/
|
||||
│ │ └── VavCore.dll # VavCore 네이티브 라이브러리
|
||||
│ └── ...
|
||||
├── assets/
|
||||
│ └── videos/
|
||||
│ └── test_video.webm # 테스트 AV1 비디오
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 🔧 설치 및 실행
|
||||
|
||||
### 1. 필요 조건
|
||||
- Godot 4.4.1 (C# 지원)
|
||||
- .NET 8.0 SDK
|
||||
- Windows 10/11 (x64)
|
||||
|
||||
### 2. 프로젝트 열기
|
||||
1. Godot Editor에서 "Import" 클릭
|
||||
2. `project.godot` 파일 선택
|
||||
3. "Import & Edit" 클릭
|
||||
|
||||
### 3. Extension 활성화
|
||||
1. Project → Project Settings
|
||||
2. Plugins 탭
|
||||
3. "VavCore" Extension 활성화
|
||||
|
||||
### 4. 실행
|
||||
1. F5 키 또는 "Play" 버튼 클릭
|
||||
2. "Load Video" 버튼으로 비디오 로드
|
||||
3. "Play" 버튼으로 재생 시작
|
||||
|
||||
## 🎯 테스트 시나리오
|
||||
|
||||
### 기본 테스트
|
||||
1. **Extension 로드 확인**: VavCore Extension이 정상 로드되는지 확인
|
||||
2. **비디오 로드**: 테스트 AV1 파일 로드 성공 여부
|
||||
3. **재생 제어**: Play/Pause/Stop 버튼 동작 확인
|
||||
4. **GPU 렌더링**: Zero-Copy GPU Pipeline 동작 확인
|
||||
5. **CPU Fallback**: GPU 실패 시 CPU 렌더링 동작 확인
|
||||
|
||||
### 고급 테스트
|
||||
1. **다양한 해상도**: 320x240, 1920x1080, 3840x2160 파일 테스트
|
||||
2. **성능 측정**: FPS, 메모리 사용량, GPU 사용률 모니터링
|
||||
3. **안정성 테스트**: 장시간 재생, 반복 로드/언로드
|
||||
4. **에러 처리**: 잘못된 파일, 코덱 오류 등 예외 상황 테스트
|
||||
|
||||
## 📊 기대 결과
|
||||
|
||||
### 성공 시나리오
|
||||
- ✅ VavCore Extension 정상 로드
|
||||
- ✅ AV1 비디오 파일 인식 및 로드
|
||||
- ✅ 부드러운 비디오 재생 (30fps 이상)
|
||||
- ✅ GPU Surface 직접 렌더링 (Zero-Copy)
|
||||
- ✅ 메모리 사용량 최적화
|
||||
|
||||
### 문제 발생 시 체크사항
|
||||
1. **Extension 로드 실패**: plugin.cfg, VavCore.dll 파일 확인
|
||||
2. **비디오 로드 실패**: 파일 경로, AV1 코덱 지원 확인
|
||||
3. **재생 오류**: GPU 드라이버, Godot 렌더링 설정 확인
|
||||
4. **성능 문제**: CPU/GPU 사용률, 메모리 누수 확인
|
||||
|
||||
## 🔍 디버깅 정보
|
||||
|
||||
### 로그 출력 위치
|
||||
- Godot Editor Output 패널
|
||||
- Windows: `%APPDATA%\Godot\app_userdata\VavCoreDemo\logs\`
|
||||
|
||||
### 주요 로그 메시지
|
||||
- `VavCore Demo: Initializing...` - 앱 시작
|
||||
- `Checking for VavCore Extension...` - Extension 로드 확인
|
||||
- `Loading video: [path]` - 비디오 로드 시작
|
||||
- `Video loaded successfully` - 로드 성공
|
||||
- `Playing/Paused/Stopped` - 재생 상태 변경
|
||||
|
||||
## 📝 향후 개선사항
|
||||
|
||||
1. **실제 VavCore Extension 통합** - 현재는 Mock 구현
|
||||
2. **파일 다이얼로그 추가** - 사용자가 비디오 파일 선택 가능
|
||||
3. **진행바 및 시간 표시** - 재생 진행 상황 시각화
|
||||
4. **설정 패널** - 디코더 선택, 품질 설정 등
|
||||
5. **전체화면 모드** - 비디오 전체화면 재생 지원
|
||||
|
||||
---
|
||||
|
||||
*생성일: 2025-09-28*
|
||||
*VavCore Extension Demo for Godot 4.4.1*
|
||||
69
godot-projects/vavcore-demo/TESTING_INSTRUCTIONS.md
Normal file
69
godot-projects/vavcore-demo/TESTING_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# VavCore Extension Testing Instructions
|
||||
|
||||
## Godot Editor에서 테스트하기
|
||||
|
||||
### 1. 프로젝트 열기
|
||||
1. Godot 4.4.1 Editor 실행
|
||||
2. `D:\Project\video-av1\godot-projects\vavcore-demo\project.godot` 열기
|
||||
3. C# 프로젝트 자동 생성 확인
|
||||
|
||||
### 2. VavCore Extension 활성화
|
||||
1. `Project → Project Settings` 메뉴
|
||||
2. `Plugins` 탭 선택
|
||||
3. `VavCore` Extension 활성화 체크
|
||||
4. Editor 재시작 (필요시)
|
||||
|
||||
### 3. 노드 등록 확인
|
||||
1. Scene 탭에서 노드 추가 시도
|
||||
2. "VavCorePlayer" 노드가 Control 하위에 표시되는지 확인
|
||||
3. VavCore 아이콘이 표시되는지 확인
|
||||
|
||||
### 4. 실제 테스트 실행
|
||||
1. `scenes/Main.tscn` 씬 열기
|
||||
2. 프로젝트 실행 (F5 또는 Play 버튼)
|
||||
3. 콘솔 출력 확인:
|
||||
- VavCore Extension 로드 성공 메시지
|
||||
- VavCore library 초기화 메시지
|
||||
- VavCorePlayer 노드 생성 성공 메시지
|
||||
|
||||
### 5. 비디오 로드 테스트
|
||||
1. "Load Video" 버튼 클릭
|
||||
2. `assets/videos/test_video.webm` 로드 시도
|
||||
3. 성공/실패 메시지 확인
|
||||
4. Play/Stop 버튼 활성화 상태 확인
|
||||
|
||||
## 예상 출력
|
||||
|
||||
### 성공 시:
|
||||
```
|
||||
VavCore Demo: Initializing...
|
||||
Checking for VavCore Extension...
|
||||
VavCorePlayer: Initializing...
|
||||
VavCorePlayer: Initializing VavCore library...
|
||||
VavCorePlayer: VavCore initialized successfully!
|
||||
VavCorePlayer: VavCore player created successfully!
|
||||
VavCorePlayer: Video texture setup complete
|
||||
VavCorePlayer node created and added to scene
|
||||
Status: VavCore Extension loaded successfully!
|
||||
```
|
||||
|
||||
### DLL 로드 실패 시:
|
||||
```
|
||||
VavCore Extension not found: [DLL load error]
|
||||
Status: VavCore Extension error: [error message]
|
||||
```
|
||||
|
||||
## 문제 해결
|
||||
|
||||
### VavCore.dll 찾을 수 없음:
|
||||
- `addons/VavCoreGodot/bin/VavCore.dll` 파일 존재 확인
|
||||
- Windows x64 아키텍처용 DLL인지 확인
|
||||
|
||||
### Extension 등록 실패:
|
||||
- `addons/VavCoreGodot/plugin.cfg` 설정 확인
|
||||
- VavCorePlugin.cs 컴파일 오류 확인
|
||||
- Godot Editor 재시작
|
||||
|
||||
### P/Invoke 오류:
|
||||
- VavCore.dll 의존성 라이브러리 확인
|
||||
- 32bit/64bit 아키텍처 매칭 확인
|
||||
BIN
godot-projects/vavcore-demo/VavCore.dll
Normal file
BIN
godot-projects/vavcore-demo/VavCore.dll
Normal file
Binary file not shown.
9
godot-projects/vavcore-demo/VavCoreDemo.csproj
Normal file
9
godot-projects/vavcore-demo/VavCoreDemo.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.4.1">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<AssemblyName>VavCoreDemo</AssemblyName>
|
||||
<RootNamespace>VavCoreDemo</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
uid://beov161gbolkv
|
||||
@@ -0,0 +1,28 @@
|
||||
using Godot;
|
||||
|
||||
[Tool]
|
||||
public partial class VavCorePlugin : EditorPlugin
|
||||
{
|
||||
public override void _EnterTree()
|
||||
{
|
||||
GD.Print("VavCore Extension: Plugin loaded!");
|
||||
|
||||
// VavCorePlayer 커스텀 노드 등록
|
||||
AddCustomType(
|
||||
"VavCorePlayer", // 노드 타입 이름
|
||||
"Control", // 베이스 클래스
|
||||
GD.Load<Script>("res://addons/VavCoreGodot/VavCorePlayer.cs"), // 스크립트
|
||||
GD.Load<Texture2D>("res://addons/VavCoreGodot/icon.svg") // 아이콘
|
||||
);
|
||||
|
||||
GD.Print("VavCore Extension: VavCorePlayer node registered!");
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
GD.Print("VavCore Extension: Plugin unloaded!");
|
||||
|
||||
// 커스텀 노드 등록 해제
|
||||
RemoveCustomType("VavCorePlayer");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://x8mlaha7ofwj
|
||||
4
godot-projects/vavcore-demo/addons/VavCoreGodot/icon.svg
Normal file
4
godot-projects/vavcore-demo/addons/VavCoreGodot/icon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="1" width="14" height="14" rx="2" fill="#4CAF50" stroke="#2E7D32" stroke-width="1"/>
|
||||
<polygon points="5,4 12,8 5,12" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 222 B |
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cm0bj0sgbcsp7"
|
||||
path="res://.godot/imported/icon.svg-70c60c1ea6525fd4731c29591ecf6d86.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/VavCoreGodot/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-70c60c1ea6525fd4731c29591ecf6d86.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="VavCore"
|
||||
description="VavCore AV1 Video Player Extension for Godot 4.4.1"
|
||||
author="VavCore Team"
|
||||
version="1.0.0"
|
||||
script=""
|
||||
16
godot-projects/vavcore-demo/icon.svg
Normal file
16
godot-projects/vavcore-demo/icon.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/>
|
||||
<g transform="scale(.101) translate(122 122)">
|
||||
<g fill="#fff">
|
||||
<path d="M105 673v33q407 354 814 0v-33z"/>
|
||||
<path d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/>
|
||||
<path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"/>
|
||||
<circle cx="725" cy="526" r="90"/>
|
||||
<circle cx="299" cy="526" r="90"/>
|
||||
</g>
|
||||
<g fill="#414042" stroke="#212532" stroke-width="10">
|
||||
<circle cx="307" cy="532" r="35"/>
|
||||
<circle cx="717" cy="532" r="35"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
37
godot-projects/vavcore-demo/icon.svg.import
Normal file
37
godot-projects/vavcore-demo/icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1oqb0bfajif2"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
26
godot-projects/vavcore-demo/project.godot
Normal file
26
godot-projects/vavcore-demo/project.godot
Normal file
@@ -0,0 +1,26 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="VavCore Demo"
|
||||
config/description="VavCore AV1 Video Player Extension Demo for Godot 4.4.1"
|
||||
config/version="1.0.0"
|
||||
run/main_scene="res://scenes/Main.tscn"
|
||||
config/features=PackedStringArray("4.4", "C#", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="VavCoreDemo"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/VavCoreGodot/plugin.cfg")
|
||||
69
godot-projects/vavcore-demo/scenes/Main.tscn
Normal file
69
godot-projects/vavcore-demo/scenes/Main.tscn
Normal file
@@ -0,0 +1,69 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b4l05ujvowftf"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b0lkfxgteunwv" path="res://scripts/Main.cs" id="1_2xvks"]
|
||||
|
||||
[node name="Main" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_2xvks")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Title" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "VavCore AV1 Video Player Demo"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VideoContainer" type="Panel" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VavCorePlayer" type="Control" parent="VBoxContainer/VideoContainer"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ControlPanel" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LoadButton" type="Button" parent="VBoxContainer/ControlPanel"]
|
||||
layout_mode = 2
|
||||
text = "Load Video"
|
||||
|
||||
[node name="PlayButton" type="Button" parent="VBoxContainer/ControlPanel"]
|
||||
layout_mode = 2
|
||||
text = "Play"
|
||||
|
||||
[node name="PauseButton" type="Button" parent="VBoxContainer/ControlPanel"]
|
||||
layout_mode = 2
|
||||
text = "Pause"
|
||||
|
||||
[node name="StopButton" type="Button" parent="VBoxContainer/ControlPanel"]
|
||||
layout_mode = 2
|
||||
text = "Stop"
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Ready"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/ControlPanel/LoadButton" to="." method="OnLoadButtonPressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/ControlPanel/PlayButton" to="." method="OnPlayButtonPressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/ControlPanel/PauseButton" to="." method="OnPauseButtonPressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/ControlPanel/StopButton" to="." method="OnStopButtonPressed"]
|
||||
235
godot-projects/vavcore-demo/scripts/Main.cs
Normal file
235
godot-projects/vavcore-demo/scripts/Main.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using Godot;
|
||||
|
||||
public partial class Main : Control
|
||||
{
|
||||
// UI 요소들
|
||||
private Label _statusLabel;
|
||||
private Control _vavCorePlayer;
|
||||
private Button _loadButton;
|
||||
private Button _playButton;
|
||||
private Button _pauseButton;
|
||||
private Button _stopButton;
|
||||
|
||||
// VavCore Player 인스턴스
|
||||
private VavCorePlayer _vavCorePlayerNode;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
GD.Print("VavCore Demo: Initializing...");
|
||||
|
||||
// UI 요소 참조 가져오기
|
||||
_statusLabel = GetNode<Label>("VBoxContainer/StatusLabel");
|
||||
_vavCorePlayer = GetNode<Control>("VBoxContainer/VideoContainer/VavCorePlayer");
|
||||
_loadButton = GetNode<Button>("VBoxContainer/ControlPanel/LoadButton");
|
||||
_playButton = GetNode<Button>("VBoxContainer/ControlPanel/PlayButton");
|
||||
_pauseButton = GetNode<Button>("VBoxContainer/ControlPanel/PauseButton");
|
||||
_stopButton = GetNode<Button>("VBoxContainer/ControlPanel/StopButton");
|
||||
|
||||
// 초기 상태 설정
|
||||
_playButton.Disabled = true;
|
||||
_pauseButton.Disabled = true;
|
||||
_stopButton.Disabled = true;
|
||||
|
||||
UpdateStatus("Ready - VavCore Extension Demo");
|
||||
|
||||
// VavCore Extension 로드 확인
|
||||
CheckVavCoreExtension();
|
||||
}
|
||||
|
||||
private void CheckVavCoreExtension()
|
||||
{
|
||||
// VavCore Extension이 로드되었는지 확인
|
||||
// 실제 VavCore 노드를 생성해보기
|
||||
try
|
||||
{
|
||||
GD.Print("=== Checking for VavCore Extension ===");
|
||||
|
||||
// VideoContainer Panel 배경을 투명하게 설정
|
||||
var videoContainer = GetNode<Panel>("VBoxContainer/VideoContainer");
|
||||
|
||||
// Panel의 기본 StyleBox를 제거하여 투명하게 만들기
|
||||
var emptyStyleBox = new StyleBoxEmpty();
|
||||
videoContainer.AddThemeStyleboxOverride("panel", emptyStyleBox);
|
||||
|
||||
GD.Print("VideoContainer panel made transparent");
|
||||
|
||||
// VavCorePlayer 노드 생성 및 추가
|
||||
GD.Print("Creating VavCorePlayer instance...");
|
||||
_vavCorePlayerNode = new VavCorePlayer();
|
||||
GD.Print("VavCorePlayer instance created successfully");
|
||||
|
||||
_vavCorePlayerNode.Name = "VavCorePlayerNode";
|
||||
GD.Print("Adding VavCorePlayer to scene...");
|
||||
_vavCorePlayer.AddChild(_vavCorePlayerNode);
|
||||
GD.Print("VavCorePlayer added to scene successfully");
|
||||
|
||||
UpdateStatus("VavCore Extension loaded successfully!");
|
||||
GD.Print("=== VavCore Extension initialization complete ===");
|
||||
|
||||
// VavCore Extension 초기화 완료
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
GD.PrintErr($"=== VavCore Extension Error ===");
|
||||
GD.PrintErr($"Exception: {ex.Message}");
|
||||
GD.PrintErr($"Stack trace: {ex.StackTrace}");
|
||||
UpdateStatus($"VavCore Extension error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatus(string message)
|
||||
{
|
||||
_statusLabel.Text = message;
|
||||
GD.Print($"Status: {message}");
|
||||
}
|
||||
|
||||
// 버튼 이벤트 핸들러들
|
||||
public void OnLoadButtonPressed()
|
||||
{
|
||||
GD.Print("=== Load button pressed ===");
|
||||
UpdateStatus("Load button clicked - checking video file...");
|
||||
|
||||
// 파일 다이얼로그를 사용하여 비디오 파일 선택
|
||||
// 또는 기본 테스트 파일 로드
|
||||
string videoPath = "res://assets/videos/test_video.webm";
|
||||
GD.Print($"Checking video path: {videoPath}");
|
||||
|
||||
// 실제 파일 경로로 변환해서 확인
|
||||
string realPath = ProjectSettings.GlobalizePath(videoPath);
|
||||
GD.Print($"Real file path: {realPath}");
|
||||
|
||||
if (FileAccess.FileExists(videoPath))
|
||||
{
|
||||
GD.Print("Video file exists - loading...");
|
||||
LoadVideo(videoPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"Test video file not found: {videoPath}");
|
||||
GD.PrintErr($"Real path checked: {realPath}");
|
||||
UpdateStatus("Test video file not found: " + videoPath);
|
||||
|
||||
// 디렉토리 내용 확인
|
||||
var dir = DirAccess.Open("res://assets/videos/");
|
||||
if (dir != null)
|
||||
{
|
||||
GD.Print("Files in assets/videos directory:");
|
||||
dir.ListDirBegin();
|
||||
var fileName = dir.GetNext();
|
||||
while (fileName != "")
|
||||
{
|
||||
GD.Print($" - {fileName}");
|
||||
fileName = dir.GetNext();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("Could not open assets/videos directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadVideo(string videoPath)
|
||||
{
|
||||
GD.Print($"=== LoadVideo called with: {videoPath} ===");
|
||||
|
||||
if (_vavCorePlayerNode == null)
|
||||
{
|
||||
GD.PrintErr("VavCore Extension not available - _vavCorePlayerNode is null");
|
||||
UpdateStatus("VavCore Extension not available");
|
||||
return;
|
||||
}
|
||||
|
||||
GD.Print("VavCorePlayer node is available, proceeding with video load...");
|
||||
|
||||
try
|
||||
{
|
||||
UpdateStatus($"Loading video: {videoPath}");
|
||||
|
||||
// 실제 VavCore Extension을 사용하여 비디오 로드
|
||||
GD.Print("Calling _vavCorePlayerNode.LoadVideo()...");
|
||||
bool success = _vavCorePlayerNode.LoadVideo(videoPath);
|
||||
GD.Print($"LoadVideo returned: {success}");
|
||||
|
||||
if (success)
|
||||
{
|
||||
// 버튼 상태 업데이트
|
||||
_playButton.Disabled = false;
|
||||
_stopButton.Disabled = false;
|
||||
|
||||
UpdateStatus("Video loaded successfully!");
|
||||
GD.Print("Video loading completed successfully!");
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus("Failed to load video - VavCore returned false");
|
||||
GD.PrintErr("Failed to load video - VavCore returned false");
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
GD.PrintErr($"Exception during video loading: {ex.Message}");
|
||||
GD.PrintErr($"Stack trace: {ex.StackTrace}");
|
||||
UpdateStatus($"Failed to load video: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPlayButtonPressed()
|
||||
{
|
||||
GD.Print("Play button pressed");
|
||||
|
||||
if (_vavCorePlayerNode != null && _vavCorePlayerNode.IsVideoLoaded())
|
||||
{
|
||||
if (_vavCorePlayerNode.IsPlaying())
|
||||
{
|
||||
UpdateStatus("Already playing");
|
||||
}
|
||||
else
|
||||
{
|
||||
_vavCorePlayerNode.StartPlayback();
|
||||
UpdateStatus("Playing video");
|
||||
_playButton.Disabled = true;
|
||||
_pauseButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus("No video loaded - please load a video first");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPauseButtonPressed()
|
||||
{
|
||||
GD.Print("Pause button pressed");
|
||||
|
||||
if (_vavCorePlayerNode != null && _vavCorePlayerNode.IsVideoLoaded())
|
||||
{
|
||||
_vavCorePlayerNode.PausePlayback();
|
||||
UpdateStatus("Paused");
|
||||
_playButton.Disabled = false;
|
||||
_pauseButton.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus("No video to pause");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnStopButtonPressed()
|
||||
{
|
||||
GD.Print("Stop button pressed");
|
||||
|
||||
if (_vavCorePlayerNode != null && _vavCorePlayerNode.IsVideoLoaded())
|
||||
{
|
||||
_vavCorePlayerNode.StopPlayback();
|
||||
UpdateStatus("Stopped");
|
||||
_playButton.Disabled = false;
|
||||
_pauseButton.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus("No video to stop");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
godot-projects/vavcore-demo/scripts/Main.cs.uid
Normal file
1
godot-projects/vavcore-demo/scripts/Main.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b0lkfxgteunwv
|
||||
861
godot-projects/vavcore-demo/scripts/VavCorePlayer.cs
Normal file
861
godot-projects/vavcore-demo/scripts/VavCorePlayer.cs
Normal file
@@ -0,0 +1,861 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// VavCore 데이터 구조체들
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct VavCoreVideoFrame
|
||||
{
|
||||
// Legacy CPU fields (for backward compatibility)
|
||||
public IntPtr y_plane; // uint8_t*
|
||||
public IntPtr u_plane; // uint8_t*
|
||||
public IntPtr v_plane; // uint8_t*
|
||||
|
||||
public int y_stride; // Y plane stride
|
||||
public int u_stride; // U plane stride
|
||||
public int v_stride; // V plane stride
|
||||
|
||||
// Frame metadata
|
||||
public int width; // Frame width
|
||||
public int height; // Frame height
|
||||
|
||||
public ulong timestamp_us; // Timestamp in microseconds
|
||||
public ulong frame_number; // Frame sequence number
|
||||
|
||||
// Surface type and data (we'll use CPU mode for now)
|
||||
public int surface_type; // VavCoreSurfaceType (0 = CPU)
|
||||
|
||||
// Union data - we'll only use the first 64 bytes for CPU data
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public ulong[] surface_data; // Union as array of ulongs
|
||||
}
|
||||
|
||||
public partial class VavCorePlayer : Control
|
||||
{
|
||||
// VavCore DLL Import - Use actual file system path
|
||||
private const string VavCoreDll = "VavCore.dll";
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int vavcore_initialize();
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr vavcore_create_player();
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int vavcore_open_file(IntPtr player, string filePath);
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool vavcore_is_open(IntPtr player);
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void vavcore_destroy_player(IntPtr player);
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void vavcore_cleanup();
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int vavcore_decode_next_frame(IntPtr player, out VavCoreVideoFrame frame);
|
||||
|
||||
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void vavcore_free_frame(ref VavCoreVideoFrame frame);
|
||||
|
||||
// VavCore Player 인스턴스
|
||||
private IntPtr _vavCorePlayer = IntPtr.Zero;
|
||||
private string _currentVideoPath = string.Empty;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
// Godot 노드들
|
||||
private TextureRect _videoTexture;
|
||||
private ShaderMaterial _yuvShaderMaterial;
|
||||
|
||||
// GPU 텍스처들
|
||||
private ImageTexture _yTexture;
|
||||
private ImageTexture _uTexture;
|
||||
private ImageTexture _vTexture;
|
||||
|
||||
// 텍스처 캐싱 최적화
|
||||
private int _cachedFrameWidth = 0;
|
||||
private int _cachedFrameHeight = 0;
|
||||
private bool _texturesInitialized = false;
|
||||
|
||||
// 재생 상태 관리
|
||||
private bool _isPlaying = false;
|
||||
private bool _isPaused = false;
|
||||
private Timer _playbackTimer;
|
||||
private double _targetFrameRate = 30.0; // 기본 30fps
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
GD.Print("VavCorePlayer: Initializing...");
|
||||
|
||||
// VavCore 라이브러리 초기화
|
||||
InitializeVavCore();
|
||||
|
||||
// 비디오 출력용 TextureRect 생성
|
||||
SetupVideoTexture();
|
||||
}
|
||||
|
||||
private void InitializeVavCore()
|
||||
{
|
||||
try
|
||||
{
|
||||
GD.Print("VavCorePlayer: Initializing VavCore library...");
|
||||
|
||||
// DLL 경로 확인
|
||||
string dllPath = System.IO.Path.Combine(System.Environment.CurrentDirectory, "VavCore.dll");
|
||||
GD.Print($"VavCorePlayer: Looking for DLL at: {dllPath}");
|
||||
GD.Print($"VavCorePlayer: DLL exists: {System.IO.File.Exists(dllPath)}");
|
||||
|
||||
GD.Print("VavCorePlayer: Calling vavcore_initialize()...");
|
||||
int initResult = vavcore_initialize();
|
||||
GD.Print($"VavCorePlayer: vavcore_initialize() returned: {initResult}");
|
||||
|
||||
if (initResult == 0) // VAVCORE_SUCCESS = 0
|
||||
{
|
||||
_isInitialized = true;
|
||||
GD.Print("VavCorePlayer: VavCore initialized successfully!");
|
||||
|
||||
// VavCore 플레이어 인스턴스 생성
|
||||
GD.Print("VavCorePlayer: Creating VavCore player instance...");
|
||||
_vavCorePlayer = vavcore_create_player();
|
||||
GD.Print($"VavCorePlayer: vavcore_create_player() returned: {_vavCorePlayer}");
|
||||
|
||||
if (_vavCorePlayer != IntPtr.Zero)
|
||||
{
|
||||
GD.Print("VavCorePlayer: VavCore player created successfully!");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to create VavCore player!");
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to initialize VavCore!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Exception during initialization: {ex.Message}");
|
||||
GD.PrintErr($"VavCorePlayer: Exception type: {ex.GetType().Name}");
|
||||
GD.PrintErr($"VavCorePlayer: Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupVideoTexture()
|
||||
{
|
||||
// 비디오 출력용 TextureRect 노드 생성
|
||||
_videoTexture = new TextureRect();
|
||||
_videoTexture.Name = "VideoTexture";
|
||||
|
||||
// Fill the entire parent control
|
||||
_videoTexture.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
|
||||
_videoTexture.ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional;
|
||||
_videoTexture.StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered;
|
||||
|
||||
// 가시성 강제 설정
|
||||
_videoTexture.Visible = true;
|
||||
_videoTexture.Modulate = Colors.White; // 완전 불투명
|
||||
_videoTexture.ZIndex = 100; // 최상위에 표시
|
||||
|
||||
// 투명 배경으로 설정
|
||||
_videoTexture.SelfModulate = Colors.White;
|
||||
|
||||
// GPU 셰이더 설정
|
||||
SetupGPUShader();
|
||||
|
||||
AddChild(_videoTexture);
|
||||
|
||||
// 재생 타이머 설정
|
||||
_playbackTimer = new Timer();
|
||||
_playbackTimer.Name = "PlaybackTimer";
|
||||
_playbackTimer.WaitTime = 1.0 / _targetFrameRate; // 30fps = ~0.033초
|
||||
_playbackTimer.Timeout += OnPlaybackTimerTimeout;
|
||||
AddChild(_playbackTimer);
|
||||
|
||||
// 디버그 정보 출력
|
||||
GD.Print($"VavCorePlayer: Video texture setup complete");
|
||||
GD.Print($"VavCorePlayer: TextureRect position: {_videoTexture.Position}");
|
||||
GD.Print($"VavCorePlayer: TextureRect size: {_videoTexture.Size}");
|
||||
GD.Print($"VavCorePlayer: TextureRect visible: {_videoTexture.Visible}");
|
||||
GD.Print($"VavCorePlayer: GPU shader setup complete");
|
||||
}
|
||||
|
||||
public bool LoadVideo(string videoPath)
|
||||
{
|
||||
if (!_isInitialized || _vavCorePlayer == IntPtr.Zero)
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Not initialized!");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
GD.Print($"VavCorePlayer: Loading video: {videoPath}");
|
||||
|
||||
// Godot 리소스 경로를 실제 파일 경로로 변환
|
||||
string realPath = ProjectSettings.GlobalizePath(videoPath);
|
||||
GD.Print($"VavCorePlayer: Real path: {realPath}");
|
||||
|
||||
int result = vavcore_open_file(_vavCorePlayer, realPath);
|
||||
if (result == 0) // VAVCORE_SUCCESS = 0
|
||||
{
|
||||
_currentVideoPath = realPath; // 성공시 경로 저장
|
||||
|
||||
// 텍스처 캐시 초기화
|
||||
_texturesInitialized = false;
|
||||
_cachedFrameWidth = 0;
|
||||
_cachedFrameHeight = 0;
|
||||
|
||||
GD.Print("VavCorePlayer: Video loaded successfully!");
|
||||
|
||||
// 비디오가 로드되었는지 확인
|
||||
bool isOpen = vavcore_is_open(_vavCorePlayer);
|
||||
GD.Print($"VavCorePlayer: Video is open: {isOpen}");
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to load video!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Exception during video loading: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsVideoLoaded()
|
||||
{
|
||||
if (!_isInitialized || _vavCorePlayer == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
return vavcore_is_open(_vavCorePlayer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Exception checking video status: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
GD.Print("VavCorePlayer: Cleaning up...");
|
||||
|
||||
// VavCore 리소스 정리
|
||||
if (_vavCorePlayer != IntPtr.Zero)
|
||||
{
|
||||
vavcore_destroy_player(_vavCorePlayer);
|
||||
_vavCorePlayer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_isInitialized)
|
||||
{
|
||||
vavcore_cleanup();
|
||||
_isInitialized = false;
|
||||
}
|
||||
|
||||
GD.Print("VavCorePlayer: Cleanup complete");
|
||||
}
|
||||
|
||||
// 재생 제어 메서드들
|
||||
public void StartPlayback()
|
||||
{
|
||||
if (!IsVideoLoaded())
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Cannot start playback - no video loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
_isPlaying = true;
|
||||
_isPaused = false;
|
||||
_playbackTimer.Start();
|
||||
GD.Print("VavCorePlayer: Playback started");
|
||||
}
|
||||
|
||||
public void PausePlayback()
|
||||
{
|
||||
_isPaused = true;
|
||||
_playbackTimer.Stop();
|
||||
GD.Print("VavCorePlayer: Playback paused");
|
||||
}
|
||||
|
||||
public void StopPlayback()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_isPaused = false;
|
||||
_playbackTimer.Stop();
|
||||
|
||||
// 처음부터 다시 재생하기 위해 비디오를 다시 로드
|
||||
if (_vavCorePlayer != IntPtr.Zero && !string.IsNullOrEmpty(_currentVideoPath))
|
||||
{
|
||||
GD.Print("VavCorePlayer: Reloading video to reset position");
|
||||
|
||||
// 현재 파일 경로 저장
|
||||
string currentPath = _currentVideoPath;
|
||||
|
||||
// 비디오 다시 로드 (내부적으로 seek to beginning과 동일한 효과)
|
||||
int result = vavcore_open_file(_vavCorePlayer, currentPath);
|
||||
if (result == 0)
|
||||
{
|
||||
GD.Print("VavCorePlayer: Video reset to beginning successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Failed to reset video position: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print("VavCorePlayer: Playback stopped");
|
||||
}
|
||||
|
||||
public bool IsPlaying()
|
||||
{
|
||||
return _isPlaying && !_isPaused;
|
||||
}
|
||||
|
||||
// 타이머 콜백 - 매 프레임마다 호출됨
|
||||
private void OnPlaybackTimerTimeout()
|
||||
{
|
||||
if (!_isPlaying || _isPaused || !IsVideoLoaded())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
DecodeAndDisplayNextFrame();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Error during frame decode: {ex.Message}");
|
||||
StopPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
// 다음 프레임 디코딩 및 표시
|
||||
private void DecodeAndDisplayNextFrame()
|
||||
{
|
||||
VavCoreVideoFrame frame = new VavCoreVideoFrame();
|
||||
|
||||
// 구조체 초기화
|
||||
frame.surface_data = new ulong[16];
|
||||
|
||||
int result = vavcore_decode_next_frame(_vavCorePlayer, out frame);
|
||||
|
||||
GD.Print($"VavCorePlayer: Decode result: {result}");
|
||||
|
||||
if (result == 0) // VAVCORE_SUCCESS
|
||||
{
|
||||
GD.Print($"VavCorePlayer: Decoded frame {frame.frame_number} ({frame.width}x{frame.height})");
|
||||
GD.Print($"VavCorePlayer: Y-plane: {frame.y_plane}, U-plane: {frame.u_plane}, V-plane: {frame.v_plane}");
|
||||
GD.Print($"VavCorePlayer: Y-stride: {frame.y_stride}, U-stride: {frame.u_stride}, V-stride: {frame.v_stride}");
|
||||
|
||||
// YUV 데이터가 유효한지 확인
|
||||
if (frame.y_plane != IntPtr.Zero && frame.width > 0 && frame.height > 0)
|
||||
{
|
||||
// GPU에서 YUV 데이터를 직접 처리하여 표시
|
||||
DisplayFrameGPU(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Invalid frame data received");
|
||||
}
|
||||
|
||||
// 프레임 메모리 해제
|
||||
vavcore_free_frame(ref frame);
|
||||
}
|
||||
else if (result == 1) // VAVCORE_END_OF_STREAM
|
||||
{
|
||||
GD.Print("VavCorePlayer: End of video reached");
|
||||
StopPlayback();
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Frame decode failed with error: {result}");
|
||||
StopPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
// 폴백 이미지 생성 (GPU 셰이더 사용 불가시)
|
||||
private Image CreateFallbackImage(VavCoreVideoFrame frame)
|
||||
{
|
||||
// GPU 셰이더가 사용 가능한 경우 null 반환 (셰이더가 처리)
|
||||
if (_yuvShaderMaterial != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// GPU 셰이더 사용 불가시 CPU 방식으로 폴백
|
||||
GD.PrintErr("VavCorePlayer: GPU shader not available, using CPU fallback");
|
||||
|
||||
// 간단한 그레이스케일 변환 (성능 최적화)
|
||||
try
|
||||
{
|
||||
var image = Image.CreateEmpty(frame.width, frame.height, false, Image.Format.Rgb8);
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* yPtr = (byte*)frame.y_plane.ToPointer();
|
||||
|
||||
for (int y = 0; y < frame.height; y++)
|
||||
{
|
||||
for (int x = 0; x < frame.width; x++)
|
||||
{
|
||||
int yIndex = y * frame.y_stride + x;
|
||||
byte yVal = yPtr[yIndex];
|
||||
|
||||
// Y 값만 사용하여 그레이스케일로 표시 (고속 처리)
|
||||
float gray = yVal / 255.0f;
|
||||
var color = new Color(gray, gray, gray, 1.0f);
|
||||
image.SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Fallback image creation error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// GPU 셰이더 설정
|
||||
private void SetupGPUShader()
|
||||
{
|
||||
try
|
||||
{
|
||||
// YUV to RGB 셰이더 로드
|
||||
var shader = GD.Load<Shader>("res://shaders/yuv_to_rgb.gdshader");
|
||||
if (shader == null)
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to load YUV shader");
|
||||
return;
|
||||
}
|
||||
|
||||
// 셰이더 머티리얼 생성
|
||||
_yuvShaderMaterial = new ShaderMaterial();
|
||||
_yuvShaderMaterial.Shader = shader;
|
||||
|
||||
GD.Print("VavCorePlayer: GPU shader loaded successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Error setting up GPU shader: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// GPU에서 YUV 프레임을 직접 처리하여 표시
|
||||
private void DisplayFrameGPU(VavCoreVideoFrame frame)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TextureRect 크기가 0이면 다시 설정
|
||||
if (_videoTexture.Size.X <= 0 || _videoTexture.Size.Y <= 0)
|
||||
{
|
||||
GD.Print("VavCorePlayer: TextureRect size is 0, forcing resize...");
|
||||
|
||||
// 부모 컨테이너 크기 확인
|
||||
var parentSize = GetParent<Control>().Size;
|
||||
GD.Print($"VavCorePlayer: Parent size: {parentSize}");
|
||||
|
||||
// 강제로 크기 설정
|
||||
_videoTexture.Size = parentSize;
|
||||
_videoTexture.Position = Vector2.Zero;
|
||||
|
||||
GD.Print($"VavCorePlayer: TextureRect resized to: {_videoTexture.Size}");
|
||||
}
|
||||
|
||||
// YUV 데이터를 GPU 텍스처로 변환
|
||||
bool success = CreateYUVTextures(frame);
|
||||
if (success && _yuvShaderMaterial != null)
|
||||
{
|
||||
// 셰이더에 YUV 텍스처 할당
|
||||
_yuvShaderMaterial.SetShaderParameter("y_texture", _yTexture);
|
||||
_yuvShaderMaterial.SetShaderParameter("u_texture", _uTexture);
|
||||
_yuvShaderMaterial.SetShaderParameter("v_texture", _vTexture);
|
||||
|
||||
// 메쉬 기반 렌더링을 위한 더미 텍스처 생성
|
||||
var dummyImage = Image.CreateEmpty(frame.width, frame.height, false, Image.Format.Rgb8);
|
||||
var dummyTexture = ImageTexture.CreateFromImage(dummyImage);
|
||||
|
||||
// TextureRect에 셰이더 머티리얼 적용
|
||||
_videoTexture.Texture = dummyTexture;
|
||||
_videoTexture.Material = _yuvShaderMaterial;
|
||||
|
||||
GD.Print($"VavCorePlayer: GPU YUV frame displayed successfully ({frame.width}x{frame.height})");
|
||||
GD.Print($"VavCorePlayer: TextureRect final size: {_videoTexture.Size}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// GPU 처리 실패시 빨간색 에러 표시
|
||||
var errorImage = Image.CreateEmpty(frame.width, frame.height, false, Image.Format.Rgb8);
|
||||
for (int y = 0; y < frame.height; y++)
|
||||
{
|
||||
for (int x = 0; x < frame.width; x++)
|
||||
{
|
||||
errorImage.SetPixel(x, y, Colors.Red);
|
||||
}
|
||||
}
|
||||
var errorTexture = ImageTexture.CreateFromImage(errorImage);
|
||||
_videoTexture.Texture = errorTexture;
|
||||
_videoTexture.Material = null; // 셰이더 비활성화
|
||||
GD.PrintErr($"VavCorePlayer: GPU YUV processing failed, showing error image");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Error displaying GPU frame: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// YUV 데이터로 GPU 텍스처 생성
|
||||
private bool CreateYUVTextures(VavCoreVideoFrame frame)
|
||||
{
|
||||
if (frame.y_plane == IntPtr.Zero || frame.width <= 0 || frame.height <= 0)
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Invalid frame data for GPU texture creation");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frame.u_plane == IntPtr.Zero || frame.v_plane == IntPtr.Zero)
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Missing UV plane data for GPU texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
GD.Print($"VavCorePlayer: Creating GPU YUV textures - Y stride: {frame.y_stride}, U stride: {frame.u_stride}, V stride: {frame.v_stride}");
|
||||
|
||||
// YUV420P 메모리 연속성 분석
|
||||
long yAddr = (long)frame.y_plane;
|
||||
long uAddr = (long)frame.u_plane;
|
||||
long vAddr = (long)frame.v_plane;
|
||||
|
||||
long ySize = frame.width * frame.height;
|
||||
long uSize = (frame.width / 2) * (frame.height / 2);
|
||||
long vSize = (frame.width / 2) * (frame.height / 2);
|
||||
|
||||
GD.Print($"VavCorePlayer: Memory layout analysis:");
|
||||
GD.Print($" Y plane: 0x{yAddr:X} (size: {ySize} bytes)");
|
||||
GD.Print($" U plane: 0x{uAddr:X} (size: {uSize} bytes)");
|
||||
GD.Print($" V plane: 0x{vAddr:X} (size: {vSize} bytes)");
|
||||
GD.Print($" Y->U gap: {uAddr - (yAddr + ySize)} bytes");
|
||||
GD.Print($" U->V gap: {vAddr - (uAddr + uSize)} bytes");
|
||||
|
||||
// 연속 메모리 공간인지 확인
|
||||
bool isContiguous = (uAddr == yAddr + ySize) && (vAddr == uAddr + uSize);
|
||||
bool hasOptimalStrides = frame.y_stride == frame.width && frame.u_stride == (frame.width / 2) && frame.v_stride == (frame.width / 2);
|
||||
GD.Print($"VavCorePlayer: YUV planes contiguous: {isContiguous}");
|
||||
GD.Print($"VavCorePlayer: Optimal strides: {hasOptimalStrides}");
|
||||
|
||||
if (isContiguous && hasOptimalStrides)
|
||||
{
|
||||
GD.Print("VavCorePlayer: Attempting single-block YUV420P copy!");
|
||||
return CreateSingleBlockYUVTexture(frame);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 텍스처 캐싱 최적화: 해상도가 바뀌었거나 첫 번째 프레임인 경우에만 재생성
|
||||
bool needsResize = !_texturesInitialized ||
|
||||
_cachedFrameWidth != frame.width ||
|
||||
_cachedFrameHeight != frame.height;
|
||||
|
||||
if (needsResize)
|
||||
{
|
||||
GD.Print($"VavCorePlayer: CREATING new textures - {frame.width}x{frame.height}");
|
||||
_cachedFrameWidth = frame.width;
|
||||
_cachedFrameHeight = frame.height;
|
||||
_texturesInitialized = true;
|
||||
|
||||
// Y 평면 텍스처 생성
|
||||
var yImage = CreatePlaneImageBlockCopy(frame.y_plane, frame.width, frame.height, frame.y_stride);
|
||||
if (yImage != null)
|
||||
{
|
||||
_yTexture = ImageTexture.CreateFromImage(yImage);
|
||||
GD.Print($"VavCorePlayer: Y texture CREATED - {yImage.GetWidth()}x{yImage.GetHeight()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to create Y plane image");
|
||||
return false;
|
||||
}
|
||||
|
||||
// U 평면 텍스처 생성
|
||||
int uvWidth = frame.width / 2;
|
||||
int uvHeight = frame.height / 2;
|
||||
var uImage = CreatePlaneImageBlockCopy(frame.u_plane, uvWidth, uvHeight, frame.u_stride);
|
||||
if (uImage != null)
|
||||
{
|
||||
_uTexture = ImageTexture.CreateFromImage(uImage);
|
||||
GD.Print($"VavCorePlayer: U texture CREATED - {uImage.GetWidth()}x{uImage.GetHeight()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to create U plane image");
|
||||
return false;
|
||||
}
|
||||
|
||||
// V 평면 텍스처 생성
|
||||
var vImage = CreatePlaneImageBlockCopy(frame.v_plane, uvWidth, uvHeight, frame.v_stride);
|
||||
if (vImage != null)
|
||||
{
|
||||
_vTexture = ImageTexture.CreateFromImage(vImage);
|
||||
GD.Print($"VavCorePlayer: V texture CREATED - {vImage.GetWidth()}x{vImage.GetHeight()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to create V plane image");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 텍스처 업데이트만 수행 (훨씬 빠름!)
|
||||
var yImage = CreatePlaneImageBlockCopy(frame.y_plane, frame.width, frame.height, frame.y_stride);
|
||||
var uImage = CreatePlaneImageBlockCopy(frame.u_plane, frame.width / 2, frame.height / 2, frame.u_stride);
|
||||
var vImage = CreatePlaneImageBlockCopy(frame.v_plane, frame.width / 2, frame.height / 2, frame.v_stride);
|
||||
|
||||
if (yImage != null && uImage != null && vImage != null)
|
||||
{
|
||||
_yTexture.Update(yImage);
|
||||
_uTexture.Update(uImage);
|
||||
_vTexture.Update(vImage);
|
||||
// GD.Print("VavCorePlayer: Textures UPDATED efficiently");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr("VavCorePlayer: Failed to update textures");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"VavCorePlayer: GPU YUV textures created successfully (3-block method)");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: GPU texture creation error: {ex.Message}");
|
||||
GD.PrintErr($"VavCorePlayer: Stack trace: {ex.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 단일 평면 데이터로 이미지 생성 (최적화된 버전)
|
||||
private Image CreatePlaneImage(IntPtr planeData, int width, int height, int stride)
|
||||
{
|
||||
try
|
||||
{
|
||||
// R8 포맷 사용 (단일 체널, 8-bit)
|
||||
var image = Image.CreateEmpty(width, height, false, Image.Format.R8);
|
||||
|
||||
GD.Print($"VavCorePlayer: Creating plane image - Size: {width}x{height}, Stride: {stride}, Format: R8");
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* srcPtr = (byte*)planeData.ToPointer();
|
||||
|
||||
// 직접 픽셀 데이터 복사 (고속 처리)
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int srcIndex = y * stride + x;
|
||||
byte value = srcPtr[srcIndex];
|
||||
|
||||
// R8 포맷: 빨간 채널에만 값 설정
|
||||
var color = new Color(value / 255.0f, 0.0f, 0.0f, 1.0f);
|
||||
image.SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"VavCorePlayer: Plane image created successfully - Format: {image.GetFormat()}");
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Error creating plane image: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 블록 메모리 복사를 위한 최고속 평면 이미지 생성
|
||||
private Image CreatePlaneImageBlockCopy(IntPtr planeData, int width, int height, int stride)
|
||||
{
|
||||
try
|
||||
{
|
||||
GD.Print($"VavCorePlayer: Block copy - Width: {width}, Height: {height}, Stride: {stride}");
|
||||
|
||||
// 케이스 1: 스트라이드가 폭과 같은 경우 - 전체 블록 복사
|
||||
if (stride == width)
|
||||
{
|
||||
GD.Print("VavCorePlayer: Using full block copy (stride == width)");
|
||||
|
||||
int totalBytes = width * height;
|
||||
var imageData = new byte[totalBytes];
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* srcPtr = (byte*)planeData.ToPointer();
|
||||
fixed (byte* dstPtr = imageData)
|
||||
{
|
||||
// 전체 메모리 블록을 한 번에 복사 (최고속)
|
||||
Buffer.MemoryCopy(srcPtr, dstPtr, totalBytes, totalBytes);
|
||||
}
|
||||
}
|
||||
|
||||
var image = Image.CreateFromData(width, height, false, Image.Format.R8, imageData);
|
||||
GD.Print($"VavCorePlayer: Block copy completed - {totalBytes} bytes");
|
||||
return image;
|
||||
}
|
||||
// 케이스 2: 스트라이드가 폭보다 큰 경우 - 라인별 복사 (하지만 memcpy 사용)
|
||||
else
|
||||
{
|
||||
GD.Print($"VavCorePlayer: Using line-by-line memcpy (stride {stride} > width {width})");
|
||||
|
||||
var imageData = new byte[width * height];
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* srcPtr = (byte*)planeData.ToPointer();
|
||||
fixed (byte* dstPtr = imageData)
|
||||
{
|
||||
// 라인별 고속 메모리 복사
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
byte* srcLine = srcPtr + (y * stride);
|
||||
byte* dstLine = dstPtr + (y * width);
|
||||
|
||||
// 한 라인을 memcpy로 고속 복사
|
||||
Buffer.MemoryCopy(srcLine, dstLine, width, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var image = Image.CreateFromData(width, height, false, Image.Format.R8, imageData);
|
||||
GD.Print($"VavCorePlayer: Line memcpy completed - {height} lines of {width} bytes");
|
||||
return image;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Error in block copy: {ex.Message}");
|
||||
// 폴백: 셰이더 기반 스트라이드 처리 시도
|
||||
return CreatePlaneImageWithShaderStride(planeData, width, height, stride);
|
||||
}
|
||||
}
|
||||
|
||||
// 셰이더에서 스트라이드를 처리하는 방식 (실험적)
|
||||
private Image CreatePlaneImageWithShaderStride(IntPtr planeData, int width, int height, int stride)
|
||||
{
|
||||
try
|
||||
{
|
||||
GD.Print($"VavCorePlayer: Shader stride mode - Creating {stride}x{height} texture for {width}x{height} content");
|
||||
|
||||
// 스트라이드 전체 데이터를 텍스처로 업로드
|
||||
int totalBytes = stride * height;
|
||||
var imageData = new byte[totalBytes];
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* srcPtr = (byte*)planeData.ToPointer();
|
||||
fixed (byte* dstPtr = imageData)
|
||||
{
|
||||
// 전체 스트라이드 데이터를 블록 복사
|
||||
Buffer.MemoryCopy(srcPtr, dstPtr, totalBytes, totalBytes);
|
||||
}
|
||||
}
|
||||
|
||||
// 스트라이드 크기로 텍스처 생성 (셰이더에서 UV 좌표 조정 필요)
|
||||
var image = Image.CreateFromData(stride, height, false, Image.Format.R8, imageData);
|
||||
GD.Print($"VavCorePlayer: Shader stride texture created - {stride}x{height} (actual content: {width}x{height})");
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Error in shader stride mode: {ex.Message}");
|
||||
// 최종 폴백: 기존 방식
|
||||
return CreatePlaneImage(planeData, width, height, stride);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateSingleBlockYUVTexture(VavCoreVideoFrame frame)
|
||||
{
|
||||
try
|
||||
{
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
long ySize = frame.width * frame.height;
|
||||
long uSize = (frame.width / 2) * (frame.height / 2);
|
||||
long vSize = (frame.width / 2) * (frame.height / 2);
|
||||
long totalSize = ySize + uSize + vSize;
|
||||
|
||||
GD.Print($"VavCorePlayer: TRUE single-block copy - Total size: {totalSize} bytes");
|
||||
|
||||
// 진정한 단일 블록 복사: 전체 YUV420P 데이터를 하나의 텍스처로
|
||||
var yuvData = new byte[totalSize];
|
||||
unsafe
|
||||
{
|
||||
byte* srcPtr = (byte*)frame.y_plane.ToPointer();
|
||||
fixed (byte* dstPtr = yuvData)
|
||||
{
|
||||
Buffer.MemoryCopy(srcPtr, dstPtr, totalSize, totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
var copyTime = DateTime.Now;
|
||||
GD.Print($"VavCorePlayer: ONLY ONE Buffer.MemoryCopy completed in {(copyTime - startTime).TotalMilliseconds:F2}ms");
|
||||
|
||||
// 전체 YUV 데이터를 하나의 1D 텍스처로 생성 (셰이더에서 오프셋 계산)
|
||||
var yuvImage = Image.CreateFromData((int)totalSize, 1, false, Image.Format.R8, yuvData);
|
||||
|
||||
// 텍스처 캐싱 최적화 적용
|
||||
ImageTexture yuvTexture;
|
||||
if (_material.GetShaderParameter("yuv_texture").AsGodotObject() == null)
|
||||
{
|
||||
yuvTexture = ImageTexture.CreateFromImage(yuvImage);
|
||||
GD.Print("VavCorePlayer: Single YUV texture CREATED");
|
||||
}
|
||||
else
|
||||
{
|
||||
yuvTexture = _material.GetShaderParameter("yuv_texture").As<ImageTexture>();
|
||||
yuvTexture.Update(yuvImage);
|
||||
// GD.Print("VavCorePlayer: Single YUV texture UPDATED efficiently");
|
||||
}
|
||||
|
||||
var textureTime = DateTime.Now;
|
||||
GD.Print($"VavCorePlayer: Single YUV texture creation completed in {(textureTime - copyTime).TotalMilliseconds:F2}ms");
|
||||
|
||||
// 셰이더에 YUV 오프셋과 크기 정보 전달
|
||||
_material.SetShaderParameter("yuv_texture", yuvTexture);
|
||||
_material.SetShaderParameter("y_offset", 0);
|
||||
_material.SetShaderParameter("u_offset", (int)ySize);
|
||||
_material.SetShaderParameter("v_offset", (int)(ySize + uSize));
|
||||
_material.SetShaderParameter("y_size", (int)ySize);
|
||||
_material.SetShaderParameter("u_size", (int)uSize);
|
||||
_material.SetShaderParameter("v_size", (int)vSize);
|
||||
_material.SetShaderParameter("frame_width", frame.width);
|
||||
_material.SetShaderParameter("frame_height", frame.height);
|
||||
|
||||
var finalTime = DateTime.Now;
|
||||
GD.Print($"VavCorePlayer: TRUE single-block method total time: {(finalTime - startTime).TotalMilliseconds:F2}ms");
|
||||
GD.Print("VavCorePlayer: TRUE single-block YUV420P copy SUCCESS - ZERO additional copies!");
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GD.PrintErr($"VavCorePlayer: Single-block copy failed: {ex.Message}");
|
||||
GD.PrintErr($"VavCorePlayer: Falling back to 3-block method");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
godot-projects/vavcore-demo/scripts/VavCorePlayer.cs.uid
Normal file
1
godot-projects/vavcore-demo/scripts/VavCorePlayer.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d05xaaqtvq8rf
|
||||
77
godot-projects/vavcore-demo/shaders/yuv_to_rgb.gdshader
Normal file
77
godot-projects/vavcore-demo/shaders/yuv_to_rgb.gdshader
Normal file
@@ -0,0 +1,77 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
// 기존 3개 텍스처 방식
|
||||
uniform sampler2D y_texture : source_color, filter_linear;
|
||||
uniform sampler2D u_texture : source_color, filter_linear;
|
||||
uniform sampler2D v_texture : source_color, filter_linear;
|
||||
|
||||
// 단일 블록 YUV 텍스처 방식
|
||||
uniform sampler2D yuv_texture : source_color, filter_linear;
|
||||
uniform int y_offset = 0;
|
||||
uniform int u_offset = 0;
|
||||
uniform int v_offset = 0;
|
||||
uniform int y_size = 0;
|
||||
uniform int u_size = 0;
|
||||
uniform int v_size = 0;
|
||||
uniform int frame_width = 0;
|
||||
uniform int frame_height = 0;
|
||||
|
||||
void fragment() {
|
||||
vec2 uv = UV;
|
||||
|
||||
// 단일 블록 텍스처가 설정된 경우
|
||||
if (y_size > 0 && frame_width > 0 && frame_height > 0) {
|
||||
// 현재 픽셀 위치 계산
|
||||
int pixel_x = int(uv.x * float(frame_width));
|
||||
int pixel_y = int(uv.y * float(frame_height));
|
||||
|
||||
// Y 평면에서 값 가져오기
|
||||
int y_index = pixel_y * frame_width + pixel_x;
|
||||
float y_coord = float(y_offset + y_index) / float(y_size + u_size + v_size);
|
||||
float y = texture(yuv_texture, vec2(y_coord, 0.5)).r;
|
||||
|
||||
// U 평면에서 값 가져오기 (4:2:0 서브샘플링)
|
||||
int u_pixel_x = pixel_x / 2;
|
||||
int u_pixel_y = pixel_y / 2;
|
||||
int u_index = u_pixel_y * (frame_width / 2) + u_pixel_x;
|
||||
float u_coord = float(u_offset + u_index) / float(y_size + u_size + v_size);
|
||||
float u = texture(yuv_texture, vec2(u_coord, 0.5)).r - 0.5;
|
||||
|
||||
// V 평면에서 값 가져오기 (4:2:0 서브샘플링)
|
||||
int v_pixel_x = pixel_x / 2;
|
||||
int v_pixel_y = pixel_y / 2;
|
||||
int v_index = v_pixel_y * (frame_width / 2) + v_pixel_x;
|
||||
float v_coord = float(v_offset + v_index) / float(y_size + u_size + v_size);
|
||||
float v = texture(yuv_texture, vec2(v_coord, 0.5)).r - 0.5;
|
||||
|
||||
// YUV to RGB conversion matrix (BT.709)
|
||||
float r = y + 1.402 * v;
|
||||
float g = y - 0.344 * u - 0.714 * v;
|
||||
float b = y + 1.772 * u;
|
||||
|
||||
// Clamp to valid range
|
||||
r = clamp(r, 0.0, 1.0);
|
||||
g = clamp(g, 0.0, 1.0);
|
||||
b = clamp(b, 0.0, 1.0);
|
||||
|
||||
COLOR = vec4(r, g, b, 1.0);
|
||||
}
|
||||
else {
|
||||
// 기존 3개 텍스처 방식 (폴백)
|
||||
float y = texture(y_texture, uv).r;
|
||||
float u = texture(u_texture, uv).r - 0.5;
|
||||
float v = texture(v_texture, uv).r - 0.5;
|
||||
|
||||
// YUV to RGB conversion matrix (BT.709)
|
||||
float r = y + 1.402 * v;
|
||||
float g = y - 0.344 * u - 0.714 * v;
|
||||
float b = y + 1.772 * u;
|
||||
|
||||
// Clamp to valid range
|
||||
r = clamp(r, 0.0, 1.0);
|
||||
g = clamp(g, 0.0, 1.0);
|
||||
b = clamp(b, 0.0, 1.0);
|
||||
|
||||
COLOR = vec4(r, g, b, 1.0);
|
||||
}
|
||||
}
|
||||
366
vav2/Android_CrossPlatform_Build_Plan.md
Normal file
366
vav2/Android_CrossPlatform_Build_Plan.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# Android 크로스 플랫폼 빌드 구현 계획
|
||||
|
||||
## 📋 **프로젝트 개요**
|
||||
|
||||
Android 플랫폼에서 VavCore 라이브러리의 완전한 네이티브 빌드를 구현하기 위한 단계별 계획입니다.
|
||||
|
||||
**현재 상태:** ✅ 플랫폼 구조 통일 완료, ❌ 크로스 플랫폼 빌드 미완성
|
||||
**목표:** Android NDK를 사용한 완전한 VavCore 네이티브 라이브러리 빌드
|
||||
|
||||
---
|
||||
|
||||
## 🔍 **현재 빌드 문제 분석**
|
||||
|
||||
### **발견된 주요 문제들:**
|
||||
1. **Windows 전용 PCH 파일**: `pch.h`에서 `#include <windows.h>` 사용
|
||||
2. **플랫폼별 조건부 컴파일 부족**: Windows와 Android 코드가 분리되지 않음
|
||||
3. **헤더 경로 문제**: `VavCore/VavCore.h` 경로를 찾을 수 없음
|
||||
4. **디코더 팩토리 분기 필요**: Windows 전용 디코더들이 Android에서 빌드 시도
|
||||
|
||||
### **성공한 부분:**
|
||||
- ✅ Android NDK 환경 구성 성공
|
||||
- ✅ CMake 크로스 컴파일 설정 성공
|
||||
- ✅ Clang 컴파일러 감지 성공
|
||||
- ✅ 심볼릭 링크된 소스 코드 접근 성공
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Phase 1: 기본 빌드 수정** ⭐ **최우선**
|
||||
|
||||
**목표:** Android에서 기본적인 VavCore 빌드 성공
|
||||
**예상 시간:** 2-3시간
|
||||
|
||||
### **1.1 Android 전용 PCH 파일 생성**
|
||||
|
||||
**파일:** `platforms/android/vavcore/src/pch_android.h`
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
// Platform detection
|
||||
#ifdef ANDROID
|
||||
// Android platform specific includes
|
||||
#include <android/log.h>
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <media/NdkMediaFormat.h>
|
||||
#include <media/NdkMediaCrypto.h>
|
||||
#include <media/NdkMediaExtractor.h>
|
||||
|
||||
// Android logging macros
|
||||
#define VAVCORE_LOG_TAG "VavCore"
|
||||
#define VAVCORE_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, VAVCORE_LOG_TAG, __VA_ARGS__)
|
||||
#define VAVCORE_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, VAVCORE_LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
// Windows platform specific includes
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3d12.h>
|
||||
|
||||
// Windows logging macros
|
||||
#define VAVCORE_LOGD(...) OutputDebugStringA(__VA_ARGS__)
|
||||
#define VAVCORE_LOGE(...) OutputDebugStringA(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
// Common cross-platform includes
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
// dav1d includes (available on both platforms)
|
||||
#include <dav1d/dav1d.h>
|
||||
#include <dav1d/picture.h>
|
||||
#include <dav1d/data.h>
|
||||
```
|
||||
|
||||
### **1.2 플랫폼별 조건부 컴파일 매크로 추가**
|
||||
|
||||
**파일:** `platforms/android/vavcore/src/Common/PlatformDefines.h`
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
// Platform detection
|
||||
#ifdef _WIN32
|
||||
#define VAVCORE_PLATFORM_WINDOWS
|
||||
#define VAVCORE_API __declspec(dllexport)
|
||||
#define VAVCORE_CALLING_CONVENTION __stdcall
|
||||
#elif defined(ANDROID)
|
||||
#define VAVCORE_PLATFORM_ANDROID
|
||||
#define VAVCORE_API __attribute__((visibility("default")))
|
||||
#define VAVCORE_CALLING_CONVENTION
|
||||
#else
|
||||
#define VAVCORE_PLATFORM_LINUX
|
||||
#define VAVCORE_API
|
||||
#define VAVCORE_CALLING_CONVENTION
|
||||
#endif
|
||||
|
||||
// Platform-specific GPU API support
|
||||
#ifdef VAVCORE_PLATFORM_WINDOWS
|
||||
#define VAVCORE_SUPPORT_D3D11
|
||||
#define VAVCORE_SUPPORT_D3D12
|
||||
#define VAVCORE_SUPPORT_NVDEC
|
||||
#define VAVCORE_SUPPORT_VPL
|
||||
#define VAVCORE_SUPPORT_AMF
|
||||
#define VAVCORE_SUPPORT_MEDIA_FOUNDATION
|
||||
#endif
|
||||
|
||||
#ifdef VAVCORE_PLATFORM_ANDROID
|
||||
#define VAVCORE_SUPPORT_VULKAN
|
||||
#define VAVCORE_SUPPORT_OPENGL_ES
|
||||
#define VAVCORE_SUPPORT_MEDIACODEC
|
||||
#endif
|
||||
|
||||
// Common support (available on all platforms)
|
||||
#define VAVCORE_SUPPORT_DAV1D
|
||||
#define VAVCORE_SUPPORT_WEBM
|
||||
|
||||
// Surface type definitions
|
||||
#ifdef VAVCORE_PLATFORM_WINDOWS
|
||||
typedef void* VavCoreSurfaceHandle; // Can be ID3D11Texture2D*, ID3D12Resource*
|
||||
#elif defined(VAVCORE_PLATFORM_ANDROID)
|
||||
typedef struct ANativeWindow* VavCoreSurfaceHandle; // Android Surface
|
||||
#else
|
||||
typedef void* VavCoreSurfaceHandle;
|
||||
#endif
|
||||
```
|
||||
|
||||
### **1.3 CMakeLists.txt 수정**
|
||||
|
||||
**파일:** `platforms/android/vavcore/CMakeLists.txt`
|
||||
```cmake
|
||||
# Platform-specific source selection
|
||||
if(ANDROID)
|
||||
# Android-only sources
|
||||
set(PLATFORM_SPECIFIC_SOURCES
|
||||
${VAVCORE_ROOT}/src/Decoder/AndroidMediaCodecAV1Decoder.cpp
|
||||
)
|
||||
|
||||
# Set Android PCH
|
||||
set(VAVCORE_PCH ${VAVCORE_ROOT}/src/pch_android.h)
|
||||
|
||||
# Android-specific preprocessor definitions
|
||||
add_definitions(-DVAVCORE_PLATFORM_ANDROID)
|
||||
add_definitions(-DVAVCORE_SUPPORT_MEDIACODEC)
|
||||
add_definitions(-DVAVCORE_SUPPORT_DAV1D)
|
||||
else()
|
||||
# Windows-only sources
|
||||
set(PLATFORM_SPECIFIC_SOURCES
|
||||
${VAVCORE_ROOT}/src/Decoder/NVDECAV1Decoder.cpp
|
||||
${VAVCORE_ROOT}/src/Decoder/VPLAV1Decoder.cpp
|
||||
${VAVCORE_ROOT}/src/Decoder/AMFAV1Decoder.cpp
|
||||
${VAVCORE_ROOT}/src/Decoder/MediaFoundationAV1Decoder.cpp
|
||||
)
|
||||
|
||||
# Set Windows PCH
|
||||
set(VAVCORE_PCH ${VAVCORE_ROOT}/src/pch.h)
|
||||
|
||||
# Windows-specific preprocessor definitions
|
||||
add_definitions(-DVAVCORE_PLATFORM_WINDOWS)
|
||||
add_definitions(-DVAVCORE_SUPPORT_D3D11)
|
||||
add_definitions(-DVAVCORE_SUPPORT_D3D12)
|
||||
endif()
|
||||
|
||||
# Common sources (available on all platforms)
|
||||
set(VAVCORE_COMMON_SOURCES
|
||||
${VAVCORE_ROOT}/src/Decoder/VideoDecoderFactory.cpp
|
||||
${VAVCORE_ROOT}/src/Decoder/AV1Decoder.cpp # dav1d decoder
|
||||
${VAVCORE_ROOT}/src/FileIO/WebMFileReader.cpp
|
||||
${VAVCORE_ROOT}/src/VavCore.cpp
|
||||
)
|
||||
|
||||
# All sources for current platform
|
||||
set(VAVCORE_ALL_SOURCES
|
||||
${VAVCORE_COMMON_SOURCES}
|
||||
${PLATFORM_SPECIFIC_SOURCES}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 **Phase 2: 디코더 분기 구현** ⭐⭐
|
||||
|
||||
**목표:** 플랫폼별 디코더 자동 선택 및 빌드
|
||||
**예상 시간:** 4-6시간
|
||||
|
||||
### **2.1 VideoDecoderFactory 플랫폼별 분기**
|
||||
|
||||
**파일:** `src/Decoder/VideoDecoderFactory.cpp` 수정
|
||||
```cpp
|
||||
#include "PlatformDefines.h"
|
||||
|
||||
std::unique_ptr<IVideoDecoder> VideoDecoderFactory::CreateDecoder(DecoderType type) {
|
||||
switch (type) {
|
||||
case DecoderType::AUTO:
|
||||
#ifdef VAVCORE_PLATFORM_WINDOWS
|
||||
// Windows 우선순위: NVDEC → VPL → AMF → dav1d → MediaFoundation
|
||||
if (auto decoder = CreateDecoder(DecoderType::NVDEC)) return decoder;
|
||||
if (auto decoder = CreateDecoder(DecoderType::VPL)) return decoder;
|
||||
if (auto decoder = CreateDecoder(DecoderType::AMF)) return decoder;
|
||||
if (auto decoder = CreateDecoder(DecoderType::DAV1D)) return decoder;
|
||||
return CreateDecoder(DecoderType::MEDIA_FOUNDATION);
|
||||
#elif defined(VAVCORE_PLATFORM_ANDROID)
|
||||
// Android 우선순위: MediaCodec → dav1d
|
||||
if (auto decoder = CreateDecoder(DecoderType::ANDROID_MEDIACODEC)) return decoder;
|
||||
return CreateDecoder(DecoderType::DAV1D);
|
||||
#else
|
||||
// Linux/기타: dav1d만
|
||||
return CreateDecoder(DecoderType::DAV1D);
|
||||
#endif
|
||||
|
||||
case DecoderType::ANDROID_MEDIACODEC:
|
||||
#ifdef VAVCORE_SUPPORT_MEDIACODEC
|
||||
return std::make_unique<AndroidMediaCodecAV1Decoder>();
|
||||
#else
|
||||
VAVCORE_LOGE("Android MediaCodec decoder not supported on this platform");
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
case DecoderType::NVDEC:
|
||||
#ifdef VAVCORE_SUPPORT_NVDEC
|
||||
return std::make_unique<NVDECAV1Decoder>();
|
||||
#else
|
||||
VAVCORE_LOGE("NVDEC decoder not supported on this platform");
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
case DecoderType::VPL:
|
||||
#ifdef VAVCORE_SUPPORT_VPL
|
||||
return std::make_unique<VPLAV1Decoder>();
|
||||
#else
|
||||
VAVCORE_LOGE("Intel VPL decoder not supported on this platform");
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
case DecoderType::AMF:
|
||||
#ifdef VAVCORE_SUPPORT_AMF
|
||||
return std::make_unique<AMFAV1Decoder>();
|
||||
#else
|
||||
VAVCORE_LOGE("AMD AMF decoder not supported on this platform");
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
case DecoderType::DAV1D:
|
||||
#ifdef VAVCORE_SUPPORT_DAV1D
|
||||
return std::make_unique<AV1Decoder>();
|
||||
#else
|
||||
VAVCORE_LOGE("dav1d decoder not supported on this platform");
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
case DecoderType::MEDIA_FOUNDATION:
|
||||
#ifdef VAVCORE_SUPPORT_MEDIA_FOUNDATION
|
||||
return std::make_unique<MediaFoundationAV1Decoder>();
|
||||
#else
|
||||
VAVCORE_LOGE("Media Foundation decoder not supported on this platform");
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
default:
|
||||
VAVCORE_LOGE("Unknown decoder type: %d", static_cast<int>(type));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **2.2 Android MediaCodec 디코더 개선**
|
||||
|
||||
**파일:** `src/Decoder/AndroidMediaCodecAV1Decoder.cpp` 검토 및 개선
|
||||
- Android NDK MediaCodec API 최신화
|
||||
- 에러 처리 강화
|
||||
- Surface 렌더링 지원 추가
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Phase 3: 완전한 크로스 플랫폼 구현** ⭐⭐⭐
|
||||
|
||||
**목표:** 모든 플랫폼별 기능 완전 지원
|
||||
**예상 시간:** 8-12시간
|
||||
|
||||
### **3.1 Surface 타입 플랫폼별 지원**
|
||||
|
||||
**파일:** `include/VavCore/VavCore.h` 수정
|
||||
```c
|
||||
// Platform-specific surface types
|
||||
typedef enum {
|
||||
VAVCORE_SURFACE_TYPE_NONE = 0,
|
||||
#ifdef VAVCORE_PLATFORM_WINDOWS
|
||||
VAVCORE_SURFACE_TYPE_D3D11_TEXTURE2D = 1,
|
||||
VAVCORE_SURFACE_TYPE_D3D12_RESOURCE = 2,
|
||||
#endif
|
||||
#ifdef VAVCORE_PLATFORM_ANDROID
|
||||
VAVCORE_SURFACE_TYPE_ANDROID_SURFACE = 10,
|
||||
VAVCORE_SURFACE_TYPE_ANDROID_SURFACE_TEXTURE = 11,
|
||||
#endif
|
||||
VAVCORE_SURFACE_TYPE_CPU_MEMORY = 100, // Available on all platforms
|
||||
} VavCoreSurfaceType;
|
||||
```
|
||||
|
||||
### **3.2 플랫폼별 빌드 스크립트 완성**
|
||||
|
||||
**파일:** `platforms/android/vavcore/build.sh` 개선
|
||||
- 더 자세한 오류 진단
|
||||
- 플랫폼별 라이브러리 링크
|
||||
- 빌드 옵션 최적화
|
||||
|
||||
### **3.3 통합 테스트 및 검증**
|
||||
|
||||
**파일:** `platforms/android/tests/native/android_vavcore_test.cpp`
|
||||
- Android 환경에서 VavCore API 전체 테스트
|
||||
- MediaCodec 디코더 기능 검증
|
||||
- dav1d fallback 동작 확인
|
||||
|
||||
---
|
||||
|
||||
## 📊 **작업 우선순위 및 일정**
|
||||
|
||||
| Phase | 작업 내용 | 예상 시간 | 우선순위 | 의존성 |
|
||||
|-------|-----------|-----------|----------|---------|
|
||||
| Phase 1 | 기본 빌드 수정 | 2-3시간 | ⭐ 최우선 | 없음 |
|
||||
| Phase 2 | 디코더 분기 구현 | 4-6시간 | ⭐⭐ 높음 | Phase 1 완료 |
|
||||
| Phase 3 | 완전한 크로스 플랫폼 | 8-12시간 | ⭐⭐⭐ 중간 | Phase 2 완료 |
|
||||
|
||||
**총 예상 시간: 14-21시간**
|
||||
|
||||
---
|
||||
|
||||
## 🔍 **검증 방법**
|
||||
|
||||
### **Phase 1 완료 기준:**
|
||||
```bash
|
||||
# Android NDK 빌드 성공
|
||||
cd platforms/android/vavcore
|
||||
export ANDROID_NDK_ROOT="/path/to/ndk"
|
||||
bash build.sh
|
||||
# → libVavCore.so 생성 성공
|
||||
```
|
||||
|
||||
### **Phase 2 완료 기준:**
|
||||
```cpp
|
||||
// 플랫폼별 디코더 자동 선택 동작
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(DecoderType::AUTO);
|
||||
// Android: AndroidMediaCodecAV1Decoder 반환
|
||||
// Windows: NVDECAV1Decoder 또는 다른 하드웨어 디코더 반환
|
||||
```
|
||||
|
||||
### **Phase 3 완료 기준:**
|
||||
```cpp
|
||||
// 모든 VavCore API가 Android에서 정상 동작
|
||||
VavCorePlayer* player = vavcore_create_player();
|
||||
vavcore_open_file(player, "test.webm");
|
||||
// → Android MediaCodec 또는 dav1d로 성공적 디코딩
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 **참고 자료**
|
||||
|
||||
- **Android NDK 문서**: https://developer.android.com/ndk
|
||||
- **Android MediaCodec**: https://developer.android.com/ndk/reference/group/media
|
||||
- **CMake Android 툴체인**: https://developer.android.com/ndk/guides/cmake
|
||||
- **dav1d 크로스 컴파일**: https://code.videolan.org/videolan/dav1d
|
||||
|
||||
---
|
||||
|
||||
*생성일: 2025-09-28*
|
||||
*프로젝트: Vav2Player Android 크로스 플랫폼 빌드*
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import chardet
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def detect_encoding(file_path):
|
||||
"""파일의 인코딩을 감지합니다."""
|
||||
try:
|
||||
with open(file_path, 'rb') as f:
|
||||
raw_data = f.read()
|
||||
result = chardet.detect(raw_data)
|
||||
return result['encoding']
|
||||
except Exception as e:
|
||||
print(f"인코딩 감지 실패: {file_path} - {e}")
|
||||
return None
|
||||
|
||||
def convert_to_utf8(file_path):
|
||||
"""파일을 UTF-8로 변환합니다."""
|
||||
try:
|
||||
# 현재 인코딩 감지
|
||||
current_encoding = detect_encoding(file_path)
|
||||
if not current_encoding:
|
||||
print(f"건너뜀: {file_path} (인코딩 감지 실패)")
|
||||
return False
|
||||
|
||||
# 이미 UTF-8인 경우 건너뜀
|
||||
if current_encoding.lower() in ['utf-8', 'ascii']:
|
||||
print(f"건너뜀: {file_path} (이미 UTF-8/ASCII)")
|
||||
return True
|
||||
|
||||
# 파일 읽기
|
||||
with open(file_path, 'r', encoding=current_encoding) as f:
|
||||
content = f.read()
|
||||
|
||||
# UTF-8로 저장
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"변환 완료: {file_path} ({current_encoding} -> UTF-8)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"변환 실패: {file_path} - {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""메인 함수"""
|
||||
current_dir = Path('.')
|
||||
extensions = ['.h', '.cpp']
|
||||
|
||||
print("C/C++ 파일 UTF-8 변환을 시작합니다...")
|
||||
print(f"대상 디렉토리: {current_dir.absolute()}")
|
||||
print(f"대상 확장자: {', '.join(extensions)}")
|
||||
print("-" * 50)
|
||||
|
||||
converted_count = 0
|
||||
failed_count = 0
|
||||
total_count = 0
|
||||
|
||||
# 모든 .h, .cpp 파일 찾기
|
||||
for ext in extensions:
|
||||
for file_path in current_dir.rglob(f'*{ext}'):
|
||||
if file_path.is_file():
|
||||
total_count += 1
|
||||
if convert_to_utf8(file_path):
|
||||
converted_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
print("-" * 50)
|
||||
print(f"변환 완료!")
|
||||
print(f"총 파일 수: {total_count}")
|
||||
print(f"성공: {converted_count}")
|
||||
print(f"실패: {failed_count}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -36,7 +36,6 @@ include_directories(
|
||||
|
||||
# Common source files (cross-platform)
|
||||
set(VAVCORE_COMMON_SOURCES
|
||||
${VAVCORE_ROOT}/src/Common/VideoTypes.cpp
|
||||
${VAVCORE_ROOT}/src/Decoder/VideoDecoderFactory.cpp
|
||||
${VAVCORE_ROOT}/src/VavCore.cpp
|
||||
)
|
||||
|
||||
@@ -92,8 +92,8 @@ class Program
|
||||
{
|
||||
// Try common test file paths
|
||||
string[] testFiles = {
|
||||
@"D:\Project\video-av1\sample\simple_test.webm",
|
||||
@"D:\Project\video-av1\sample\output.webm",
|
||||
@"D:\Project\video-av1\sample\simple_test.webm",
|
||||
@"sample.webm",
|
||||
@"test.webm"
|
||||
};
|
||||
@@ -142,7 +142,7 @@ class Program
|
||||
// Test frame decoding
|
||||
Console.WriteLine(" Testing frame decoding:");
|
||||
int frameCount = 0;
|
||||
for (int i = 0; i < 5; i++)
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
if (player.DecodeNextFrame(out var frame))
|
||||
{
|
||||
|
||||
56
vav2/todo11.txt
Normal file
56
vav2/todo11.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
🔍 발견된 개선 과제:
|
||||
|
||||
- AV1 OBU 파싱 최적화 필요 (별도 프로젝트)
|
||||
- 타임스탬프 정보 처리 개선 (별도 프로젝트)
|
||||
- 실제 비디오 재생 파이프라인 완성 (별도 프로젝트)
|
||||
|
||||
|
||||
|
||||
● 🤔 다음 작업 제안 옵션들:
|
||||
|
||||
VavCore 통합 테스트가 완료되었으니, 다음 단계로 진행할 수 있는 작업들을 제안드립니다:
|
||||
|
||||
옵션 A: AV1 OBU 파싱 문제 해결 ⭐⭐⭐ 추천
|
||||
|
||||
- 현재 발견된 "Error parsing OBU data" 문제 직접 해결
|
||||
- dav1d 라이브러리와 WebM 파싱 로직 디버깅
|
||||
- 실제 비디오 재생까지 완성
|
||||
|
||||
옵션 B: Godot 4.4.1 Extension 실제 테스트 ⭐⭐⭐ 추천
|
||||
|
||||
- VavCore.Godot을 실제 Godot 프로젝트에서 사용
|
||||
- Zero-Copy GPU Pipeline과 CPU Fallback 실제 동작 검증
|
||||
- Godot 씬에서 AV1 비디오 재생 데모 구현
|
||||
|
||||
옵션 C: Android 크로스 플랫폼 빌드 구현 ⭐⭐
|
||||
|
||||
- Android_CrossPlatform_Build_Plan.md의 Phase 1 구현
|
||||
- Android 전용 PCH 파일 및 조건부 컴파일 적용
|
||||
- Android NDK 네이티브 빌드 성공까지
|
||||
|
||||
옵션 D: 프로젝트 완성도 향상 ⭐⭐
|
||||
|
||||
- GUI 애플리케이션 (Vav2Player) 최종 테스트
|
||||
- 전체 빌드 시스템 검증
|
||||
- 사용자 가이드 및 문서 완성
|
||||
|
||||
옵션 E: 새로운 기능 개발 ⭐
|
||||
|
||||
- 특정 요청사항이나 새로운 기능 구현
|
||||
- 성능 최적화 또는 추가 디코더 지원
|
||||
- 새로운 플랫폼 지원 (iOS, Linux 등)
|
||||
|
||||
개인적 추천:
|
||||
|
||||
**옵션 B (Godot Extension 실제 테스트)**를 강력히 추천합니다!
|
||||
|
||||
이유:
|
||||
1. 🎯 완성도 높은 성과: 실제 동작하는 Godot AV1 플레이어 데모
|
||||
2. 🔧 기존 작업 활용: 이미 구축된 Zero-Copy GPU Pipeline 검증
|
||||
3. 🚀 실용성: Godot 개발자들이 바로 사용할 수 있는 결과물
|
||||
4. 📊 검증 완료: VavCore API가 이미 검증되어 안정적 진행 가능
|
||||
|
||||
어떤 작업을 진행하시겠습니까?
|
||||
|
||||
Godot Extension을 실제로 테스트해보면서 GPU Surface 바인딩과 CPU Fallback이 정말로 동작하는지 확인해보는 것이 가장
|
||||
흥미로울 것 같습니다! 🎮✨
|
||||
Reference in New Issue
Block a user