- Implemented native Song playback in OpenAL and XAudio2 - Some tweaks in the MediaPlayer and MediaQueue - Added a testmusic.ogg file to the media folder
231 lines
7.8 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|