#region Copyright & License Information /* * Copyright 2007-2022 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 { get; } public Stream GetPCMInputStream() { return new OggStream(new OggFormat(this)); } public void Dispose() { reader.Dispose(); } readonly VorbisReader reader; readonly Stream stream; public OggFormat(Stream stream) { var startPosition = stream.Position; try { this.stream = stream; reader = new VorbisReader(stream, false); LengthInSeconds = (float)reader.TotalTime.TotalSeconds; } finally { stream.Position = startPosition; } } OggFormat(OggFormat cloneFrom) { stream = SegmentStream.CreateWithoutOwningStream(cloneFrom.stream, 0, (int)cloneFrom.stream.Length); reader = new VorbisReader(stream, false) { // 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); } } } }