diff --git a/AUTHORS b/AUTHORS index c5382c1379..d594107a59 100644 --- a/AUTHORS +++ b/AUTHORS @@ -182,6 +182,9 @@ licensed under the GNU LGPL Version 3. Using TagLib# by Stephen Shaw licensed under the GNU LGPL Version 2.1. +Using NVorbis by Andrew Ward distributed under +the MIT license. + Using ICSharpCode.SharpZipLib initially by Mike Krueger and distributed under the GNU GPL terms. diff --git a/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs b/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs new file mode 100644 index 0000000000..9257326774 --- /dev/null +++ b/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs @@ -0,0 +1,133 @@ +#region Copyright & License Information +/* + * Copyright 2007-2021 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.IO; +using NVorbis; +using OpenRA.Primitives; + +namespace OpenRA.Mods.Common.AudioLoaders +{ + public class OggLoader : ISoundLoader + { + bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound) + { + try + { + sound = new OggFormat(stream); + return true; + } + catch + { + // Unsupported file + } + + sound = null; + return false; + } + } + + public sealed class OggFormat : ISoundFormat + { + public int SampleBits => 16; + public int Channels => reader.Channels; + public int SampleRate => reader.SampleRate; + public float LengthInSeconds => (float)reader.TotalTime.TotalSeconds; + public Stream GetPCMInputStream() { return new OggStream(new OggFormat(this)); } + public void Dispose() { reader.Dispose(); } + + readonly VorbisReader reader; + readonly Stream stream; + + public OggFormat(Stream stream) + { + this.stream = stream; + reader = new VorbisReader(stream); + } + + OggFormat(OggFormat cloneFrom) + { + stream = SegmentStream.CreateWithoutOwningStream(cloneFrom.stream, 0, (int)cloneFrom.stream.Length); + reader = new VorbisReader(stream) + { + // Tell NVorbis to clip samples so we don't have to range-check during reading. + ClipSamples = true + }; + } + + public class OggStream : Stream + { + readonly OggFormat format; + + // This buffer can be static because it can only be used by 1 instance per thread. + [ThreadStatic] + static float[] conversionBuffer = null; + + public OggStream(OggFormat format) + { + this.format = format; + } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + + public override long Length => format.reader.TotalSamples; + + public override long Position + { + get => format.reader.SamplePosition; + set => throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + // Adjust count so it is in 16-bit samples instead of bytes. + count /= 2; + + // Make sure we don't have an odd count. + count -= count % format.reader.Channels; + + // Get the buffer, creating a new one if none exists or the existing one is too small. + var floatBuffer = conversionBuffer ?? (conversionBuffer = new float[count]); + if (floatBuffer.Length < count) + floatBuffer = conversionBuffer = new float[count]; + + // Let NVorbis do the actual reading. + var samples = format.reader.ReadSamples(floatBuffer, offset, count); + + // Move the data back to the request buffer and convert to 16-bit signed samples for OpenAL. + for (var i = 0; i < samples; i++) + { + var conversion = (short)(floatBuffer[i] * 32767); + buffer[offset++] = (byte)(conversion & 255); + buffer[offset++] = (byte)(conversion >> 8); + } + + // Adjust count back to bytes. + return samples * 2; + } + + public override void Flush() { throw new NotImplementedException(); } + public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } + public override void SetLength(long value) { throw new NotImplementedException(); } + public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } + + protected override void Dispose(bool disposing) + { + if (disposing) + format.reader.Dispose(); + + base.Dispose(disposing); + } + } + } +} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index c0f6da3c32..ab3a45cd6a 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -7,6 +7,7 @@ +