398 lines
10 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|