Merge pull request #10912 from pchote/soundloader-streams
Introduce ISoundFormat for parsing sound files.
This commit is contained in:
@@ -45,6 +45,77 @@ namespace OpenRA.FileFormats
|
||||
}
|
||||
|
||||
public class AudLoader : ISoundLoader
|
||||
{
|
||||
bool IsAud(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
s.Position += 10;
|
||||
var readFlag = s.ReadByte();
|
||||
var readFormat = s.ReadByte();
|
||||
s.Position = start;
|
||||
|
||||
if (!Enum.IsDefined(typeof(SoundFlags), readFlag))
|
||||
return false;
|
||||
|
||||
return Enum.IsDefined(typeof(SoundFormat), readFormat);
|
||||
}
|
||||
|
||||
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsAud(stream))
|
||||
{
|
||||
sound = new AudFormat(stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Not a supported AUD
|
||||
}
|
||||
|
||||
sound = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class AudFormat : ISoundFormat
|
||||
{
|
||||
public int Channels { get { return 1; } }
|
||||
public int SampleBits { get { return 16; } }
|
||||
public int SampleRate { get { return sampleRate; } }
|
||||
public float LengthInSeconds { get { return AudReader.SoundLength(stream); } }
|
||||
public Stream GetPCMInputStream() { return new MemoryStream(rawData.Value); }
|
||||
|
||||
int sampleRate;
|
||||
Lazy<byte[]> rawData;
|
||||
|
||||
Stream stream;
|
||||
|
||||
public AudFormat(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
var position = stream.Position;
|
||||
rawData = Exts.Lazy(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] data;
|
||||
if (!AudReader.LoadSound(stream, out data, out sampleRate))
|
||||
throw new InvalidDataException();
|
||||
return data;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = position;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class AudReader
|
||||
{
|
||||
static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
static readonly int[] StepTable =
|
||||
@@ -166,38 +237,5 @@ namespace OpenRA.FileFormats
|
||||
rawData = output;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits,
|
||||
out int sampleRate)
|
||||
{
|
||||
channels = sampleBits = sampleRate = 0;
|
||||
var position = stream.Position;
|
||||
|
||||
try
|
||||
{
|
||||
if (!LoadSound(stream, out rawData, out sampleRate))
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// LoadSound() will check if the stream is in a format that this parser supports.
|
||||
// If not, it will simply return false so we know we can't use it. If it is, it will start
|
||||
// parsing the data without any further failsafes, which means that it will crash on corrupted files
|
||||
// (that end prematurely or otherwise don't conform to the specifications despite the headers being OK).
|
||||
Log.Write("sound", "Failed to parse AUD file {0}. Error message:".F(fileName));
|
||||
Log.Write("sound", e.ToString());
|
||||
rawData = null;
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = position;
|
||||
}
|
||||
|
||||
channels = 1;
|
||||
sampleBits = 16;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,38 +18,30 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
public class VocLoader : ISoundLoader
|
||||
{
|
||||
bool ISoundLoader.TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits, out int sampleRate)
|
||||
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
||||
{
|
||||
var position = stream.Position;
|
||||
|
||||
try
|
||||
{
|
||||
var vocStream = new VocStream(stream);
|
||||
channels = vocStream.Channels;
|
||||
sampleBits = vocStream.BitsPerSample;
|
||||
sampleRate = vocStream.SampleRate;
|
||||
rawData = vocStream.ReadAllBytes();
|
||||
sound = new VocFormat(stream);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
rawData = null;
|
||||
channels = sampleBits = sampleRate = 0;
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = position;
|
||||
// Not a (supported) WAV
|
||||
}
|
||||
|
||||
return true;
|
||||
sound = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class VocStream : Stream
|
||||
public class VocFormat : ISoundFormat
|
||||
{
|
||||
public int BitsPerSample { get { return 8; } }
|
||||
public int SampleBits { get { return 8; } }
|
||||
public int Channels { get { return 1; } }
|
||||
public int SampleRate { get; private set; }
|
||||
public float LengthInSeconds { get { return (float)totalSamples / SampleRate; } }
|
||||
public Stream GetPCMInputStream() { return new VocStream(this); }
|
||||
|
||||
int totalSamples = 0;
|
||||
int samplePosition = 0;
|
||||
@@ -98,9 +90,10 @@ namespace OpenRA.FileFormats
|
||||
public int Count;
|
||||
}
|
||||
|
||||
public VocStream(Stream stream)
|
||||
public VocFormat(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
CheckVocHeader();
|
||||
Preload();
|
||||
}
|
||||
@@ -187,7 +180,7 @@ namespace OpenRA.FileFormats
|
||||
break;
|
||||
}
|
||||
|
||||
// Silence
|
||||
// Silence
|
||||
case 3:
|
||||
{
|
||||
if (block.Length != 3)
|
||||
@@ -199,7 +192,7 @@ namespace OpenRA.FileFormats
|
||||
break;
|
||||
}
|
||||
|
||||
// Repeat start
|
||||
// Repeat start
|
||||
case 6:
|
||||
{
|
||||
if (block.Length != 2)
|
||||
@@ -208,11 +201,11 @@ namespace OpenRA.FileFormats
|
||||
break;
|
||||
}
|
||||
|
||||
// Repeat end
|
||||
// Repeat end
|
||||
case 7:
|
||||
break;
|
||||
|
||||
// Extra info
|
||||
// Extra info
|
||||
case 8:
|
||||
{
|
||||
if (block.Length != 4)
|
||||
@@ -316,44 +309,7 @@ namespace OpenRA.FileFormats
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ReadAllBytes()
|
||||
{
|
||||
Rewind();
|
||||
var buffer = new byte[totalSamples];
|
||||
Read(buffer, 0, totalSamples);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
public override bool CanSeek { get { return false; } }
|
||||
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override long Length { get { return totalSamples; } }
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return samplePosition; }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
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 int Read(byte[] buffer, int offset, int count)
|
||||
int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var bytesWritten = 0;
|
||||
var samplesLeft = Math.Min(count, buffer.Length - offset);
|
||||
@@ -362,7 +318,7 @@ namespace OpenRA.FileFormats
|
||||
var len = FillBuffer(samplesLeft);
|
||||
if (len == 0)
|
||||
break;
|
||||
Array.Copy(this.buffer, 0, buffer, offset, len);
|
||||
Buffer.BlockCopy(this.buffer, 0, buffer, offset, len);
|
||||
samplesLeft -= len;
|
||||
offset += len;
|
||||
bytesWritten += len;
|
||||
@@ -371,9 +327,34 @@ namespace OpenRA.FileFormats
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
public class VocStream : Stream
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
VocFormat format;
|
||||
public VocStream(VocFormat format)
|
||||
{
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return format.samplePosition < format.totalSamples; } }
|
||||
public override bool CanSeek { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override long Length { get { return format.totalSamples; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return format.samplePosition; }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return format.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,74 @@ using System.IO;
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class WavLoader : ISoundLoader
|
||||
{
|
||||
bool IsWave(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
var type = s.ReadASCII(4);
|
||||
s.Position += 4;
|
||||
var format = s.ReadASCII(4);
|
||||
s.Position = start;
|
||||
|
||||
return type == "RIFF" && format == "WAVE";
|
||||
}
|
||||
|
||||
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsWave(stream))
|
||||
{
|
||||
sound = new WavFormat(stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Not a (supported) WAV
|
||||
}
|
||||
|
||||
sound = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class WavFormat : ISoundFormat
|
||||
{
|
||||
public int Channels { get { return reader.Value.Channels; } }
|
||||
public int SampleBits { get { return reader.Value.BitsPerSample; } }
|
||||
public int SampleRate { get { return reader.Value.SampleRate; } }
|
||||
public float LengthInSeconds { get { return WavReader.WaveLength(stream); } }
|
||||
public Stream GetPCMInputStream() { return new MemoryStream(reader.Value.RawOutput); }
|
||||
|
||||
Lazy<WavReader> reader;
|
||||
|
||||
readonly Stream stream;
|
||||
|
||||
public WavFormat(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
var position = stream.Position;
|
||||
reader = Exts.Lazy(() =>
|
||||
{
|
||||
var wavReader = new WavReader();
|
||||
try
|
||||
{
|
||||
if (!wavReader.LoadSound(stream))
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = position;
|
||||
}
|
||||
|
||||
return wavReader;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class WavReader
|
||||
{
|
||||
public int FileSize;
|
||||
public string Format;
|
||||
@@ -34,7 +102,7 @@ namespace OpenRA.FileFormats
|
||||
public enum WaveType { Pcm = 0x1, ImaAdpcm = 0x11 }
|
||||
public static WaveType Type { get; private set; }
|
||||
|
||||
bool LoadSound(Stream s)
|
||||
public bool LoadSound(Stream s)
|
||||
{
|
||||
var type = s.ReadASCII(4);
|
||||
if (type != "RIFF")
|
||||
@@ -176,40 +244,5 @@ namespace OpenRA.FileFormats
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels,
|
||||
out int sampleBits, out int sampleRate)
|
||||
{
|
||||
rawData = null;
|
||||
channels = sampleBits = sampleRate = 0;
|
||||
var position = stream.Position;
|
||||
|
||||
try
|
||||
{
|
||||
if (!LoadSound(stream))
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// LoadSound() will check if the stream is in a format that this parser supports.
|
||||
// If not, it will simply return false so we know we can't use it. If it is, it will start
|
||||
// parsing the data without any further failsafes, which means that it will crash on corrupted files
|
||||
// (that end prematurely or otherwise don't conform to the specifications despite the headers being OK).
|
||||
Log.Write("sound", "Failed to parse WAV file {0}. Error message:".F(fileName));
|
||||
Log.Write("sound", e.ToString());
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = position;
|
||||
}
|
||||
|
||||
rawData = RawOutput;
|
||||
channels = Channels;
|
||||
sampleBits = BitsPerSample;
|
||||
sampleRate = SampleRate;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ namespace OpenRA.FileSystem
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(16));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavLoader.WaveType.Pcm));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavReader.WaveType.Pcm));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate));
|
||||
@@ -90,7 +90,7 @@ namespace OpenRA.FileSystem
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(20));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavLoader.WaveType.ImaAdpcm));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavReader.WaveType.ImaAdpcm));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
@@ -35,19 +36,24 @@ namespace OpenRA.GameRules
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
}
|
||||
|
||||
public void Load(IReadOnlyFileSystem filesystem)
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
if (!filesystem.Exists(Filename))
|
||||
Stream stream;
|
||||
if (!fileSystem.TryOpen(Filename, out stream))
|
||||
return;
|
||||
|
||||
Exists = true;
|
||||
using (var s = filesystem.Open(Filename))
|
||||
ISoundFormat soundFormat;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
if (Filename.ToLowerInvariant().EndsWith("wav"))
|
||||
Length = (int)WavLoader.WaveLength(s);
|
||||
else
|
||||
Length = (int)AudLoader.SoundLength(s);
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
{
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,16 @@ namespace OpenRA
|
||||
{
|
||||
public interface ISoundLoader
|
||||
{
|
||||
bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits, out int sampleRate);
|
||||
bool TryParseSound(Stream stream, out ISoundFormat sound);
|
||||
}
|
||||
|
||||
public interface ISoundFormat
|
||||
{
|
||||
int Channels { get; }
|
||||
int SampleBits { get; }
|
||||
int SampleRate { get; }
|
||||
float LengthInSeconds { get; }
|
||||
Stream GetPCMInputStream();
|
||||
}
|
||||
|
||||
public sealed class Sound : IDisposable
|
||||
@@ -35,7 +44,7 @@ namespace OpenRA
|
||||
|
||||
public Sound(string engineName)
|
||||
{
|
||||
var enginePath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + engineName + ".dll"));
|
||||
var enginePath = Platform.ResolvePath(".", "OpenRA.Platforms." + engineName + ".dll");
|
||||
soundEngine = CreateDevice(Assembly.LoadFile(enginePath));
|
||||
}
|
||||
|
||||
@@ -60,16 +69,17 @@ namespace OpenRA
|
||||
|
||||
using (var stream = fileSystem.Open(filename))
|
||||
{
|
||||
byte[] rawData;
|
||||
int channels;
|
||||
int sampleBits;
|
||||
int sampleRate;
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSound(stream, filename, out rawData, out channels, out sampleBits, out sampleRate))
|
||||
return soundEngine.AddSoundSourceFromMemory(rawData, channels, sampleBits, sampleRate);
|
||||
|
||||
throw new InvalidDataException(filename + " is not a valid sound file!");
|
||||
ISoundFormat soundFormat;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
stream.Position = 0;
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
return soundEngine.AddSoundSourceFromMemory(
|
||||
soundFormat.GetPCMInputStream().ReadAllBytes(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDataException(filename + " is not a valid sound file!");
|
||||
}
|
||||
|
||||
public void Initialize(ISoundLoader[] loaders, IReadOnlyFileSystem fileSystem)
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
}
|
||||
|
||||
if (audioChannels == 1)
|
||||
audioData = compressed ? AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
|
||||
audioData = compressed ? AudReader.LoadSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
|
||||
else
|
||||
{
|
||||
byte[] leftData, rightData;
|
||||
@@ -233,9 +233,9 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
else
|
||||
{
|
||||
adpcmIndex = 0;
|
||||
leftData = AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex);
|
||||
leftData = AudReader.LoadSound(audio1.ToArray(), ref adpcmIndex);
|
||||
adpcmIndex = 0;
|
||||
rightData = AudLoader.LoadSound(audio2.ToArray(), ref adpcmIndex);
|
||||
rightData = AudReader.LoadSound(audio2.ToArray(), ref adpcmIndex);
|
||||
}
|
||||
|
||||
audioData = new byte[rightData.Length + leftData.Length];
|
||||
|
||||
Reference in New Issue
Block a user