QOA Support #48
|
@ -1 +1 @@
|
|||
Subproject commit c42b6814c2550bd757b25b9387c0881eb4860af7
|
||||
Subproject commit aaf2568c3e5b202c5cfbd74734386e69f204482c
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue