kaliber/src/engine/audio/audio_alsa.cc

205 lines
6.0 KiB
C++

#include "engine/audio/audio_alsa.h"
#include <memory>
#include <alsa/asoundlib.h>
#include "base/log.h"
using namespace base;
namespace eng {
AudioAlsa::AudioAlsa() = default;
AudioAlsa::~AudioAlsa() = default;
bool AudioAlsa::Initialize() {
LOG << "Initializing audio system.";
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 << "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 << "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 << "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 << "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 << "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 << "Cannot set sample rate. Error: " << snd_strerror(err);
break;
}
if ((err = snd_pcm_hw_params_set_channels(device_, hw_params, 2)) < 0) {
LOG << "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 << "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 << "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 << "Cannot set parameters. Error: " << snd_strerror(err);
break;
}
if ((err = snd_pcm_prepare(device_)) < 0) {
LOG << "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 << "Alsa Audio:";
LOG << " access: " << snd_pcm_access_name(access);
LOG << " format: " << snd_pcm_format_name(format);
LOG << " channel count: " << num_channels;
LOG << " sample rate: " << sample_rate;
LOG << " period size: " << period_size;
LOG << " period time: " << period_time;
LOG << " periods: " << periods;
LOG << " 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 AudioAlsa::Shutdown() {
LOG << "Shutting down audio system.";
TerminateAudioThread();
snd_pcm_drop(device_);
snd_pcm_close(device_);
}
void AudioAlsa::Suspend() {
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
suspend_audio_thread_.store(true, std::memory_order_relaxed);
}
void AudioAlsa::Resume() {
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
suspend_audio_thread_.store(false, std::memory_order_relaxed);
}
int AudioAlsa::GetHardwareSampleRate() {
return sample_rate_;
}
void AudioAlsa::StartAudioThread() {
LOG << "Starting audio thread.";
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
audio_thread_ = std::thread(&AudioAlsa::AudioThreadMain, this);
}
void AudioAlsa::TerminateAudioThread() {
if (terminate_audio_thread_.load(std::memory_order_relaxed))
return;
LOG << "Terminating audio thread";
// Notify worker thread and wait for it to terminate.
terminate_audio_thread_.store(true, std::memory_order_relaxed);
suspend_audio_thread_.store(true, std::memory_order_relaxed);
audio_thread_.join();
}
void AudioAlsa::AudioThreadMain() {
size_t num_frames = period_size_ / (num_channels_ * sizeof(float));
auto buffer = std::make_unique<float[]>(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.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
RenderAudio(buffer.get(), num_frames);
while (snd_pcm_writei(device_, buffer.get(), num_frames) < 0) {
snd_pcm_prepare(device_);
DLOG << "Audio buffer underrun!";
}
}
}
} // namespace eng