diff --git a/OpenRA.Game/Graphics/Video.cs b/OpenRA.Game/Graphics/Video.cs
index eebe79e692..032951667e 100644
--- a/OpenRA.Game/Graphics/Video.cs
+++ b/OpenRA.Game/Graphics/Video.cs
@@ -13,13 +13,16 @@ namespace OpenRA.Video
{
public interface IVideo
{
- ushort Frames { get; }
+ ushort FrameCount { get; }
byte Framerate { get; }
ushort Width { get; }
ushort Height { get; }
- uint[,] FrameData { get; }
- int CurrentFrame { get; }
+ ///
+ /// Current frame color data in 32-bit BGRA.
+ ///
+ uint[,] CurrentFrameData { get; }
+ int CurrentFrameNumber { get; }
void AdvanceFrame();
bool HasAudio { get; }
diff --git a/OpenRA.Mods.Cnc/FileFormats/VqaReader.cs b/OpenRA.Mods.Cnc/FileFormats/VqaReader.cs
index 1f10ec7813..b049aba544 100644
--- a/OpenRA.Mods.Cnc/FileFormats/VqaReader.cs
+++ b/OpenRA.Mods.Cnc/FileFormats/VqaReader.cs
@@ -18,29 +18,38 @@ namespace OpenRA.Mods.Cnc.FileFormats
{
public class VqaReader : IVideo
{
- public ushort Frames => frames;
- public byte Framerate => framerate;
- public ushort Width => width;
- public ushort Height => height;
+ public ushort FrameCount { get; }
+ public byte Framerate { get; }
+ public ushort Width { get; }
+ public ushort Height { get; }
- readonly ushort frames;
- readonly byte framerate;
- readonly ushort width;
- readonly ushort height;
+ public int CurrentFrameNumber { get; private set; }
+ public uint[,] CurrentFrameData
+ {
+ get
+ {
+ if (cachedFrameNumber != CurrentFrameNumber)
+ DecodeFrameData();
- Stream stream;
- int currentFrame;
- ushort numColors;
- ushort blockWidth;
- ushort blockHeight;
- byte chunkBufferParts;
- int2 blocks;
- uint[] offsets;
- uint[] palette;
- uint videoFlags; // if 0x10 is set the video is a 16 bit hq video (ts and later)
- int sampleRate;
- int sampleBits;
- int audioChannels;
+ return cachedCurrentFrameData;
+ }
+ }
+
+ public bool HasAudio { get; set; }
+ public byte[] AudioData { get; private set; } // audio for this frame: 22050Hz 16bit mono pcm, uncompressed.
+ public int AudioChannels { get; }
+ public int SampleBits { get; }
+ public int SampleRate { get; }
+
+ readonly Stream stream;
+ readonly ushort numColors;
+ readonly ushort blockWidth;
+ readonly ushort blockHeight;
+ readonly byte chunkBufferParts;
+ readonly int2 blocks;
+ readonly uint[] offsets;
+ readonly uint[] palette;
+ readonly uint videoFlags; // if 0x10 is set the video is a 16 bit hq video (ts and later)
// Stores a list of subpixels, referenced by the VPTZ chunk
byte[] cbf;
@@ -60,17 +69,8 @@ namespace OpenRA.Mods.Cnc.FileFormats
// Top half contains block info, bottom half contains references to cbf array
byte[] origData;
- // Final frame output
- uint[,] frameData;
- byte[] audioData; // audio for this frame: 22050Hz 16bit mono pcm, uncompressed.
- bool hasAudio;
-
- public byte[] AudioData => audioData;
- public int CurrentFrame => currentFrame;
- public int SampleRate => sampleRate;
- public int SampleBits => sampleBits;
- public int AudioChannels => audioChannels;
- public bool HasAudio => hasAudio;
+ int cachedFrameNumber = -1;
+ uint[,] cachedCurrentFrameData;
public VqaReader(Stream stream)
{
@@ -87,15 +87,15 @@ namespace OpenRA.Mods.Cnc.FileFormats
/*var version = */stream.ReadUInt16();
videoFlags = stream.ReadUInt16();
- frames = stream.ReadUInt16();
- width = stream.ReadUInt16();
- height = stream.ReadUInt16();
+ FrameCount = stream.ReadUInt16();
+ Width = stream.ReadUInt16();
+ Height = stream.ReadUInt16();
blockWidth = stream.ReadUInt8();
blockHeight = stream.ReadUInt8();
- framerate = stream.ReadUInt8();
+ Framerate = stream.ReadUInt8();
chunkBufferParts = stream.ReadUInt8();
- blocks = new int2(width / blockWidth, height / blockHeight);
+ blocks = new int2(Width / blockWidth, Height / blockHeight);
numColors = stream.ReadUInt16();
/*var maxBlocks = */stream.ReadUInt16();
@@ -103,9 +103,9 @@ namespace OpenRA.Mods.Cnc.FileFormats
/*var unknown2 = */stream.ReadUInt32();
// Audio
- sampleRate = stream.ReadUInt16();
- audioChannels = stream.ReadByte();
- sampleBits = stream.ReadByte();
+ SampleRate = stream.ReadUInt16();
+ AudioChannels = stream.ReadByte();
+ SampleBits = stream.ReadByte();
/*var unknown3 =*/stream.ReadUInt32();
/*var unknown4 =*/stream.ReadUInt16();
@@ -113,7 +113,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
/*var unknown5 =*/stream.ReadUInt32();
- var frameSize = Exts.NextPowerOf2(Math.Max(width, height));
+ var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
if (IsHqVqa)
{
@@ -123,14 +123,14 @@ namespace OpenRA.Mods.Cnc.FileFormats
}
else
{
- cbfBuffer = new byte[width * height];
- cbf = new byte[width * height];
- cbp = new byte[width * height];
+ cbfBuffer = new byte[Width * Height];
+ cbf = new byte[Width * Height];
+ cbp = new byte[Width * Height];
origData = new byte[2 * blocks.X * blocks.Y];
}
palette = new uint[numColors];
- frameData = new uint[frameSize, frameSize];
+ cachedCurrentFrameData = new uint[frameSize, frameSize];
var type = stream.ReadASCII(4);
while (type != "FINF")
{
@@ -149,8 +149,8 @@ namespace OpenRA.Mods.Cnc.FileFormats
/*var unknown4 = */stream.ReadUInt16();
// Frame offsets
- offsets = new uint[frames];
- for (var i = 0; i < frames; i++)
+ offsets = new uint[FrameCount];
+ for (var i = 0; i < FrameCount; i++)
{
offsets[i] = stream.ReadUInt32();
if (offsets[i] > 0x40000000)
@@ -165,7 +165,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
public void Reset()
{
- currentFrame = chunkBufferOffset = currentChunkBuffer = 0;
+ CurrentFrameNumber = chunkBufferOffset = currentChunkBuffer = 0;
LoadFrame();
}
@@ -175,10 +175,10 @@ namespace OpenRA.Mods.Cnc.FileFormats
var audio2 = new MemoryStream(); // right channel
var adpcmIndex = 0;
var compressed = false;
- for (var i = 0; i < frames; i++)
+ for (var i = 0; i < FrameCount; i++)
{
stream.Seek(offsets[i], SeekOrigin.Begin);
- var end = (i < frames - 1) ? offsets[i + 1] : stream.Length;
+ var end = (i < FrameCount - 1) ? offsets[i + 1] : stream.Length;
while (stream.Position < end)
{
@@ -196,9 +196,9 @@ namespace OpenRA.Mods.Cnc.FileFormats
{
case "SND0":
case "SND2":
- if (audioChannels == 0)
+ if (AudioChannels == 0)
throw new NotSupportedException();
- else if (audioChannels == 1)
+ else if (AudioChannels == 1)
{
var rawAudio = stream.ReadBytes((int)length);
audio1.WriteArray(rawAudio);
@@ -227,8 +227,8 @@ namespace OpenRA.Mods.Cnc.FileFormats
}
}
- if (audioChannels == 1)
- audioData = compressed ? ImaAdpcmReader.LoadImaAdpcmSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
+ if (AudioChannels == 1)
+ AudioData = compressed ? ImaAdpcmReader.LoadImaAdpcmSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
else
{
byte[] leftData, rightData;
@@ -245,40 +245,40 @@ namespace OpenRA.Mods.Cnc.FileFormats
rightData = ImaAdpcmReader.LoadImaAdpcmSound(audio2.ToArray(), ref adpcmIndex);
}
- audioData = new byte[rightData.Length + leftData.Length];
+ AudioData = new byte[rightData.Length + leftData.Length];
var rightIndex = 0;
var leftIndex = 0;
- for (var i = 0; i < audioData.Length;)
+ for (var i = 0; i < AudioData.Length;)
{
- audioData[i++] = leftData[leftIndex++];
- audioData[i++] = leftData[leftIndex++];
- audioData[i++] = rightData[rightIndex++];
- audioData[i++] = rightData[rightIndex++];
+ AudioData[i++] = leftData[leftIndex++];
+ AudioData[i++] = leftData[leftIndex++];
+ AudioData[i++] = rightData[rightIndex++];
+ AudioData[i++] = rightData[rightIndex++];
}
}
- hasAudio = audioData.Length > 0;
+ HasAudio = AudioData.Length > 0;
}
public void AdvanceFrame()
{
- currentFrame++;
+ CurrentFrameNumber++;
LoadFrame();
}
void LoadFrame()
{
- if (currentFrame >= frames)
+ if (CurrentFrameNumber >= FrameCount)
return;
// Seek to the start of the frame
- stream.Seek(offsets[currentFrame], SeekOrigin.Begin);
- var end = (currentFrame < frames - 1) ? offsets[currentFrame + 1] : stream.Length;
+ stream.Seek(offsets[CurrentFrameNumber], SeekOrigin.Begin);
+ var end = (CurrentFrameNumber < FrameCount - 1) ? offsets[CurrentFrameNumber + 1] : stream.Length;
while (stream.Position < end)
{
var type = stream.ReadASCII(4);
- var length = 0U;
+ uint length;
if (type == "SN2J")
{
var jmp = int2.Swap(stream.ReadUInt32());
@@ -425,11 +425,9 @@ namespace OpenRA.Mods.Cnc.FileFormats
}
}
- int cachedFrame = -1;
-
void DecodeFrameData()
{
- cachedFrame = currentFrame;
+ cachedFrameNumber = CurrentFrameNumber;
if (IsHqVqa)
{
/* The VP?? chunks of the video file contains an array of instructions for
@@ -494,7 +492,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
{
var cbfi = (mod * 256 + px) * 8 + j * blockWidth + i;
var color = (mod == 0x0f) ? px : cbf[cbfi];
- frameData[y * blockHeight + j, x * blockWidth + i] = palette[color];
+ cachedCurrentFrameData[y * blockHeight + j, x * blockWidth + i] = palette[color];
}
}
}
@@ -502,17 +500,6 @@ namespace OpenRA.Mods.Cnc.FileFormats
}
}
- public uint[,] FrameData
- {
- get
- {
- if (cachedFrame != currentFrame)
- DecodeFrameData();
-
- return frameData;
- }
- }
-
bool IsHqVqa => (videoFlags & 0x10) == 16;
void WriteBlock(int blockNumber, int count, ref int x, ref int y)
@@ -527,7 +514,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
{
var p = (bx + by * blockWidth) * 3;
- frameData[frameY + by, frameX + bx] = (uint)(0xFF << 24 | cbf[offset + p] << 16 | cbf[offset + p + 1] << 8 | cbf[offset + p + 2]);
+ cachedCurrentFrameData[frameY + by, frameX + bx] = (uint)(0xFF << 24 | cbf[offset + p] << 16 | cbf[offset + p + 1] << 8 | cbf[offset + p + 2]);
}
x++;
diff --git a/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs b/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs
index cb4e109268..068218b849 100644
--- a/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs
+++ b/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs
@@ -17,49 +17,55 @@ namespace OpenRA.Mods.Cnc.FileFormats
{
public class WsaReader : IVideo
{
+ public ushort FrameCount { get; }
+ public byte Framerate => 1;
+ public ushort Width { get; }
+ public ushort Height { get; }
+
+ public int CurrentFrameNumber { get; private set; }
+ public uint[,] CurrentFrameData
+ {
+ get
+ {
+ if (cachedFrameNumber != CurrentFrameNumber)
+ LoadFrame();
+
+ return cachedCurrentFrameData;
+ }
+ }
+
+ public bool HasAudio => false;
+ public byte[] AudioData => null;
+ public int AudioChannels => 0;
+ public int SampleBits => 0;
+ public int SampleRate => 0;
+
readonly Stream stream;
-
- public ushort Frames { get { return frameCount; } }
- public byte Framerate { get { return 1; } }
- public ushort Width { get { return width; } }
- public ushort Height { get { return height; } }
-
- readonly ushort frameCount;
- readonly ushort width;
- readonly ushort height;
readonly uint[] palette;
readonly uint[] frameOffsets;
- uint[,] coloredFrameData;
- public uint[,] FrameData { get { return coloredFrameData; } }
+ byte[] previousFramePaletteIndexData;
+ byte[] currentFramePaletteIndexData;
- int currentFrame;
- byte[] previousFrameData;
- byte[] currentFrameData;
-
- public byte[] AudioData { get { return null; } }
- public int CurrentFrame { get { return currentFrame; } }
- public int SampleRate { get { return 0; } }
- public int SampleBits { get { return 0; } }
- public int AudioChannels { get { return 0; } }
- public bool HasAudio { get { return false; } }
+ int cachedFrameNumber = -1;
+ uint[,] cachedCurrentFrameData;
public WsaReader(Stream stream)
{
this.stream = stream;
- frameCount = stream.ReadUInt16();
+ FrameCount = stream.ReadUInt16();
- var x = stream.ReadUInt16();
- var y = stream.ReadUInt16();
+ /*var x = */stream.ReadUInt16();
+ /*var y = */stream.ReadUInt16();
- width = stream.ReadUInt16();
- height = stream.ReadUInt16();
+ Width = stream.ReadUInt16();
+ Height = stream.ReadUInt16();
var delta = stream.ReadUInt16() + 37;
var flags = stream.ReadUInt16();
- frameOffsets = new uint[frameCount + 2];
+ frameOffsets = new uint[FrameCount + 2];
for (var i = 0; i < frameOffsets.Length; i++)
frameOffsets[i] = stream.ReadUInt32();
@@ -89,48 +95,48 @@ namespace OpenRA.Mods.Cnc.FileFormats
public void Reset()
{
- currentFrame = 0;
- previousFrameData = null;
+ CurrentFrameNumber = 0;
+ previousFramePaletteIndexData = null;
LoadFrame();
}
public void AdvanceFrame()
{
- previousFrameData = currentFrameData;
- currentFrame++;
+ previousFramePaletteIndexData = currentFramePaletteIndexData;
+ CurrentFrameNumber++;
LoadFrame();
}
void LoadFrame()
{
- if (currentFrame >= frameCount)
+ if (CurrentFrameNumber >= FrameCount)
return;
- stream.Seek(frameOffsets[currentFrame], SeekOrigin.Begin);
+ stream.Seek(frameOffsets[CurrentFrameNumber], SeekOrigin.Begin);
- var dataLength = frameOffsets[currentFrame + 1] - frameOffsets[currentFrame];
+ var dataLength = frameOffsets[CurrentFrameNumber + 1] - frameOffsets[CurrentFrameNumber];
var rawData = StreamExts.ReadBytes(stream, (int)dataLength);
- var intermediateData = new byte[width * height];
+ var intermediateData = new byte[Width * Height];
// Format80 decompression
LCWCompression.DecodeInto(rawData, intermediateData);
// and Format40 decompression
- currentFrameData = new byte[width * height];
- if (previousFrameData == null)
- Array.Clear(currentFrameData, 0, currentFrameData.Length);
+ currentFramePaletteIndexData = new byte[Width * Height];
+ if (previousFramePaletteIndexData == null)
+ Array.Clear(currentFramePaletteIndexData, 0, currentFramePaletteIndexData.Length);
else
- Array.Copy(previousFrameData, currentFrameData, currentFrameData.Length);
+ Array.Copy(previousFramePaletteIndexData, currentFramePaletteIndexData, currentFramePaletteIndexData.Length);
- XORDeltaCompression.DecodeInto(intermediateData, currentFrameData, 0);
+ XORDeltaCompression.DecodeInto(intermediateData, currentFramePaletteIndexData, 0);
var c = 0;
- var frameSize = Exts.NextPowerOf2(Math.Max(width, height));
- coloredFrameData = new uint[frameSize, frameSize];
- for (var y = 0; y < height; y++)
- for (var x = 0; x < width; x++)
- coloredFrameData[y, x] = palette[currentFrameData[c++]];
+ var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
+ cachedCurrentFrameData = new uint[frameSize, frameSize];
+ for (var y = 0; y < Height; y++)
+ for (var x = 0; x < Width; x++)
+ cachedCurrentFrameData[y, x] = palette[currentFramePaletteIndexData[c++]];
}
}
}
diff --git a/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs
index c49744e480..a67a0b045b 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs
@@ -165,7 +165,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var frameContainer = panel.GetOrNull("FRAME_SELECTOR");
if (frameContainer != null)
frameContainer.IsVisible = () => (currentSprites != null && currentSprites.Length > 1) ||
- (isVideoLoaded && player != null && player.Video != null && player.Video.Frames > 1) ||
+ (isVideoLoaded && player != null && player.Video != null && player.Video.FrameCount > 1) ||
currentSoundFormat != null;
frameSlider = panel.GetOrNull("FRAME_SLIDER");
@@ -180,7 +180,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
frameSlider.GetValue = () =>
{
if (isVideoLoaded)
- return player.Video.CurrentFrame;
+ return player.Video.CurrentFrameNumber;
if (currentSound != null)
return currentSound.SeekPosition * currentSoundFormat.SampleRate;
@@ -197,7 +197,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
frameText.GetText = () =>
{
if (isVideoLoaded)
- return $"{player.Video.CurrentFrame + 1} / {player.Video.Frames}";
+ return $"{player.Video.CurrentFrameNumber + 1} / {player.Video.FrameCount}";
if (currentSoundFormat != null)
return $"{Math.Round(currentSoundFormat.LengthInSeconds, 3)} sec";
@@ -516,7 +516,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (frameSlider != null)
{
- frameSlider.MaximumValue = (float)player.Video.Frames - 1;
+ frameSlider.MaximumValue = (float)player.Video.FrameCount - 1;
frameSlider.Ticks = 0;
}
}
diff --git a/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs b/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs
index c1c295aa13..ea774d9a5f 100644
--- a/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs
+++ b/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs
@@ -60,14 +60,14 @@ namespace OpenRA.Mods.Common.Widgets
Game.Sound.StopVideo();
onComplete = () => { };
- invLength = video.Framerate * 1f / video.Frames;
+ invLength = video.Framerate * 1f / video.FrameCount;
var size = Math.Max(video.Width, video.Height);
var textureSize = Exts.NextPowerOf2(size);
var videoSheet = new Sheet(SheetType.BGRA, new Size(textureSize, textureSize));
videoSheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
- videoSheet.GetTexture().SetData(video.FrameData);
+ videoSheet.GetTexture().SetData(video.CurrentFrameData);
videoSprite = new Sprite(videoSheet,
new Rectangle(
@@ -93,27 +93,29 @@ namespace OpenRA.Mods.Common.Widgets
if (!stopped && !paused)
{
- var nextFrame = video.CurrentFrame + 1;
+ int nextFrame;
if (video.HasAudio && !Game.Sound.DummyEngine)
- nextFrame = (int)float2.Lerp(0, video.Frames, Game.Sound.VideoSeekPosition * invLength);
+ nextFrame = (int)float2.Lerp(0, video.FrameCount, Game.Sound.VideoSeekPosition * invLength);
+ else
+ nextFrame = video.CurrentFrameNumber + 1;
// Without the 2nd check the sound playback sometimes ends before the final frame is displayed which causes the player to be stuck on the first frame
- if (nextFrame > video.Frames || nextFrame < video.CurrentFrame)
+ if (nextFrame > video.FrameCount || nextFrame < video.CurrentFrameNumber)
{
Stop();
return;
}
var skippedFrames = 0;
- while (nextFrame > video.CurrentFrame)
+ while (nextFrame > video.CurrentFrameNumber)
{
video.AdvanceFrame();
- videoSprite.Sheet.GetTexture().SetData(video.FrameData);
+ videoSprite.Sheet.GetTexture().SetData(video.CurrentFrameData);
skippedFrames++;
}
if (skippedFrames > 1)
- Log.Write("perf", "VqaPlayer : {0} skipped {1} frames at position {2}", cachedVideo, skippedFrames, video.CurrentFrame);
+ Log.Write("perf", "VqaPlayer : {0} skipped {1} frames at position {2}", cachedVideo, skippedFrames, video.CurrentFrameNumber);
}
WidgetUtils.DrawSprite(videoSprite, videoOrigin, videoSize);
@@ -216,7 +218,7 @@ namespace OpenRA.Mods.Common.Widgets
paused = true;
Game.Sound.StopVideo();
video.Reset();
- videoSprite.Sheet.GetTexture().SetData(video.FrameData);
+ videoSprite.Sheet.GetTexture().SetData(video.CurrentFrameData);
Game.RunAfterTick(onComplete);
}