138 lines
3.8 KiB
C#
138 lines
3.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace MoonWorks.Audio
|
|
{
|
|
public class StreamingSoundQoa : StreamingSoundSeekable
|
|
{
|
|
private IntPtr QoaHandle = IntPtr.Zero;
|
|
private IntPtr FileDataPtr = IntPtr.Zero;
|
|
|
|
uint Channels;
|
|
uint SamplesPerChannelPerFrame;
|
|
uint TotalSamplesPerChannel;
|
|
|
|
public override bool Loaded => QoaHandle != IntPtr.Zero;
|
|
private string FilePath;
|
|
|
|
private const uint QOA_MAGIC = 0x716f6166; /* 'qoaf' */
|
|
|
|
private static unsafe UInt64 ReverseEndianness(UInt64 value)
|
|
{
|
|
byte* bytes = (byte*) &value;
|
|
|
|
return
|
|
((UInt64)(bytes[0]) << 56) | ((UInt64)(bytes[1]) << 48) |
|
|
((UInt64)(bytes[2]) << 40) | ((UInt64)(bytes[3]) << 32) |
|
|
((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
|
|
((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
|
|
}
|
|
|
|
public unsafe static StreamingSoundQoa Create(AudioDevice device, string filePath)
|
|
{
|
|
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
|
using var reader = new BinaryReader(stream);
|
|
|
|
UInt64 fileHeader = ReverseEndianness(reader.ReadUInt64());
|
|
if ((fileHeader >> 32) != QOA_MAGIC)
|
|
{
|
|
throw new AudioLoadException("Specified file is not a QOA file.");
|
|
}
|
|
|
|
uint totalSamplesPerChannel = (uint) (fileHeader & (0xFFFFFFFF));
|
|
if (totalSamplesPerChannel == 0)
|
|
{
|
|
throw new AudioLoadException("Specified file is not a valid QOA file.");
|
|
}
|
|
|
|
UInt64 frameHeader = ReverseEndianness(reader.ReadUInt64());
|
|
uint channels = (uint) ((frameHeader >> 56) & 0x0000FF);
|
|
uint samplerate = (uint) ((frameHeader >> 32) & 0xFFFFFF);
|
|
uint samplesPerChannelPerFrame = (uint) ((frameHeader >> 16) & 0x00FFFF);
|
|
|
|
return new StreamingSoundQoa(
|
|
device,
|
|
filePath,
|
|
channels,
|
|
samplerate,
|
|
samplesPerChannelPerFrame,
|
|
totalSamplesPerChannel
|
|
);
|
|
}
|
|
|
|
internal unsafe StreamingSoundQoa(
|
|
AudioDevice device,
|
|
string filePath,
|
|
uint channels,
|
|
uint samplesPerSecond,
|
|
uint samplesPerChannelPerFrame,
|
|
uint totalSamplesPerChannel
|
|
) : base(
|
|
device,
|
|
1,
|
|
16,
|
|
(ushort) (2 * channels),
|
|
(ushort) channels,
|
|
samplesPerSecond,
|
|
samplesPerChannelPerFrame * channels * sizeof(short),
|
|
true
|
|
) {
|
|
Channels = channels;
|
|
SamplesPerChannelPerFrame = samplesPerChannelPerFrame;
|
|
TotalSamplesPerChannel = totalSamplesPerChannel;
|
|
FilePath = filePath;
|
|
}
|
|
|
|
public override void Seek(uint sampleFrame)
|
|
{
|
|
FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame);
|
|
}
|
|
|
|
public override unsafe void Load()
|
|
{
|
|
var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
|
|
FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
|
|
var fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length);
|
|
fileStream.ReadExactly(fileDataSpan);
|
|
fileStream.Close();
|
|
|
|
QoaHandle = FAudio.qoa_open_from_memory((char*) FileDataPtr, (uint) fileDataSpan.Length, 0);
|
|
if (QoaHandle == IntPtr.Zero)
|
|
{
|
|
NativeMemory.Free((void*) FileDataPtr);
|
|
Logger.LogError("Error opening QOA file!");
|
|
throw new AudioLoadException("Error opening QOA file!");
|
|
}
|
|
}
|
|
|
|
public override unsafe void Unload()
|
|
{
|
|
if (Loaded)
|
|
{
|
|
FAudio.qoa_close(QoaHandle);
|
|
NativeMemory.Free((void*) FileDataPtr);
|
|
|
|
QoaHandle = IntPtr.Zero;
|
|
FileDataPtr = IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|