QOA Support #48
|
@ -1 +1 @@
|
||||||
Subproject commit c42b6814c2550bd757b25b9387c0881eb4860af7
|
Subproject commit aaf2568c3e5b202c5cfbd74734386e69f204482c
|
|
@ -197,6 +197,44 @@ namespace MoonWorks.Audio
|
||||||
return sound;
|
return sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static unsafe StaticSound FromQOA(AudioDevice device, string path)
|
||||||
|
{
|
||||||
|
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||||
|
var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length);
|
||||||
|
var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length);
|
||||||
|
fileStream.ReadExactly(fileDataSpan);
|
||||||
|
fileStream.Close();
|
||||||
|
|
||||||
|
var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0);
|
||||||
|
if (qoaHandle == 0)
|
||||||
|
{
|
||||||
|
NativeMemory.Free(fileDataPtr);
|
||||||
|
Logger.LogError("Error opening QOA file!");
|
||||||
|
throw new AudioLoadException("Error opening QOA file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
FAudio.qoa_attributes(qoaHandle, out var channels, out var samplerate, out var samples_per_channel_per_frame, out var total_samples_per_channel);
|
||||||
|
|
||||||
|
var bufferLengthInBytes = total_samples_per_channel * channels * sizeof(short);
|
||||||
|
var buffer = NativeMemory.Alloc(bufferLengthInBytes);
|
||||||
|
FAudio.qoa_decode_entire(qoaHandle, (short*) buffer);
|
||||||
|
|
||||||
|
FAudio.qoa_close(qoaHandle);
|
||||||
|
NativeMemory.Free(fileDataPtr);
|
||||||
|
|
||||||
|
return new StaticSound(
|
||||||
|
device,
|
||||||
|
1,
|
||||||
|
16,
|
||||||
|
(ushort) (channels * 2),
|
||||||
|
(ushort) channels,
|
||||||
|
samplerate,
|
||||||
|
(nint) buffer,
|
||||||
|
bufferLengthInBytes,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public StaticSound(
|
public StaticSound(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
ushort formatTag,
|
ushort formatTag,
|
||||||
|
|
|
@ -10,9 +10,6 @@ namespace MoonWorks.Audio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class StreamingSound : SoundInstance
|
public abstract class StreamingSound : SoundInstance
|
||||||
{
|
{
|
||||||
// How big should each buffer we consume be?
|
|
||||||
protected abstract int BUFFER_SIZE { get; }
|
|
||||||
|
|
||||||
// Should the AudioDevice thread automatically update this class?
|
// Should the AudioDevice thread automatically update this class?
|
||||||
public abstract bool AutoUpdate { get; }
|
public abstract bool AutoUpdate { get; }
|
||||||
|
|
||||||
|
@ -20,6 +17,7 @@ namespace MoonWorks.Audio
|
||||||
protected bool ConsumingBuffers = false;
|
protected bool ConsumingBuffers = false;
|
||||||
|
|
||||||
private const int BUFFER_COUNT = 3;
|
private const int BUFFER_COUNT = 3;
|
||||||
|
private nuint BufferSize;
|
||||||
private readonly IntPtr[] buffers;
|
private readonly IntPtr[] buffers;
|
||||||
private int nextBufferIndex = 0;
|
private int nextBufferIndex = 0;
|
||||||
private uint queuedBufferCount = 0;
|
private uint queuedBufferCount = 0;
|
||||||
|
@ -32,13 +30,17 @@ namespace MoonWorks.Audio
|
||||||
ushort bitsPerSample,
|
ushort bitsPerSample,
|
||||||
ushort blockAlign,
|
ushort blockAlign,
|
||||||
ushort channels,
|
ushort channels,
|
||||||
uint samplesPerSecond
|
uint samplesPerSecond,
|
||||||
|
uint bufferSize
|
||||||
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
||||||
{
|
{
|
||||||
|
BufferSize = bufferSize;
|
||||||
|
System.Console.WriteLine(BufferSize);
|
||||||
|
|
||||||
buffers = new IntPtr[BUFFER_COUNT];
|
buffers = new IntPtr[BUFFER_COUNT];
|
||||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||||
{
|
{
|
||||||
buffers[i] = (IntPtr) NativeMemory.Alloc((nuint) BUFFER_SIZE);
|
buffers[i] = (IntPtr) NativeMemory.Alloc(bufferSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +158,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
FillBuffer(
|
FillBuffer(
|
||||||
(void*) buffer,
|
(void*) buffer,
|
||||||
BUFFER_SIZE,
|
(int) BufferSize,
|
||||||
out int filledLengthInBytes,
|
out int filledLengthInBytes,
|
||||||
out bool reachedEnd
|
out bool reachedEnd
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,8 +9,6 @@ namespace MoonWorks.Audio
|
||||||
private IntPtr VorbisHandle;
|
private IntPtr VorbisHandle;
|
||||||
private IntPtr FileDataPtr;
|
private IntPtr FileDataPtr;
|
||||||
private FAudio.stb_vorbis_info Info;
|
private FAudio.stb_vorbis_info Info;
|
||||||
|
|
||||||
protected override int BUFFER_SIZE => 32768;
|
|
||||||
public override bool AutoUpdate => true;
|
public override bool AutoUpdate => true;
|
||||||
|
|
||||||
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
|
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
|
||||||
|
@ -43,14 +41,16 @@ namespace MoonWorks.Audio
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
||||||
IntPtr vorbisHandle,
|
IntPtr vorbisHandle,
|
||||||
FAudio.stb_vorbis_info info
|
FAudio.stb_vorbis_info info,
|
||||||
|
uint bufferSize = 32768
|
||||||
) : base(
|
) : base(
|
||||||
device,
|
device,
|
||||||
3, /* float type */
|
3, /* float type */
|
||||||
32, /* size of float */
|
32, /* size of float */
|
||||||
(ushort) (4 * info.channels),
|
(ushort) (4 * info.channels),
|
||||||
(ushort) info.channels,
|
(ushort) info.channels,
|
||||||
info.sample_rate
|
info.sample_rate,
|
||||||
|
bufferSize
|
||||||
) {
|
) {
|
||||||
FileDataPtr = fileDataPtr;
|
FileDataPtr = fileDataPtr;
|
||||||
VorbisHandle = vorbisHandle;
|
VorbisHandle = vorbisHandle;
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
public class StreamingSoundQoa : StreamingSoundSeekable
|
||||||
|
{
|
||||||
|
private IntPtr QoaHandle;
|
||||||
|
private IntPtr FileDataPtr;
|
||||||
|
|
||||||
|
public override bool AutoUpdate => true;
|
||||||
|
|
||||||
|
uint Channels;
|
||||||
|
uint SamplesPerChannelPerFrame;
|
||||||
|
uint TotalSamplesPerChannel;
|
||||||
|
|
||||||
|
public unsafe static StreamingSoundQoa Load(AudioDevice device, string filePath)
|
||||||
|
{
|
||||||
|
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||||
|
var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length);
|
||||||
|
var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length);
|
||||||
|
fileStream.ReadExactly(fileDataSpan);
|
||||||
|
fileStream.Close();
|
||||||
|
|
||||||
|
var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0);
|
||||||
|
if (qoaHandle == 0)
|
||||||
|
{
|
||||||
|
NativeMemory.Free(fileDataPtr);
|
||||||
|
Logger.LogError("Error opening QOA file!");
|
||||||
|
throw new AudioLoadException("Error opening QOA file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
FAudio.qoa_attributes(qoaHandle, out var channels, out var sampleRate, out var samplesPerChannelPerFrame, out var totalSamplesPerChannel);
|
||||||
|
|
||||||
|
return new StreamingSoundQoa(
|
||||||
|
device,
|
||||||
|
(IntPtr) fileDataPtr,
|
||||||
|
qoaHandle,
|
||||||
|
channels,
|
||||||
|
sampleRate,
|
||||||
|
samplesPerChannelPerFrame,
|
||||||
|
totalSamplesPerChannel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe StreamingSoundQoa(
|
||||||
|
AudioDevice device,
|
||||||
|
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
||||||
|
IntPtr qoaHandle,
|
||||||
|
uint channels,
|
||||||
|
uint samplesPerSecond,
|
||||||
|
uint samplesPerChannelPerFrame,
|
||||||
|
uint totalSamplesPerChannel
|
||||||
|
) : base(
|
||||||
|
device,
|
||||||
|
1,
|
||||||
|
16,
|
||||||
|
(ushort) (2 * channels),
|
||||||
|
(ushort) channels,
|
||||||
|
samplesPerSecond,
|
||||||
|
samplesPerChannelPerFrame * channels * sizeof(short)
|
||||||
|
) {
|
||||||
|
FileDataPtr = fileDataPtr;
|
||||||
|
QoaHandle = qoaHandle;
|
||||||
|
Channels = channels;
|
||||||
|
SamplesPerChannelPerFrame = samplesPerChannelPerFrame;
|
||||||
|
TotalSamplesPerChannel = totalSamplesPerChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Seek(uint sampleFrame)
|
||||||
|
{
|
||||||
|
FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override unsafe void FillBuffer(
|
||||||
|
void* buffer,
|
||||||
|
int bufferLengthInBytes,
|
||||||
|
out int filledLengthInBytes,
|
||||||
|
out bool reachedEnd
|
||||||
|
) {
|
||||||
|
var lengthInShorts = bufferLengthInBytes / sizeof(short);
|
||||||
|
|
||||||
|
// NOTE: this function returns samples per channel!
|
||||||
|
var samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer);
|
||||||
|
|
||||||
|
var sampleCount = samples * Channels;
|
||||||
|
reachedEnd = sampleCount < lengthInShorts;
|
||||||
|
filledLengthInBytes = (int) (sampleCount * sizeof(short));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override unsafe void Destroy()
|
||||||
|
{
|
||||||
|
base.Destroy();
|
||||||
|
|
||||||
|
if (!IsDisposed)
|
||||||
|
{
|
||||||
|
FAudio.qoa_close(QoaHandle);
|
||||||
|
NativeMemory.Free((void*) FileDataPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,24 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public bool Loop { get; set; }
|
public bool Loop { get; set; }
|
||||||
|
|
||||||
protected StreamingSoundSeekable(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
protected StreamingSoundSeekable(
|
||||||
{
|
AudioDevice device,
|
||||||
|
ushort formatTag,
|
||||||
|
ushort bitsPerSample,
|
||||||
|
ushort blockAlign,
|
||||||
|
ushort channels,
|
||||||
|
uint samplesPerSecond,
|
||||||
|
uint bufferSize
|
||||||
|
) : base(
|
||||||
|
device,
|
||||||
|
formatTag,
|
||||||
|
bitsPerSample,
|
||||||
|
blockAlign,
|
||||||
|
channels,
|
||||||
|
samplesPerSecond,
|
||||||
|
bufferSize
|
||||||
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void Seek(uint sampleFrame);
|
public abstract void Seek(uint sampleFrame);
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace MoonWorks.Video
|
||||||
public unsafe class StreamingSoundTheora : StreamingSound
|
public unsafe class StreamingSoundTheora : StreamingSound
|
||||||
{
|
{
|
||||||
private IntPtr VideoHandle;
|
private IntPtr VideoHandle;
|
||||||
protected override int BUFFER_SIZE => 8192;
|
|
||||||
// Theorafile is not thread safe, so let's update on the main thread.
|
// Theorafile is not thread safe, so let's update on the main thread.
|
||||||
public override bool AutoUpdate => false;
|
public override bool AutoUpdate => false;
|
||||||
|
|
||||||
|
@ -14,14 +14,16 @@ namespace MoonWorks.Video
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
IntPtr videoHandle,
|
IntPtr videoHandle,
|
||||||
int channels,
|
int channels,
|
||||||
uint sampleRate
|
uint sampleRate,
|
||||||
|
uint bufferSize = 8192
|
||||||
) : base(
|
) : base(
|
||||||
device,
|
device,
|
||||||
3, /* float type */
|
3, /* float type */
|
||||||
32, /* size of float */
|
32, /* size of float */
|
||||||
(ushort) (4 * channels),
|
(ushort) (4 * channels),
|
||||||
(ushort) channels,
|
(ushort) channels,
|
||||||
sampleRate
|
sampleRate,
|
||||||
|
bufferSize
|
||||||
) {
|
) {
|
||||||
VideoHandle = videoHandle;
|
VideoHandle = videoHandle;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue