Files
video-orchestra/godot-project/scripts/VP9TestController.cs
2025-09-15 00:17:01 +09:00

398 lines
10 KiB
C#

using Godot;
using System;
using System.IO;
using System.Collections.Generic;
using VideoOrchestra.Utils;
namespace VideoOrchestra
{
/// <summary>
/// Test controller for VP9 video loading and playback
/// Demonstrates usage of VideoOrchestraManager with sample VP9 streams
/// </summary>
public partial class VP9TestController : Control
{
private VideoOrchestraManager _orchestraManager;
private TextureRect[] _textureRects;
private Label _statusLabel;
private Button _loadButton;
private Button _playButton;
private Button _stopButton;
// VP9 WebM video files
private string[] _webmFilePaths = new string[]
{
"res://assets/haewon-oo-00-vp9.webm",
"res://assets/haewon-oo-01-vp9.webm",
"res://assets/haewon-oo-02-vp9.webm"
};
private byte[][] _webmFileData;
private List<byte[]>[] _extractedFrames; // VP9 frames per stream
private bool _isPlaying = false;
private int _currentFrame = 0;
private Timer _playbackTimer;
public override void _Ready()
{
SetupUI();
InitializeOrchestra();
SetupPlaybackTimer();
}
private void SetupUI()
{
// Get UI references
_textureRects = new TextureRect[3];
_textureRects[0] = GetNode<TextureRect>("UI/StreamContainer/Stream0/TextureRect0");
_textureRects[1] = GetNode<TextureRect>("UI/StreamContainer/Stream1/TextureRect1");
_textureRects[2] = GetNode<TextureRect>("UI/StreamContainer/Stream2/TextureRect2");
_statusLabel = GetNode<Label>("UI/StatusLabel");
_loadButton = GetNode<Button>("UI/Controls/LoadButton");
_playButton = GetNode<Button>("UI/Controls/PlayButton");
_stopButton = GetNode<Button>("UI/Controls/StopButton");
// Connect button signals
_loadButton.Pressed += OnLoadButtonPressed;
_playButton.Pressed += OnPlayButtonPressed;
_stopButton.Pressed += OnStopButtonPressed;
// Initial state
_playButton.Disabled = true;
_stopButton.Disabled = true;
UpdateStatus("Ready - Click Load to load VP9 WebM videos");
}
private void InitializeOrchestra()
{
_orchestraManager = GetNode<VideoOrchestraManager>("VideoOrchestraManager");
if (_orchestraManager == null)
{
UpdateStatus("Error: VideoOrchestraManager not found!");
return;
}
// Connect signals
_orchestraManager.StreamDecoded += OnStreamDecoded;
_orchestraManager.DecoderError += OnDecoderError;
_orchestraManager.DecoderInitialized += OnDecoderInitialized;
}
private void SetupPlaybackTimer()
{
_playbackTimer = new Timer();
AddChild(_playbackTimer);
_playbackTimer.WaitTime = 1.0f / 30.0f; // 30 FPS
_playbackTimer.Timeout += OnPlaybackTick;
}
private void OnDecoderInitialized(string platformName, bool hardwareEnabled)
{
var platformInfo = _orchestraManager.GetPlatformInfo();
var hardwareStatus = hardwareEnabled ? "Hardware" : "Software";
UpdateStatus($"VP9 decoder initialized on {platformName} ({hardwareStatus} decoding)");
GD.Print($"Platform capabilities: {platformInfo}");
}
private void OnLoadButtonPressed()
{
UpdateStatus("Loading VP9 WebM video files...");
// TEXTURE FORMAT COMPATIBILITY: Check before loading
GD.Print("Running texture format compatibility check...");
TextureFormatAnalyzer.LogFormatCompatibility();
try
{
// Load real WebM VP9 video files
LoadWebMStreams();
if (_extractedFrames != null && _extractedFrames.Length > 0)
{
_loadButton.Disabled = true;
_playButton.Disabled = false;
// Calculate stats for status
ulong totalBytes = 0;
int totalFrames = 0;
int validFiles = 0;
for (int i = 0; i < _webmFileData.Length; i++)
{
if (_webmFileData[i] != null && _extractedFrames[i] != null)
{
totalBytes += (ulong)_webmFileData[i].Length;
totalFrames += _extractedFrames[i].Count;
validFiles++;
}
}
UpdateStatus($"Loaded {validFiles} VP9 WebM files ({totalBytes / 1024 / 1024:F1} MB, {totalFrames} frames total) - Ready to play");
}
else
{
UpdateStatus("Error: No WebM files loaded");
}
}
catch (Exception ex)
{
UpdateStatus($"Error loading WebM files: {ex.Message}");
GD.PrintErr($"Failed to load WebM streams: {ex}");
}
}
private void LoadWebMStreams()
{
_webmFileData = new byte[_webmFilePaths.Length][];
_extractedFrames = new List<byte[]>[_webmFilePaths.Length];
for (int i = 0; i < _webmFilePaths.Length; i++)
{
try
{
string filePath = _webmFilePaths[i];
GD.Print($"Loading WebM file {i}: {filePath}");
// Use Godot's FileAccess to load the file
using var file = Godot.FileAccess.Open(filePath, Godot.FileAccess.ModeFlags.Read);
if (file == null)
{
GD.PrintErr($"Failed to open WebM file: {filePath}");
continue;
}
// Read entire file into byte array
ulong fileSize = file.GetLength();
_webmFileData[i] = file.GetBuffer((long)fileSize);
// Extract VP9 frames from WebM container
_extractedFrames[i] = WebMParser.ExtractVP9Frames(_webmFileData[i]);
GD.Print($"Loaded WebM file {i}: {fileSize} bytes, extracted {_extractedFrames[i].Count} frames ({filePath})");
}
catch (Exception ex)
{
GD.PrintErr($"Error loading WebM file {i} ({_webmFilePaths[i]}): {ex.Message}");
// Fallback to dummy data
_webmFileData[i] = CreateDummyVP9Frame(i);
_extractedFrames[i] = new List<byte[]> { CreateDummyVP9Frame(i) };
}
}
// Validate that we have at least some data
bool hasValidData = false;
for (int i = 0; i < _webmFileData.Length; i++)
{
if (_extractedFrames[i] != null && _extractedFrames[i].Count > 0)
{
hasValidData = true;
break;
}
}
if (!hasValidData)
{
GD.PrintErr("No valid WebM files loaded, falling back to dummy data");
// Create dummy data as fallback
for (int i = 0; i < 3; i++)
{
_webmFileData[i] = CreateDummyVP9Frame(i);
_extractedFrames[i] = new List<byte[]> { CreateDummyVP9Frame(i) };
}
}
}
private byte[] CreateDummyVP9Frame(int streamId)
{
// Create a dummy VP9 frame for testing
// This is not a real VP9 frame - just placeholder data
var frameData = new byte[1024];
// VP9 frame header (simplified)
frameData[0] = 0x82; // VP9 signature
frameData[1] = 0x49; // VP9 signature
frameData[2] = 0x83; // VP9 signature
frameData[3] = 0x42; // VP9 signature
// Fill with test pattern based on stream ID
for (int i = 4; i < frameData.Length; i++)
{
frameData[i] = (byte)((i + streamId) % 256);
}
return frameData;
}
private void OnPlayButtonPressed()
{
if (!_isPlaying)
{
StartPlayback();
}
}
private void OnStopButtonPressed()
{
if (_isPlaying)
{
StopPlayback();
}
}
private void StartPlayback()
{
_isPlaying = true;
_playButton.Text = "Playing...";
_playButton.Disabled = true;
_stopButton.Disabled = false;
_currentFrame = 0;
UpdateStatus("Starting VP9 WebM playback...");
// Start timer-based frame playback
_playbackTimer.Start();
}
private void StopPlayback()
{
_isPlaying = false;
_playButton.Text = "Play";
_playButton.Disabled = false;
_stopButton.Disabled = true;
_playbackTimer.Stop();
UpdateStatus("Playback stopped");
}
private void OnPlaybackTick()
{
if (!_isPlaying || _extractedFrames == null)
{
_playbackTimer.Stop();
return;
}
try
{
bool anyFramesSubmitted = false;
int maxFrames = 0;
// Find the maximum number of frames across all streams
for (int i = 0; i < _extractedFrames.Length; i++)
{
if (_extractedFrames[i] != null)
{
maxFrames = Math.Max(maxFrames, _extractedFrames[i].Count);
}
}
// If we've reached the end of all streams, loop back or stop
if (maxFrames > 0 && _currentFrame >= maxFrames)
{
_currentFrame = 0; // Loop playback
}
// Submit decode request for the current frame for all streams
for (int streamId = 0; streamId < Math.Min(3, _extractedFrames.Length); streamId++)
{
if (_extractedFrames[streamId] != null && _extractedFrames[streamId].Count > 0)
{
int frameIndex = _currentFrame % _extractedFrames[streamId].Count;
byte[] frameData = _extractedFrames[streamId][frameIndex];
if (_orchestraManager.DecodeFrame(frameData, streamId))
{
anyFramesSubmitted = true;
}
}
}
// After submitting, ask the manager to process any completed frames from its queue
if (anyFramesSubmitted)
{
_orchestraManager.UpdateTextures();
}
// Now update the UI with the latest textures
for (int streamId = 0; streamId < 3; streamId++)
{
UpdateStreamTexture(streamId);
}
if (anyFramesSubmitted)
{
_currentFrame++;
UpdateStatus($"Playing frame {_currentFrame}");
}
else if (maxFrames == 0)
{
UpdateStatus("No frames to play.");
StopPlayback();
}
}
catch (Exception ex)
{
UpdateStatus($"Frame decode error: {ex.Message}");
GD.PrintErr($"Error in playback tick: {ex}");
StopPlayback();
}
}
private void UpdateStreamTexture(int streamId)
{
if (streamId < 0 || streamId >= _textureRects.Length)
return;
try
{
var texture = _orchestraManager.GetStreamTexture(streamId);
if (texture != null)
{
_textureRects[streamId].Texture = texture;
}
}
catch (Exception ex)
{
GD.PrintErr($"Error updating texture for stream {streamId}: {ex.Message}");
}
}
private void OnStreamDecoded(int streamId)
{
GD.Print($"Stream {streamId} decoded successfully");
}
private void OnDecoderError(int streamId, string error)
{
GD.PrintErr($"Decoder error for stream {streamId}: {error}");
UpdateStatus($"Error in stream {streamId}: {error}");
if (_isPlaying)
{
StopPlayback();
}
}
private void UpdateStatus(string message)
{
if (_statusLabel != null)
{
_statusLabel.Text = $"Status: {message}";
}
GD.Print($"VP9Orchestra: {message}");
}
public override void _ExitTree()
{
if (_isPlaying)
{
StopPlayback();
}
_playbackTimer?.QueueFree();
}
}
}