First commit.
This commit is contained in:
383
src/io/audio/AudioDevice_ALSA.cpp
Normal file
383
src/io/audio/AudioDevice_ALSA.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
#include "ehs/io/audio/AudioDevice_ALSA.h"
|
||||
#include "ehs/EHS.h"
|
||||
#include "ehs/Log.h"
|
||||
|
||||
namespace ehs
|
||||
{
|
||||
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)
|
||||
{
|
||||
EHS_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:
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_LOG_INT("Error", 1, "Failed to set channels.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleRate)
|
||||
{
|
||||
if (snd_pcm_hw_params_set_rate_near(hdl, params, &sampleRate, nullptr) < 0)
|
||||
{
|
||||
EHS_LOG_INT("Error", 2, "Failed to set sample rate.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_set_period_time_near(hdl, params, &period, nullptr) < 0)
|
||||
{
|
||||
EHS_LOG_INT("Error", 3, "Failed to set period.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_set_buffer_time_near(hdl, params, &latency, nullptr) < 0)
|
||||
{
|
||||
EHS_LOG_INT("Error", 4, "Failed to set latency.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params(hdl, params) < 0)
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_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:
|
||||
{
|
||||
EHS_LOG_INT("Error", 7, "Format unsupported.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!channels)
|
||||
{
|
||||
if (snd_pcm_hw_params_get_channels(params, &channels) < 0)
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_LOG_INT("Error", 9, "Failed to retrieve sample rate.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_get_buffer_size(params, &maxFrames) < 0)
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_LOG_INT("Error", 11, "Failed to retrieve software parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_sw_params_set_silence_threshold(hdl, swParams, maxFrames) < 0)
|
||||
{
|
||||
EHS_LOG_INT("Error", 12, "Failed to set silence threshold.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_sw_params_set_silence_size(hdl, swParams, maxFrames) < 0)
|
||||
{
|
||||
EHS_LOG_INT("Error", 12, "Failed to set silence size.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_sw_params(hdl, swParams) < 0)
|
||||
{
|
||||
EHS_LOG_INT("Error", 13, "Failed to set software parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (snd_pcm_prepare(hdl) < 0)
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_LOG_INT("Warning", 0, "Buffer overrun/underrun occurred.");
|
||||
if (snd_pcm_recover(hdl, -EPIPE, 0) < 0)
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_LOG_INT("Warning", 3, "Buffer overrun/underrun occurred.");
|
||||
if (snd_pcm_recover(hdl, -EPIPE, 1) < 0)
|
||||
{
|
||||
EHS_LOG_INT("Error", 4, "Failed to recover from buffer overrun/underrun.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return GetAvailFrames();
|
||||
}
|
||||
else
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_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)
|
||||
{
|
||||
EHS_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
|
||||
{
|
||||
EHS_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 {};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user