First commit.
This commit is contained in:
1929
src/IO/Audio/Audio.cpp
Normal file
1929
src/IO/Audio/Audio.cpp
Normal file
File diff suppressed because it is too large
Load Diff
109
src/IO/Audio/AudioCodec.cpp
Normal file
109
src/IO/Audio/AudioCodec.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "../../../include/IO/Audio/AudioCodec.h"
|
||||
|
||||
namespace lwe
|
||||
{
|
||||
AudioCodec::AudioCodec()
|
||||
: hashExt(0), endianness(Endianness::LE), encodeCb(nullptr), decodeCb(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
AudioCodec::AudioCodec(Str_8 id, Str_8 ext, const Endianness end,
|
||||
bool (* encodeCb)(const AudioCodec* const, Serializer<UInt_64>&, const Audio*),
|
||||
bool (* decodeCb)(const AudioCodec* const, Serializer<UInt_64>&, Audio*))
|
||||
: id(std::move(id)), hashExt(ext.Hash_64()), ext(std::move(ext)), endianness(end), encodeCb(encodeCb), decodeCb(decodeCb)
|
||||
{
|
||||
}
|
||||
|
||||
AudioCodec::AudioCodec(AudioCodec&& codec) noexcept
|
||||
: id(std::move(codec.id)), hashExt(codec.hashExt), ext(std::move(codec.ext)), endianness(codec.endianness),
|
||||
encodeCb(codec.encodeCb), decodeCb(codec.decodeCb)
|
||||
{
|
||||
codec.hashExt = 0;
|
||||
codec.endianness = Endianness::LE;
|
||||
codec.encodeCb = nullptr;
|
||||
codec.decodeCb = nullptr;
|
||||
}
|
||||
|
||||
AudioCodec::AudioCodec(const AudioCodec& codec)
|
||||
: id(codec.id), hashExt(codec.hashExt), ext(codec.ext), endianness(codec.endianness), encodeCb(codec.encodeCb),
|
||||
decodeCb(codec.decodeCb)
|
||||
{
|
||||
}
|
||||
|
||||
AudioCodec& AudioCodec::operator=(AudioCodec&& codec) noexcept
|
||||
{
|
||||
if (this == &codec)
|
||||
return *this;
|
||||
|
||||
id = std::move(codec.id);
|
||||
hashExt = codec.hashExt;
|
||||
ext = std::move(codec.ext);
|
||||
endianness = codec.endianness;
|
||||
encodeCb = codec.encodeCb;
|
||||
decodeCb = codec.decodeCb;
|
||||
|
||||
codec.hashExt = 0;
|
||||
codec.endianness = Endianness::LE;
|
||||
codec.encodeCb = nullptr;
|
||||
codec.decodeCb = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AudioCodec& AudioCodec::operator=(const AudioCodec& codec)
|
||||
{
|
||||
if (this == &codec)
|
||||
return *this;
|
||||
|
||||
id = codec.id;
|
||||
hashExt = codec.hashExt;
|
||||
ext = codec.ext;
|
||||
endianness = codec.endianness;
|
||||
encodeCb = codec.encodeCb;
|
||||
decodeCb = codec.decodeCb;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Str_8 AudioCodec::GetId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
UInt_64 AudioCodec::GetHashExt() const
|
||||
{
|
||||
return hashExt;
|
||||
}
|
||||
|
||||
Str_8 AudioCodec::GetExt() const
|
||||
{
|
||||
return ext;
|
||||
}
|
||||
|
||||
Endianness AudioCodec::GetEndianness() const
|
||||
{
|
||||
return endianness;
|
||||
}
|
||||
|
||||
bool AudioCodec::Encode(Serializer<UInt_64>& out, const Audio* in) const
|
||||
{
|
||||
if (!encodeCb)
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Encoding is not supported for the " + id + " format.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return encodeCb(this, out, in);
|
||||
}
|
||||
|
||||
bool AudioCodec::Decode(Serializer<UInt_64>& in, Audio* out) const
|
||||
{
|
||||
if (!decodeCb)
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Decoding is not supported for the " + id + " format.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return decodeCb(this, in, out);
|
||||
}
|
||||
}
|
383
src/IO/Audio/AudioDevice_ALSA.cpp
Normal file
383
src/IO/Audio/AudioDevice_ALSA.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
#include "../../../include/IO/Audio/AudioDevice_ALSA.h"
|
||||
#include "../../../include/EHS.h"
|
||||
#include "../../../include/Log.h"
|
||||
|
||||
namespace lwe
|
||||
{
|
||||
AudioDevice::~AudioDevice()
|
||||
{
|
||||
if (!hdl)
|
||||
return;
|
||||
|
||||
snd_pcm_drain(hdl);
|
||||
snd_pcm_close(hdl);
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice()
|
||||
: hdl(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice(AudioDevice&& device) noexcept
|
||||
: BaseAudioDevice(device), hdl(device.hdl)
|
||||
{
|
||||
device.hdl = nullptr;
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice(const AudioDevice& device)
|
||||
: BaseAudioDevice(device), hdl(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
AudioDevice& AudioDevice::operator=(AudioDevice&& device) noexcept
|
||||
{
|
||||
if (this == &device)
|
||||
return *this;
|
||||
|
||||
BaseAudioDevice::operator=(device);
|
||||
|
||||
hdl = device.hdl;
|
||||
|
||||
device.hdl = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AudioDevice& AudioDevice::operator=(const AudioDevice& device)
|
||||
{
|
||||
if (this == &device)
|
||||
return *this;
|
||||
|
||||
BaseAudioDevice::operator=(device);
|
||||
|
||||
hdl = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void AudioDevice::Release()
|
||||
{
|
||||
if (!IsValid())
|
||||
return;
|
||||
|
||||
snd_pcm_drain(hdl);
|
||||
snd_pcm_close(hdl);
|
||||
hdl = nullptr;
|
||||
}
|
||||
|
||||
void AudioDevice::OpenStream()
|
||||
{
|
||||
if (streaming)
|
||||
return;
|
||||
|
||||
snd_pcm_hw_params_t* params;
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
|
||||
snd_pcm_hw_params_any(hdl, params);
|
||||
|
||||
if (snd_pcm_hw_params_set_access(hdl, params, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to set access.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (bitDepth)
|
||||
{
|
||||
snd_pcm_format_t format;
|
||||
switch (dataType)
|
||||
{
|
||||
case DataType::FLOAT:
|
||||
{
|
||||
format = SND_PCM_FORMAT_FLOAT;
|
||||
break;
|
||||
}
|
||||
case DataType::SINT_32:
|
||||
{
|
||||
format = SND_PCM_FORMAT_S32;
|
||||
break;
|
||||
}
|
||||
case DataType::SINT_24:
|
||||
{
|
||||
format = SND_PCM_FORMAT_S24;
|
||||
break;
|
||||
}
|
||||
case DataType::SINT_16:
|
||||
{
|
||||
format = SND_PCM_FORMAT_S16;
|
||||
break;
|
||||
}
|
||||
case DataType::SINT_8:
|
||||
{
|
||||
format = SND_PCM_FORMAT_S8;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Invalid data type.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_set_format(hdl, params, format);
|
||||
}
|
||||
|
||||
if (channels)
|
||||
{
|
||||
if (snd_pcm_hw_params_set_channels_near(hdl, params, &channels) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 1, "Failed to set channels.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleRate)
|
||||
{
|
||||
if (snd_pcm_hw_params_set_rate_near(hdl, params, &sampleRate, nullptr) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 2, "Failed to set sample rate.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_set_period_time_near(hdl, params, &period, nullptr) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 3, "Failed to set period.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_set_buffer_time_near(hdl, params, &latency, nullptr) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 4, "Failed to set latency.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params(hdl, params) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 5, "Failed to apply hardware parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bitDepth)
|
||||
{
|
||||
snd_pcm_format_t format;
|
||||
if (snd_pcm_hw_params_get_format(params, &format) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 6, "Failed to retrieve audio device properties.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case SND_PCM_FORMAT_FLOAT:
|
||||
{
|
||||
dataType = DataType::FLOAT;
|
||||
bitDepth = 32;
|
||||
break;
|
||||
}
|
||||
case SND_PCM_FORMAT_S32:
|
||||
{
|
||||
dataType = DataType::SINT_32;
|
||||
bitDepth = 32;
|
||||
break;
|
||||
}
|
||||
case SND_PCM_FORMAT_S24:
|
||||
{
|
||||
dataType = DataType::SINT_24;
|
||||
bitDepth = 24;
|
||||
break;
|
||||
}
|
||||
case SND_PCM_FORMAT_S16:
|
||||
{
|
||||
dataType = DataType::SINT_16;
|
||||
bitDepth = 16;
|
||||
break;
|
||||
}
|
||||
case SND_PCM_FORMAT_S8:
|
||||
{
|
||||
dataType = DataType::SINT_8;
|
||||
bitDepth = 8;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LWE_LOG_INT("Error", 7, "Format unsupported.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!channels)
|
||||
{
|
||||
if (snd_pcm_hw_params_get_channels(params, &channels) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 8, "Failed to retrieve channel count.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sampleRate)
|
||||
{
|
||||
int dir;
|
||||
if (snd_pcm_hw_params_get_rate(params, &sampleRate, &dir) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 9, "Failed to retrieve sample rate.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_get_buffer_size(params, &maxFrames) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 10, "Failed to retrieve buffer size.");
|
||||
}
|
||||
|
||||
snd_pcm_sw_params_t* swParams = nullptr;
|
||||
snd_pcm_sw_params_alloca(&swParams);
|
||||
|
||||
if (snd_pcm_sw_params_current(hdl, swParams) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 11, "Failed to retrieve software parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_sw_params_set_silence_threshold(hdl, swParams, maxFrames) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 12, "Failed to set silence threshold.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_sw_params_set_silence_size(hdl, swParams, maxFrames) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 12, "Failed to set silence size.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_sw_params(hdl, swParams) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 13, "Failed to set software parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_prepare(hdl) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 14, "Failed to prepare audio stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
streaming = true;
|
||||
}
|
||||
|
||||
void AudioDevice::CloseStream()
|
||||
{
|
||||
if (!streaming)
|
||||
return;
|
||||
|
||||
snd_pcm_drain(hdl);
|
||||
|
||||
streaming = false;
|
||||
}
|
||||
|
||||
UInt_64 AudioDevice::GetAvailFrames() const
|
||||
{
|
||||
snd_pcm_state_t state = snd_pcm_state(hdl);
|
||||
if (state == SND_PCM_STATE_XRUN)
|
||||
{
|
||||
LWE_LOG_INT("Warning", 0, "Buffer overrun/underrun occurred.");
|
||||
if (snd_pcm_recover(hdl, -EPIPE, 0) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 1, "Failed to recover from buffer overrun/underrun.");
|
||||
return 0;
|
||||
}
|
||||
return GetAvailFrames();
|
||||
}
|
||||
else if (state == SND_PCM_STATE_PREPARED)
|
||||
{
|
||||
if (snd_pcm_start(hdl) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 2, "Failed to start audio stream.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return GetAvailFrames();
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t frames = snd_pcm_avail_update(hdl);
|
||||
if (frames < 0)
|
||||
{
|
||||
if (frames == -EPIPE)
|
||||
{
|
||||
LWE_LOG_INT("Warning", 3, "Buffer overrun/underrun occurred.");
|
||||
if (snd_pcm_recover(hdl, -EPIPE, 1) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 4, "Failed to recover from buffer overrun/underrun.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return GetAvailFrames();
|
||||
}
|
||||
else
|
||||
{
|
||||
LWE_LOG_INT("Error", 5, "Failed to retrieve available frames with error #" + Str_8::FromNum(frames) + ".");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
Byte* AudioDevice::Map(UInt_64* offset, UInt_64* frames)
|
||||
{
|
||||
const snd_pcm_channel_area_t* areas;
|
||||
if (snd_pcm_mmap_begin(hdl, &areas, offset, frames) < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to map audio buffer.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return (Byte*)areas->addr + areas->first / 8;
|
||||
}
|
||||
|
||||
void AudioDevice::UnMap(const UInt_64 offset, const UInt_64 frames)
|
||||
{
|
||||
snd_pcm_sframes_t committed = snd_pcm_mmap_commit(hdl, offset, frames);
|
||||
if (committed < 0)
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to commit mapped audio buffer.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioDevice::IsValid() const
|
||||
{
|
||||
return hdl;
|
||||
}
|
||||
|
||||
AudioDevice AudioDevice::GetDefault(const AudioDeviceType type)
|
||||
{
|
||||
AudioDevice result;
|
||||
|
||||
snd_pcm_stream_t rType;
|
||||
if (type == AudioDeviceType::INPUT)
|
||||
{
|
||||
rType = SND_PCM_STREAM_CAPTURE;
|
||||
}
|
||||
else if (type == AudioDeviceType::OUTPUT)
|
||||
{
|
||||
rType = SND_PCM_STREAM_PLAYBACK;
|
||||
}
|
||||
else
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Wrong value for the audio device type.");
|
||||
return {};
|
||||
}
|
||||
|
||||
snd_pcm_open(&result.hdl, "default", rType, SND_PCM_NONBLOCK);
|
||||
result.type = AudioDeviceType::OUTPUT;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Array<AudioDevice> AudioDevice::Get(const AudioDeviceType type, const AudioDeviceState state)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
581
src/IO/Audio/AudioDevice_W32.cpp
Normal file
581
src/IO/Audio/AudioDevice_W32.cpp
Normal file
@@ -0,0 +1,581 @@
|
||||
#include "../../../include/IO/Audio/AudioDevice_W32.h"
|
||||
#include "../../../include/Log.h"
|
||||
|
||||
namespace lwe
|
||||
{
|
||||
AudioDevice::~AudioDevice()
|
||||
{
|
||||
if (hdl)
|
||||
{
|
||||
hdl->Release();
|
||||
hdl = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice()
|
||||
: hdl(nullptr), client(nullptr), playbackClient(nullptr), captureClient(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice(AudioDevice&& device) noexcept
|
||||
: BaseAudioDevice(device), hdl(device.hdl), client(device.client), playbackClient(device.playbackClient),
|
||||
captureClient(device.captureClient)
|
||||
{
|
||||
device.hdl = nullptr;
|
||||
device.client = nullptr;
|
||||
device.playbackClient = nullptr;
|
||||
device.captureClient = nullptr;
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice(const AudioDevice& device)
|
||||
: BaseAudioDevice(device), hdl(nullptr), client(nullptr), playbackClient(nullptr),
|
||||
captureClient(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
AudioDevice& AudioDevice::operator=(AudioDevice&& device) noexcept
|
||||
{
|
||||
if (this == &device)
|
||||
return *this;
|
||||
|
||||
BaseAudioDevice::operator=(device);
|
||||
|
||||
hdl = device.hdl;
|
||||
client = device.client;
|
||||
playbackClient = device.playbackClient;
|
||||
captureClient = device.captureClient;
|
||||
|
||||
device.hdl = nullptr;
|
||||
device.client = nullptr;
|
||||
device.playbackClient = nullptr;
|
||||
device.captureClient = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AudioDevice& AudioDevice::operator=(const AudioDevice& device)
|
||||
{
|
||||
if (this == &device)
|
||||
return *this;
|
||||
|
||||
BaseAudioDevice::operator=(device);
|
||||
|
||||
hdl = nullptr;
|
||||
client = nullptr;
|
||||
playbackClient = nullptr;
|
||||
captureClient = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void AudioDevice::Release()
|
||||
{
|
||||
if (captureClient)
|
||||
{
|
||||
captureClient->Release();
|
||||
captureClient = nullptr;
|
||||
}
|
||||
|
||||
if (playbackClient)
|
||||
{
|
||||
playbackClient->Release();
|
||||
playbackClient = nullptr;
|
||||
}
|
||||
|
||||
if (hdl)
|
||||
{
|
||||
HRESULT r = client->Stop();
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to stop audio with error #" + Str_8::FromNum(r) + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
hdl->Release();
|
||||
hdl = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDevice::OpenStream()
|
||||
{
|
||||
if (streaming)
|
||||
return;
|
||||
|
||||
HRESULT r = hdl->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&client);
|
||||
if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 0, "Failed to create audio client with error #" + Str_8::FromNum(r) + ".");
|
||||
|
||||
WAVEFORMATEXTENSIBLE* format;
|
||||
client->GetMixFormat((WAVEFORMATEX**)&format);
|
||||
|
||||
if (bitDepth)
|
||||
{
|
||||
if (dataType == DataType::FLOAT)
|
||||
format->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
else
|
||||
format->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
|
||||
format->Format.wBitsPerSample = bitDepth;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (format->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
|
||||
dataType = DataType::FLOAT;
|
||||
else
|
||||
dataType = FromAudioBitDepth(format->Format.wBitsPerSample);
|
||||
|
||||
bitDepth = format->Format.wBitsPerSample;
|
||||
}
|
||||
|
||||
if (sampleRate)
|
||||
format->Format.nSamplesPerSec = sampleRate;
|
||||
else
|
||||
sampleRate = format->Format.nSamplesPerSec;
|
||||
|
||||
if (channels)
|
||||
format->Format.nChannels = channels;
|
||||
else
|
||||
channels = format->Format.nChannels;
|
||||
|
||||
format->Format.nBlockAlign = bitDepth / 8 * channels;
|
||||
format->Format.nAvgBytesPerSec = format->Format.nBlockAlign * sampleRate;
|
||||
|
||||
WAVEFORMATEX* match = {};
|
||||
r = client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX*)format, &match);
|
||||
if (FAILED(r))
|
||||
{
|
||||
if (r == AUDCLNT_E_UNSUPPORTED_FORMAT)
|
||||
{
|
||||
LWE_LOG_INT("Error", 1,
|
||||
"The audio device, \"" + GetName_8() + "\" doesn't support the format {" +
|
||||
Str_8::FromNum(format->Format.wBitsPerSample) + "-bit, " +
|
||||
Str_8::FromNum(format->Format.nSamplesPerSec) + "Hz, " +
|
||||
Str_8::FromNum(format->Format.nChannels) + " Channels}."
|
||||
);
|
||||
}
|
||||
else
|
||||
LWE_LOG_INT("Error", 2, "Failed to retrieve supported audio format with error #" + Str_8::FromNum(r) + ".");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
LWE_LOG_INT("Error", 3,
|
||||
"The audio device, \"" + GetName_8() + "\" doesn't support the format {" +
|
||||
Str_8::FromNum(format->Format.wBitsPerSample) + "-bit, " +
|
||||
Str_8::FromNum(format->Format.nSamplesPerSec) + "Hz, " +
|
||||
Str_8::FromNum(format->Format.nChannels) + " Channels}. Closest format, {" +
|
||||
Str_8::FromNum(match->wBitsPerSample) + "-bit Max, " +
|
||||
Str_8::FromNum(match->nSamplesPerSec) + "Hz Max, " +
|
||||
Str_8::FromNum(match->nChannels) + " Channels Min}."
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
r = client->Initialize(
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
0,
|
||||
latency * 10,
|
||||
period * 10,
|
||||
(WAVEFORMATEX*)format,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (r == AUDCLNT_E_ALREADY_INITIALIZED)
|
||||
{
|
||||
LWE_LOG_INT("Error", 5, "Audio client is already initialized.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_WRONG_ENDPOINT_TYPE)
|
||||
{
|
||||
LWE_LOG_INT("Error", 6, "The AUDCLNT_STREAMFLAGS_LOOPBACK flag is set but the endpoint device is a capture device, not a rendering device.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_BUFFER_SIZE_ERROR)
|
||||
{
|
||||
LWE_LOG_INT("Error", 7, "Indicates that the buffer duration value requested by an exclusive-mode client is out of range. The requested duration value for pull mode must not be greater than 5000 milliseconds; for push mode the duration value must not be greater than 2 seconds.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_CPUUSAGE_EXCEEDED)
|
||||
{
|
||||
LWE_LOG_INT("Error", 8, "The audio endpoint device has been unplugged, or the audio hardware or associated hardware resources have been reconfigured, disabled, removed, or otherwise made unavailable for use.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_DEVICE_IN_USE)
|
||||
{
|
||||
LWE_LOG_INT("Error", 9, "The endpoint device is already in use. Either the device is being used in exclusive mode, or the device is being used in shared mode and the caller asked to use the device in exclusive mode.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_ENDPOINT_CREATE_FAILED)
|
||||
{
|
||||
LWE_LOG_INT("Error", 10, "The method failed to create the audio endpoint for the render or the capture device. This can occur if the audio endpoint device has been unplugged, or the audio hardware or associated hardware resources have been reconfigured, disabled, removed, or otherwise made unavailable for use.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_INVALID_DEVICE_PERIOD)
|
||||
{
|
||||
LWE_LOG_INT("Error", 11, "Indicates that the device period requested by an exclusive-mode client is greater than 5000 milliseconds.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_UNSUPPORTED_FORMAT)
|
||||
{
|
||||
LWE_LOG_INT("Error", 12, "The audio engine (shared mode) or audio endpoint device (exclusive mode) does not support the specified format.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
|
||||
{
|
||||
LWE_LOG_INT("Error", 13, "The caller is requesting exclusive-mode use of the endpoint device, but the user has disabled exclusive-mode use of the device.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL)
|
||||
{
|
||||
LWE_LOG_INT("Error", 14, "The AUDCLNT_STREAMFLAGS_EVENTCALLBACK flag is set but parameters hnsBufferDuration and hnsPeriodicity are not equal.");
|
||||
return;
|
||||
}
|
||||
else if (r == AUDCLNT_E_SERVICE_NOT_RUNNING)
|
||||
{
|
||||
LWE_LOG_INT("Error", 15, "The Windows audio service is not running.");
|
||||
return;
|
||||
}
|
||||
else if (r == E_POINTER)
|
||||
{
|
||||
LWE_LOG_INT("Error", 16, "Parameter pFormat is NULL.");
|
||||
return;
|
||||
}
|
||||
else if (r == E_INVALIDARG)
|
||||
{
|
||||
LWE_LOG_INT("Error", 17, "Parameter pFormat points to an invalid format description; or the AUDCLNT_STREAMFLAGS_LOOPBACK flag is set but ShareMode is not equal to AUDCLNT_SHAREMODE_SHARED; or the AUDCLNT_STREAMFLAGS_CROSSPROCESS flag is set but ShareMode is equal to AUDCLNT_SHAREMODE_EXCLUSIVE.\n"
|
||||
"A prior call to SetClientProperties was made with an invalid category for audio/render streams.");
|
||||
return;
|
||||
}
|
||||
else if (r == E_OUTOFMEMORY)
|
||||
{
|
||||
LWE_LOG_INT("Error", 18, "Out of memory.");
|
||||
return;
|
||||
}
|
||||
else if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 19, "Failed to initialize audio stream with error #" + Str_8::FromNum(r) + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == AudioDeviceType::OUTPUT)
|
||||
{
|
||||
r = client->GetService(__uuidof(IAudioRenderClient), (void**)&playbackClient);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 27, "Failed to retrieve audio render client with error #" + Str_8::FromNum(r) + ".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (type == AudioDeviceType::INPUT)
|
||||
{
|
||||
r = client->GetService(__uuidof(IAudioCaptureClient), (void**)&captureClient);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 28, "Failed to retrieve audio capture client with error #" + Str_8::FromNum(r) + ".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
r = client->GetBufferSize((UINT32*)&maxFrames);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 29, "Failed to retrieve buffer size with error #" + Str_8::FromNum(r) + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
r = client->Start();
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 30, "Failed to start audio with error #" + Str_8::FromNum(r) + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
streaming = true;
|
||||
}
|
||||
|
||||
void AudioDevice::CloseStream()
|
||||
{
|
||||
if (!streaming)
|
||||
return;
|
||||
|
||||
if (playbackClient)
|
||||
{
|
||||
playbackClient->Release();
|
||||
playbackClient = nullptr;
|
||||
}
|
||||
|
||||
if (captureClient)
|
||||
{
|
||||
captureClient->Release();
|
||||
captureClient = nullptr;
|
||||
}
|
||||
|
||||
if (hdl)
|
||||
{
|
||||
HRESULT r = client->Stop();
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to stop audio with error #" + Str_8::FromNum(r) + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
client->Release();
|
||||
client = nullptr;
|
||||
}
|
||||
|
||||
streaming = false;
|
||||
}
|
||||
|
||||
UInt_64 AudioDevice::GetAvailFrames() const
|
||||
{
|
||||
if (!IsValid() || !streaming)
|
||||
return 0;
|
||||
|
||||
UInt_32 sampleSize = 0;
|
||||
|
||||
if (playbackClient)
|
||||
{
|
||||
HRESULT r = client->GetCurrentPadding(&sampleSize);
|
||||
if (r == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
LWE_LOG_INT("Error", 0, "The audio device has been invalidated.");
|
||||
else if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 1, "Failed to retrieve samples padding with error #" + Str_8::FromNum(r) + ".");
|
||||
|
||||
sampleSize = maxFrames - sampleSize;
|
||||
}
|
||||
else if (captureClient)
|
||||
{
|
||||
HRESULT r = captureClient->GetNextPacketSize(&sampleSize);
|
||||
if (r == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
LWE_LOG_INT("Error", 0, "The audio device has been invalidated.");
|
||||
else if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 1, "Failed to retrieve samples size with error #" + Str_8::FromNum(r) + ".");
|
||||
}
|
||||
|
||||
return sampleSize;
|
||||
}
|
||||
|
||||
Byte* AudioDevice::Map(UInt_64* offset, UInt_64* frames)
|
||||
{
|
||||
Byte* buffer = nullptr;
|
||||
|
||||
if (!IsValid())
|
||||
return buffer;
|
||||
|
||||
HRESULT r = client->GetCurrentPadding((UINT32*)&offset);
|
||||
if (r == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
LWE_LOG_INT("Error", 0, "The audio device has been invalidated.");
|
||||
else if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 1, "Failed to retrieve samples padding with error #" + Str_8::FromNum(r) + ".");
|
||||
|
||||
if (playbackClient)
|
||||
{
|
||||
r = playbackClient->GetBuffer(*frames, &buffer);
|
||||
if (r == AUDCLNT_E_BUFFER_TOO_LARGE)
|
||||
LWE_LOG_INT("Error", 0, "The given frame size, " + Str_8::FromNum(*frames) + ", must be less than or equal to, " + Str_8::FromNum(maxFrames - *offset) + ".");
|
||||
else if (r == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
LWE_LOG_INT("Error", 1, "The audio device has been invalidated.");
|
||||
else if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 2, "Failed to retrieve buffer with error #" + Str_8::FromNum(r) + ".");
|
||||
}
|
||||
else if (captureClient)
|
||||
{
|
||||
UInt_32 flags = 0;
|
||||
|
||||
r = captureClient->GetBuffer(&buffer, (UINT32*)frames, (DWORD*)&flags, nullptr, nullptr);
|
||||
if (r == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
LWE_LOG_INT("Error", 1, "The audio device has been invalidated.");
|
||||
else if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 2, "Failed to retrieve buffer with error #" + Str_8::FromNum(r) + ".");
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void AudioDevice::UnMap(const UInt_64 offset, const UInt_64 frames)
|
||||
{
|
||||
if (!IsValid() || !streaming)
|
||||
return;
|
||||
|
||||
if (playbackClient)
|
||||
{
|
||||
HRESULT r = playbackClient->ReleaseBuffer(frames, 0);
|
||||
if (r == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
LWE_LOG_INT("Error", 0, "The audio device has been invalidated.");
|
||||
else if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 1, "Failed to release buffer with error #" + Str_8::FromNum(r) + ".");
|
||||
}
|
||||
else if (captureClient)
|
||||
{
|
||||
HRESULT r = captureClient->ReleaseBuffer(frames);
|
||||
if (r == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
LWE_LOG_INT("Error", 0, "The audio device has been invalidated.");
|
||||
else if (FAILED(r))
|
||||
LWE_LOG_INT("Error", 1, "Failed to release buffer with error #" + Str_8::FromNum(r) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
Str_32 AudioDevice::GetInterfaceName_32() const
|
||||
{
|
||||
return UTF::To_32(GetInterfaceName_16());
|
||||
}
|
||||
|
||||
Str_16 AudioDevice::GetInterfaceName_16() const
|
||||
{
|
||||
IPropertyStore* pProps = nullptr;
|
||||
HRESULT r = hdl->OpenPropertyStore(STGM_READ, &pProps);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to retrieve interface name with error #" + Str_8::FromNum(r) + ".");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PROPVARIANT varName = {};
|
||||
PropVariantInit(&varName);
|
||||
|
||||
pProps->GetValue(PKEY_DeviceInterface_FriendlyName, &varName);
|
||||
|
||||
Str_16 name((wchar_t*)varName.pwszVal);
|
||||
|
||||
PropVariantClear(&varName);
|
||||
pProps->Release();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
Str_8 AudioDevice::GetInterfaceName_8() const
|
||||
{
|
||||
return UTF::To_8(GetInterfaceName_16());
|
||||
}
|
||||
|
||||
Str_32 AudioDevice::GetName_32() const
|
||||
{
|
||||
return UTF::To_32(GetName_16());
|
||||
}
|
||||
|
||||
Str_16 AudioDevice::GetName_16() const
|
||||
{
|
||||
IPropertyStore* pProps = nullptr;
|
||||
HRESULT r = hdl->OpenPropertyStore(STGM_READ, &pProps);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to retrieve name with error #" + Str_8::FromNum(r) + ".");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PROPVARIANT varName = {};
|
||||
PropVariantInit(&varName);
|
||||
|
||||
pProps->GetValue(PKEY_Device_FriendlyName, &varName);
|
||||
|
||||
Str_16 name((wchar_t*)varName.pwszVal);
|
||||
|
||||
PropVariantClear(&varName);
|
||||
pProps->Release();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
Str_8 AudioDevice::GetName_8() const
|
||||
{
|
||||
return UTF::To_8(GetName_16());
|
||||
}
|
||||
|
||||
bool AudioDevice::IsValid() const
|
||||
{
|
||||
return hdl;
|
||||
}
|
||||
|
||||
AudioDevice AudioDevice::GetDefault(const AudioDeviceType type)
|
||||
{
|
||||
AudioDevice result;
|
||||
|
||||
HRESULT r = CoInitialize(nullptr);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to initialize COM with error #" + Str_8::FromNum(r) + ".");
|
||||
return result;
|
||||
}
|
||||
|
||||
IMMDeviceEnumerator* enumerator = nullptr;
|
||||
r = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&enumerator);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 1, "Failed to initialize WASAPI with error #" + Str_8::FromNum(r) + ".");
|
||||
return result;
|
||||
}
|
||||
|
||||
IMMDevice* deviceHdl = nullptr;
|
||||
r = enumerator->GetDefaultAudioEndpoint((EDataFlow)type, eConsole, &deviceHdl);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 2, "Failed to retrieve default audio output device with error #" + Str_8::FromNum(r) + ".");
|
||||
return result;
|
||||
}
|
||||
|
||||
result.hdl = deviceHdl;
|
||||
result.type = type;
|
||||
|
||||
enumerator->Release();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Array<AudioDevice> AudioDevice::Get(const AudioDeviceType type, const AudioDeviceState state)
|
||||
{
|
||||
Array<AudioDevice> devices;
|
||||
|
||||
HRESULT r = CoInitialize(nullptr);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 0, "Failed to initialize COM with error #" + Str_8::FromNum(r) + ".");
|
||||
return devices;
|
||||
}
|
||||
|
||||
IMMDeviceEnumerator* enumerator = nullptr;
|
||||
r = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&enumerator);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 1, "Failed to initialize WASAPI with error #" + Str_8::FromNum(r) + ".");
|
||||
return devices;
|
||||
}
|
||||
|
||||
IMMDeviceCollection* collection = nullptr;
|
||||
r = enumerator->EnumAudioEndpoints((EDataFlow)type, (DWORD)state, &collection);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 2, "Failed to retrieve audio device collection with error #" + Str_8::FromNum(r) + ".");
|
||||
return devices;
|
||||
}
|
||||
|
||||
UInt_32 count = 0;
|
||||
r = collection->GetCount((UINT*)&count);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 3, "Failed to retrieve audio device count with error #" + Str_8::FromNum(r) + ".");
|
||||
return devices;
|
||||
}
|
||||
|
||||
devices.Resize(count);
|
||||
for (UInt_32 i = 0; i < count; ++i)
|
||||
{
|
||||
IMMDevice* deviceHdl = nullptr;
|
||||
r = collection->Item(i, &deviceHdl);
|
||||
if (FAILED(r))
|
||||
{
|
||||
LWE_LOG_INT("Error", 4, "Failed to retrieve audio device with error #" + Str_8::FromNum(r) + ".");
|
||||
return devices;
|
||||
}
|
||||
|
||||
devices[i].hdl = deviceHdl;
|
||||
devices[i].type = type;
|
||||
}
|
||||
|
||||
enumerator->Release();
|
||||
|
||||
return devices;
|
||||
}
|
||||
}
|
171
src/IO/Audio/BaseAudioDevice.cpp
Normal file
171
src/IO/Audio/BaseAudioDevice.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "../../../include/IO/Audio/BaseAudioDevice.h"
|
||||
|
||||
namespace lwe
|
||||
{
|
||||
BaseAudioDevice::BaseAudioDevice()
|
||||
: type(AudioDeviceType::ALL), dataType(DataType::SINT_8), bitDepth(0), sampleRate(0), channels(0),
|
||||
period(20000), latency(150000), maxFrames(0), streaming(false)
|
||||
{
|
||||
}
|
||||
|
||||
BaseAudioDevice::BaseAudioDevice(const BaseAudioDevice& device)
|
||||
: type(device.type), dataType(device.dataType), bitDepth(device.bitDepth), sampleRate(device.sampleRate),
|
||||
channels(device.channels), period(device.period), latency(device.latency), maxFrames(0), streaming(false)
|
||||
{
|
||||
}
|
||||
|
||||
BaseAudioDevice& BaseAudioDevice::operator=(const BaseAudioDevice& device)
|
||||
{
|
||||
if (this == &device)
|
||||
return *this;
|
||||
|
||||
type = device.type;
|
||||
dataType = device.dataType;
|
||||
bitDepth = device.bitDepth;
|
||||
sampleRate = device.sampleRate;
|
||||
channels = device.channels;
|
||||
period = device.period;
|
||||
latency = device.latency;
|
||||
maxFrames = device.maxFrames;
|
||||
streaming = false;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BaseAudioDevice::Release()
|
||||
{
|
||||
}
|
||||
|
||||
void BaseAudioDevice::OpenStream()
|
||||
{
|
||||
}
|
||||
|
||||
void BaseAudioDevice::CloseStream()
|
||||
{
|
||||
}
|
||||
|
||||
UInt_64 BaseAudioDevice::GetAvailFrames() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Byte* BaseAudioDevice::Map(UInt_64* offset, UInt_64* frames)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BaseAudioDevice::UnMap(const UInt_64 offset, const UInt_64 frames)
|
||||
{
|
||||
}
|
||||
|
||||
AudioDeviceType BaseAudioDevice::GetType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
void BaseAudioDevice::SetDataType(const DataType newDataType)
|
||||
{
|
||||
if (streaming)
|
||||
return;
|
||||
|
||||
dataType = newDataType;
|
||||
bitDepth = ToBitDepth(newDataType);
|
||||
}
|
||||
|
||||
DataType BaseAudioDevice::GetDataType() const
|
||||
{
|
||||
return dataType;
|
||||
}
|
||||
|
||||
UInt_8 BaseAudioDevice::GetByteDepth() const
|
||||
{
|
||||
return bitDepth / 8;
|
||||
}
|
||||
|
||||
UInt_16 BaseAudioDevice::GetBitDepth() const
|
||||
{
|
||||
return bitDepth;
|
||||
}
|
||||
|
||||
void BaseAudioDevice::SetSampleRate(const UInt_32 newSampleRate)
|
||||
{
|
||||
if (streaming)
|
||||
return;
|
||||
|
||||
sampleRate = newSampleRate;
|
||||
}
|
||||
|
||||
UInt_32 BaseAudioDevice::GetSampleRate() const
|
||||
{
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
void BaseAudioDevice::SetChannels(const UInt_32 newChannels)
|
||||
{
|
||||
if (streaming)
|
||||
return;
|
||||
|
||||
channels = newChannels;
|
||||
}
|
||||
|
||||
UInt_16 BaseAudioDevice::GetChannels() const
|
||||
{
|
||||
return channels;
|
||||
}
|
||||
|
||||
UInt_32 BaseAudioDevice::GetFrameSize() const
|
||||
{
|
||||
return bitDepth / 8 * channels;
|
||||
}
|
||||
|
||||
void BaseAudioDevice::SetPeriod(const UInt_32 newPeriod)
|
||||
{
|
||||
if (streaming)
|
||||
return;
|
||||
|
||||
period = newPeriod;
|
||||
}
|
||||
|
||||
UInt_32 BaseAudioDevice::GetPeriod() const
|
||||
{
|
||||
return period;
|
||||
}
|
||||
|
||||
void BaseAudioDevice::SetLatency(const UInt_32 newLatency)
|
||||
{
|
||||
if (streaming)
|
||||
return;
|
||||
|
||||
latency = newLatency;
|
||||
}
|
||||
|
||||
UInt_32 BaseAudioDevice::GetLatency() const
|
||||
{
|
||||
return latency;
|
||||
}
|
||||
|
||||
UInt_64 BaseAudioDevice::GetMaxFrames() const
|
||||
{
|
||||
return maxFrames;
|
||||
}
|
||||
|
||||
bool BaseAudioDevice::IsStreaming() const
|
||||
{
|
||||
return streaming;
|
||||
}
|
||||
|
||||
bool BaseAudioDevice::IsValid() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BaseAudioDevice BaseAudioDevice::GetDefault(const AudioDeviceType type)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
Array<BaseAudioDevice> BaseAudioDevice::Get(const AudioDeviceType type, const AudioDeviceState state)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user