QOA Support (#48)

Reviewed-on: MoonsideGames/MoonWorks#48
remotes/1695061714407202320/main
cosmonaut 2023-05-05 22:26:32 +00:00
parent 2d7bb24b5c
commit bd405dfbf0
7 changed files with 177 additions and 16 deletions

@ -1 +1 @@
Subproject commit c42b6814c2550bd757b25b9387c0881eb4860af7 Subproject commit aaf2568c3e5b202c5cfbd74734386e69f204482c

View File

@ -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,

View File

@ -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
); );

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -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);

View File

@ -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;
} }