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;
}
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(
AudioDevice device,
ushort formatTag,

View File

@ -10,9 +10,6 @@ namespace MoonWorks.Audio
/// </summary>
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?
public abstract bool AutoUpdate { get; }
@ -20,6 +17,7 @@ namespace MoonWorks.Audio
protected bool ConsumingBuffers = false;
private const int BUFFER_COUNT = 3;
private nuint BufferSize;
private readonly IntPtr[] buffers;
private int nextBufferIndex = 0;
private uint queuedBufferCount = 0;
@ -32,13 +30,17 @@ namespace MoonWorks.Audio
ushort bitsPerSample,
ushort blockAlign,
ushort channels,
uint samplesPerSecond
uint samplesPerSecond,
uint bufferSize
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
{
BufferSize = bufferSize;
System.Console.WriteLine(BufferSize);
buffers = new IntPtr[BUFFER_COUNT];
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(
(void*) buffer,
BUFFER_SIZE,
(int) BufferSize,
out int filledLengthInBytes,
out bool reachedEnd
);

View File

@ -9,8 +9,6 @@ namespace MoonWorks.Audio
private IntPtr VorbisHandle;
private IntPtr FileDataPtr;
private FAudio.stb_vorbis_info Info;
protected override int BUFFER_SIZE => 32768;
public override bool AutoUpdate => true;
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
@ -43,14 +41,16 @@ namespace MoonWorks.Audio
AudioDevice device,
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
IntPtr vorbisHandle,
FAudio.stb_vorbis_info info
FAudio.stb_vorbis_info info,
uint bufferSize = 32768
) : base(
device,
3, /* float type */
32, /* size of float */
(ushort) (4 * info.channels),
(ushort) info.channels,
info.sample_rate
info.sample_rate,
bufferSize
) {
FileDataPtr = fileDataPtr;
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; }
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);

View File

@ -6,7 +6,7 @@ namespace MoonWorks.Video
public unsafe class StreamingSoundTheora : StreamingSound
{
private IntPtr VideoHandle;
protected override int BUFFER_SIZE => 8192;
// Theorafile is not thread safe, so let's update on the main thread.
public override bool AutoUpdate => false;
@ -14,14 +14,16 @@ namespace MoonWorks.Video
AudioDevice device,
IntPtr videoHandle,
int channels,
uint sampleRate
uint sampleRate,
uint bufferSize = 8192
) : base(
device,
3, /* float type */
32, /* size of float */
(ushort) (4 * channels),
(ushort) channels,
sampleRate
sampleRate,
bufferSize
) {
VideoHandle = videoHandle;
}