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); }