#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::Get(const AudioDeviceType type, const AudioDeviceState state) { return {}; } }