Path: blob/master/drivers/alsa/audio_driver_alsa.cpp
10279 views
/**************************************************************************/1/* audio_driver_alsa.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "audio_driver_alsa.h"3132#ifdef ALSA_ENABLED3334#include "core/config/project_settings.h"35#include "core/os/os.h"3637#include <cerrno>3839#if defined(PULSEAUDIO_ENABLED) && defined(SOWRAP_ENABLED)40extern "C" {41extern int initialize_pulse(int verbose);42}43#endif4445Error AudioDriverALSA::init_output_device() {46mix_rate = _get_configured_mix_rate();4748speaker_mode = SPEAKER_MODE_STEREO;49channels = 2;5051// If there is a specified output device check that it is really present52if (output_device_name != "Default") {53PackedStringArray list = get_output_device_list();54if (!list.has(output_device_name)) {55output_device_name = "Default";56new_output_device = "Default";57}58}5960int status;61snd_pcm_hw_params_t *hwparams;62snd_pcm_sw_params_t *swparams;6364#define CHECK_FAIL(m_cond) \65if (m_cond) { \66fprintf(stderr, "ALSA ERR: %s\n", snd_strerror(status)); \67if (pcm_handle) { \68snd_pcm_close(pcm_handle); \69pcm_handle = nullptr; \70} \71ERR_FAIL_COND_V(m_cond, ERR_CANT_OPEN); \72}7374//todo, add75//6 chans - "plug:surround51"76//4 chans - "plug:surround40";7778if (output_device_name == "Default") {79status = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);80} else {81String device = output_device_name;82int pos = device.find_char(';');83if (pos != -1) {84device = device.substr(0, pos);85}86status = snd_pcm_open(&pcm_handle, device.utf8().get_data(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);87}8889ERR_FAIL_COND_V(status < 0, ERR_CANT_OPEN);9091snd_pcm_hw_params_alloca(&hwparams);9293status = snd_pcm_hw_params_any(pcm_handle, hwparams);94CHECK_FAIL(status < 0);9596status = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);97CHECK_FAIL(status < 0);9899//not interested in anything else100status = snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE);101CHECK_FAIL(status < 0);102103//todo: support 4 and 6104status = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2);105CHECK_FAIL(status < 0);106107status = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &mix_rate, nullptr);108CHECK_FAIL(status < 0);109110// In ALSA the period size seems to be the one that will determine the actual latency111// Ref: https://www.alsa-project.org/main/index.php/FramesPeriods112unsigned int periods = 2;113int latency = Engine::get_singleton()->get_audio_output_latency();114buffer_frames = closest_power_of_2(latency * mix_rate / 1000);115buffer_size = buffer_frames * periods;116period_size = buffer_frames;117118// set buffer size from project settings119status = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size);120CHECK_FAIL(status < 0);121122status = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &period_size, nullptr);123CHECK_FAIL(status < 0);124125print_verbose("Audio buffer frames: " + itos(period_size) + " calculated latency: " + itos(period_size * 1000 / mix_rate) + "ms");126127status = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &periods, nullptr);128CHECK_FAIL(status < 0);129130status = snd_pcm_hw_params(pcm_handle, hwparams);131CHECK_FAIL(status < 0);132133//snd_pcm_hw_params_free(&hwparams);134135snd_pcm_sw_params_alloca(&swparams);136137status = snd_pcm_sw_params_current(pcm_handle, swparams);138CHECK_FAIL(status < 0);139140status = snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, period_size);141CHECK_FAIL(status < 0);142143status = snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);144CHECK_FAIL(status < 0);145146status = snd_pcm_sw_params(pcm_handle, swparams);147CHECK_FAIL(status < 0);148149samples_in.resize(period_size * channels);150samples_out.resize(period_size * channels);151152return OK;153}154155Error AudioDriverALSA::init() {156#ifdef SOWRAP_ENABLED157#ifdef DEBUG_ENABLED158int dylibloader_verbose = 1;159#else160int dylibloader_verbose = 0;161#endif162#ifdef PULSEAUDIO_ENABLED163// On pulse enabled systems Alsa will silently use pulse.164// It doesn't matter if this fails as that likely means there is no pulse165initialize_pulse(dylibloader_verbose);166#endif167168if (initialize_asound(dylibloader_verbose)) {169return ERR_CANT_OPEN;170}171#endif172bool ver_ok = false;173String version = String::utf8(snd_asoundlib_version());174Vector<String> ver_parts = version.split(".");175if (ver_parts.size() >= 2) {176ver_ok = ((ver_parts[0].to_int() == 1 && ver_parts[1].to_int() >= 1)) || (ver_parts[0].to_int() > 1); // 1.1.0177}178print_verbose(vformat("ALSA %s detected.", version));179if (!ver_ok) {180print_verbose("Unsupported ALSA library version!");181return ERR_CANT_OPEN;182}183184active.clear();185exit_thread.clear();186187Error err = init_output_device();188if (err == OK) {189thread.start(AudioDriverALSA::thread_func, this);190}191192return err;193}194195void AudioDriverALSA::thread_func(void *p_udata) {196AudioDriverALSA *ad = static_cast<AudioDriverALSA *>(p_udata);197198while (!ad->exit_thread.is_set()) {199ad->lock();200ad->start_counting_ticks();201202if (!ad->active.is_set()) {203for (uint64_t i = 0; i < ad->period_size * ad->channels; i++) {204ad->samples_out.write[i] = 0;205}206207} else {208ad->audio_server_process(ad->period_size, ad->samples_in.ptrw());209210for (uint64_t i = 0; i < ad->period_size * ad->channels; i++) {211ad->samples_out.write[i] = ad->samples_in[i] >> 16;212}213}214215int todo = ad->period_size;216int total = 0;217218while (todo && !ad->exit_thread.is_set()) {219int16_t *src = (int16_t *)ad->samples_out.ptr();220int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo);221222if (wrote > 0) {223total += wrote;224todo -= wrote;225} else if (wrote == -EAGAIN) {226ad->stop_counting_ticks();227ad->unlock();228229OS::get_singleton()->delay_usec(1000);230231ad->lock();232ad->start_counting_ticks();233} else {234wrote = snd_pcm_recover(ad->pcm_handle, wrote, 0);235if (wrote < 0) {236ERR_PRINT("ALSA: Failed and can't recover: " + String(snd_strerror(wrote)));237ad->active.clear();238ad->exit_thread.set();239}240}241}242243// User selected a new output device, finish the current one so we'll init the new device.244if (ad->output_device_name != ad->new_output_device) {245ad->output_device_name = ad->new_output_device;246ad->finish_output_device();247248Error err = ad->init_output_device();249if (err != OK) {250ERR_PRINT("ALSA: init_output_device error");251ad->output_device_name = "Default";252ad->new_output_device = "Default";253254err = ad->init_output_device();255if (err != OK) {256ad->active.clear();257ad->exit_thread.set();258}259}260}261262ad->stop_counting_ticks();263ad->unlock();264}265}266267void AudioDriverALSA::start() {268active.set();269}270271int AudioDriverALSA::get_mix_rate() const {272return mix_rate;273}274275AudioDriver::SpeakerMode AudioDriverALSA::get_speaker_mode() const {276return speaker_mode;277}278279PackedStringArray AudioDriverALSA::get_output_device_list() {280PackedStringArray list;281282list.push_back("Default");283284void **hints;285286if (snd_device_name_hint(-1, "pcm", &hints) < 0) {287return list;288}289290for (void **n = hints; *n != nullptr; n++) {291char *name = snd_device_name_get_hint(*n, "NAME");292char *desc = snd_device_name_get_hint(*n, "DESC");293294if (name != nullptr && !strncmp(name, "plughw", 6)) {295if (desc) {296list.push_back(String::utf8(name) + ";" + String::utf8(desc));297} else {298list.push_back(String::utf8(name));299}300}301302if (desc != nullptr) {303free(desc);304}305if (name != nullptr) {306free(name);307}308}309snd_device_name_free_hint(hints);310311return list;312}313314String AudioDriverALSA::get_output_device() {315return output_device_name;316}317318void AudioDriverALSA::set_output_device(const String &p_name) {319lock();320new_output_device = p_name;321unlock();322}323324void AudioDriverALSA::lock() {325mutex.lock();326}327328void AudioDriverALSA::unlock() {329mutex.unlock();330}331332void AudioDriverALSA::finish_output_device() {333if (pcm_handle) {334snd_pcm_close(pcm_handle);335pcm_handle = nullptr;336}337}338339void AudioDriverALSA::finish() {340exit_thread.set();341if (thread.is_started()) {342thread.wait_to_finish();343}344345finish_output_device();346}347348#endif // ALSA_ENABLED349350351