anx.framework/Support/OggUtils/OggInputStream.cs
SND\AstrorEnales_cp 0d25ca7589 - Added OggUtils Support project by making use of the csogg and csvorbis projects
- Implemented native Song playback in OpenAL and XAudio2
- Some tweaks in the MediaPlayer and MediaQueue
- Added a testmusic.ogg file to the media folder
2015-03-15 01:11:27 +01:00

231 lines
7.8 KiB
C#

using System;
using System.IO;
// This file is part of the ANX.Framework created by the
// "ANX.Framework developer group" and released under the Ms-PL license.
// For details see: http://anxframework.codeplex.com/license
namespace OggUtils
{
public class OggInputStream
{
protected readonly OggStreamingData StreamData;
private const int DefaultConvsize = 4096 * 2;
private readonly int[] indexCache;
private static int convsizePerChannel = DefaultConvsize;
protected int ConversionBufferOffset;
protected int ConversionBufferSize;
protected static readonly byte[] ConversionBuffer = new byte[DefaultConvsize];
private readonly float[][][] pcmCache = new float[1][][];
public int SampleRate
{
get { return StreamData.Info.rate; }
}
public int Channels
{
get { return StreamData.Info.channels; }
}
public OggInputStream(Stream input)
{
StreamData = new OggStreamingData(input);
try
{
InitVorbis();
convsizePerChannel = DefaultConvsize / StreamData.Info.channels;
indexCache = new int[StreamData.Info.channels];
}
catch
{
StreamData.EndOfStream = true;
}
}
public int Read(byte[] buffer)
{
if (StreamData.EndOfStream)
return 0;
int length = buffer.Length;
int readOffset = 0;
while (length > 0)
{
if (FillConversionBufferIfNeeded() == false)
break;
int convertedBytesAvailable = ConversionBufferSize - ConversionBufferOffset;
int bytesToCopy = Math.Min(length, convertedBytesAvailable);
Array.Copy(ConversionBuffer, ConversionBufferOffset, buffer, readOffset, bytesToCopy);
ConversionBufferOffset += bytesToCopy;
length -= bytesToCopy;
readOffset += bytesToCopy;
}
return readOffset;
}
private void InitVorbis()
{
if (StreamData.InitSyncState() == false)
return;
StreamData.InitStreamState();
StreamData.Info.init();
if (StreamData.PageIn() < 0)
throw new Exception("Error reading first page of Ogg bitstream data.");
if (StreamData.PacketOut() != 1)
throw new Exception("Error reading initial header packet.");
if (StreamData.Info.synthesis_headerin(StreamData.Comment, StreamData.Packet) < 0)
throw new Exception("This Ogg bitstream does not contain Vorbis audio data.");
int headerIndex = 0;
while (headerIndex < 2)
{
while (headerIndex < 2)
{
int result = StreamData.PageOut();
if (result == 0)
break;
if (result != 1)
continue;
StreamData.PageIn();
while (headerIndex < 2)
{
result = StreamData.PacketOut();
if (result == 0)
break;
if (result == -1)
throw new Exception("Corrupt secondary header. Exiting.");
StreamData.Info.synthesis_headerin(StreamData.Comment, StreamData.Packet);
headerIndex++;
}
}
int index = StreamData.SyncState.buffer(4096);
int bytes = Math.Max(0, StreamData.ReadSyncStateDataAt(index));
if (bytes == 0 && headerIndex < 2)
throw new Exception("End of file before finding all Vorbis headers!");
StreamData.SyncState.wrote(bytes);
}
StreamData.DspState.synthesis_init(StreamData.Info);
StreamData.Block.init(StreamData.DspState);
}
protected bool FillConversionBufferIfNeeded()
{
if (ConversionBufferOffset < ConversionBufferSize)
return true;
ConversionBufferSize = GetNextPacket();
ConversionBufferOffset = 0;
if (ConversionBufferSize == -1)
{
StreamData.EndOfStream = true;
return false;
}
ConversionBufferSize = DecodePacket();
return true;
}
private int GetNextPacket()
{
bool fetchedPacket = false;
while (StreamData.EndOfStream == false && fetchedPacket == false)
{
int result1 = StreamData.PacketOut();
if (result1 == 0)
{
int result2 = 0;
while (StreamData.EndOfStream == false && result2 == 0)
{
result2 = StreamData.PageOut();
if (result2 == 0)
FetchData();
}
if (result2 == 0 && StreamData.EndOfPage)
return -1;
if (result2 == 0)
FetchData();
else if (result2 == -1)
return -1;
else
StreamData.PageIn();
}
else if (result1 == -1)
return -1;
else
fetchedPacket = true;
}
return 0;
}
private void FetchData()
{
if (StreamData.EndOfStream)
return;
int index = StreamData.SyncState.buffer(4096);
if (index >= 0)
{
int bytesRead = StreamData.ReadSyncStateDataAt(index);
StreamData.SyncState.wrote(bytesRead);
if (bytesRead > 0)
return;
}
StreamData.EndOfStream = true;
}
private int DecodePacket()
{
if (StreamData.Block.synthesis(StreamData.Packet) == 0)
StreamData.DspState.synthesis_blockin(StreamData.Block);
int conversionOffset = 0;
int samplesToProcess;
while ((samplesToProcess = StreamData.DspState.synthesis_pcmout(pcmCache, indexCache)) > 0)
{
float[][] pcmDataPerChannel = pcmCache[0];
int bout = Math.Min(samplesToProcess, convsizePerChannel);
for (int channelIndex = 0; channelIndex < StreamData.Info.channels; channelIndex++)
{
int ptr = (channelIndex << 1) + conversionOffset;
int mono = indexCache[channelIndex];
for (int sampleOffset = 0; sampleOffset < bout; sampleOffset++)
{
int pcmSample = ConvertPcmData(pcmDataPerChannel[channelIndex][mono + sampleOffset]);
ConversionBuffer[ptr + 0] = (byte)pcmSample;
ConversionBuffer[ptr + 1] = (byte)(int)((uint)pcmSample >> 8);
ptr += StreamData.Info.channels << 1;
}
}
conversionOffset += 2 * StreamData.Info.channels * bout;
StreamData.DspState.synthesis_read(bout);
}
return conversionOffset;
}
private static int ConvertPcmData(float sourceSample)
{
sourceSample = Math.Max(-1f, Math.Min(1f, sourceSample));
int pcmSampleResult = (int)(sourceSample * 32767);
return pcmSampleResult | (pcmSampleResult < 0 ? 0x8000 : 0);
}
}
}