#include "engine/audio/audio_device_alsa.h" #include #include #include "base/log.h" #include "base/timer.h" using namespace base; namespace eng { AudioDeviceAlsa::AudioDeviceAlsa(AudioDevice::Delegate* delegate) : delegate_(delegate) {} AudioDeviceAlsa::~AudioDeviceAlsa() { LOG(0) << "Shutting down audio."; TerminateAudioThread(); snd_pcm_drop(device_); snd_pcm_close(device_); } bool AudioDeviceAlsa::Initialize() { LOG(0) << "Initializing audio."; int err; // Contains information about the hardware. snd_pcm_hw_params_t* hw_params; // TODO: "default" is usualy PulseAudio. Select a device with "plughw" for // direct hardware device with software format conversion. if ((err = snd_pcm_open(&device_, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { LOG(0) << "Cannot open audio device. Error: " << snd_strerror(err); return false; } do { // Allocate the snd_pcm_hw_params_t structure on the stack. snd_pcm_hw_params_alloca(&hw_params); // Init hw_params with full configuration space. if ((err = snd_pcm_hw_params_any(device_, hw_params)) < 0) { LOG(0) << "Cannot initialize hardware parameter structure. Error: " << snd_strerror(err); break; } if ((err = snd_pcm_hw_params_set_access( device_, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { LOG(0) << "Cannot set access type. Error: " << snd_strerror(err); break; } if ((err = snd_pcm_hw_params_set_format(device_, hw_params, SND_PCM_FORMAT_FLOAT)) < 0) { LOG(0) << "Cannot set sample format. Error: " << snd_strerror(err); break; } // Disable software resampler. if ((err = snd_pcm_hw_params_set_rate_resample(device_, hw_params, 0)) < 0) { LOG(0) << "Cannot disbale software resampler. Error: " << snd_strerror(err); break; } unsigned sample_rate = 48000; if ((err = snd_pcm_hw_params_set_rate_near(device_, hw_params, &sample_rate, 0)) < 0) { LOG(0) << "Cannot set sample rate. Error: " << snd_strerror(err); break; } if ((err = snd_pcm_hw_params_set_channels(device_, hw_params, 2)) < 0) { LOG(0) << "Cannot set channel count. Error: " << snd_strerror(err); break; } // Set period time to 4 ms. The latency will be 12 ms for 3 perods. unsigned period_time = 4000; if ((err = snd_pcm_hw_params_set_period_time_near(device_, hw_params, &period_time, 0)) < 0) { LOG(0) << "Cannot set periods. Error: " << snd_strerror(err); break; } unsigned periods = 3; if ((err = snd_pcm_hw_params_set_periods_near(device_, hw_params, &periods, 0)) < 0) { LOG(0) << "Cannot set periods. Error: " << snd_strerror(err); break; } // Apply HW parameter settings to PCM device and prepare device. if ((err = snd_pcm_hw_params(device_, hw_params)) < 0) { LOG(0) << "Cannot set parameters. Error: " << snd_strerror(err); break; } if ((err = snd_pcm_prepare(device_)) < 0) { LOG(0) << "Cannot prepare audio interface for use. Error: " << snd_strerror(err); break; } snd_pcm_access_t access; unsigned num_channels; snd_pcm_format_t format; snd_pcm_uframes_t period_size; snd_pcm_uframes_t buffer_size; snd_pcm_hw_params_get_access(hw_params, &access); snd_pcm_hw_params_get_channels(hw_params, &num_channels); snd_pcm_hw_params_get_format(hw_params, &format); snd_pcm_hw_params_get_period_size(hw_params, &period_size, nullptr); snd_pcm_hw_params_get_period_time(hw_params, &period_time, nullptr); snd_pcm_hw_params_get_periods(hw_params, &periods, nullptr); snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size); LOG(0) << "Alsa Audio:"; LOG(0) << " access: " << snd_pcm_access_name(access); LOG(0) << " format: " << snd_pcm_format_name(format); LOG(0) << " channel count: " << num_channels; LOG(0) << " sample rate: " << sample_rate; LOG(0) << " period size: " << period_size; LOG(0) << " period time: " << period_time; LOG(0) << " periods: " << periods; LOG(0) << " buffer_size: " << buffer_size; num_channels_ = num_channels; sample_rate_ = sample_rate; period_size_ = period_size; StartAudioThread(); return true; } while (false); snd_pcm_close(device_); return false; } void AudioDeviceAlsa::Suspend() { suspend_audio_thread_.store(true, std::memory_order_relaxed); } void AudioDeviceAlsa::Resume() { suspend_audio_thread_.store(false, std::memory_order_relaxed); } size_t AudioDeviceAlsa::GetHardwareSampleRate() { return sample_rate_; } void AudioDeviceAlsa::StartAudioThread() { DCHECK(!audio_thread_.joinable()); LOG(0) << "Starting audio thread."; terminate_audio_thread_.store(false, std::memory_order_relaxed); suspend_audio_thread_.store(false, std::memory_order_relaxed); audio_thread_ = std::thread(&AudioDeviceAlsa::AudioThreadMain, this); } void AudioDeviceAlsa::TerminateAudioThread() { if (!audio_thread_.joinable()) return; LOG(0) << "Terminating audio thread"; terminate_audio_thread_.store(true, std::memory_order_relaxed); suspend_audio_thread_.store(true, std::memory_order_relaxed); audio_thread_.join(); } void AudioDeviceAlsa::AudioThreadMain() { DCHECK(delegate_); size_t num_frames = period_size_ / (num_channels_ * sizeof(float)); auto buffer = std::make_unique(num_frames * 2); for (;;) { while (suspend_audio_thread_.load(std::memory_order_relaxed)) { if (terminate_audio_thread_.load(std::memory_order_relaxed)) return; // Avoid busy-looping. Sleep(1); } delegate_->RenderAudio(buffer.get(), num_frames); while (snd_pcm_writei(device_, buffer.get(), num_frames) < 0) { snd_pcm_prepare(device_); DLOG(0) << "Alsa buffer underrun!"; } } } } // namespace eng