LCOV - code coverage report
Current view: top level - muse/framework/audio/driver/platform/lin - alsaaudiodriver.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 120 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "alsaaudiodriver.h"
      23              : 
      24              : #define ALSA_PCM_NEW_HW_PARAMS_API
      25              : #include <alsa/asoundlib.h>
      26              : 
      27              : #include <fcntl.h>
      28              : #include <unistd.h>
      29              : #include <string.h>
      30              : #include <math.h>
      31              : #include <pthread.h>
      32              : 
      33              : #include "translation.h"
      34              : #include "log.h"
      35              : #include "runtime.h"
      36              : 
      37              : using namespace muse;
      38              : using namespace muse::audio;
      39              : 
      40              : namespace {
      41              : struct ALSAData
      42              : {
      43              :     float* buffer = nullptr;
      44              :     snd_pcm_t* alsaDeviceHandle = nullptr;
      45              :     unsigned long samples = 0;
      46              :     int channels = 0;
      47              :     bool audioProcessingDone = false;
      48              :     pthread_t threadHandle = 0;
      49              :     IAudioDriver::Callback callback;
      50              :     void* userdata = nullptr;
      51              : };
      52              : 
      53              : static ALSAData* s_alsaData{ nullptr };
      54              : static muse::audio::IAudioDriver::Spec s_format;
      55              : 
      56            0 : static void* alsaThread(void* aParam)
      57              : {
      58            0 :     muse::runtime::setThreadName("audio_driver");
      59            0 :     ALSAData* data = static_cast<ALSAData*>(aParam);
      60              : 
      61            0 :     int ret = snd_pcm_wait(data->alsaDeviceHandle, 1000);
      62            0 :     IF_ASSERT_FAILED(ret > 0) {
      63            0 :         return nullptr;
      64              :     }
      65              : 
      66            0 :     while (!data->audioProcessingDone)
      67              :     {
      68            0 :         uint8_t* stream = (uint8_t*)data->buffer;
      69            0 :         int len = data->samples * data->channels * sizeof(float);
      70              : 
      71            0 :         data->callback(stream, len);
      72              : 
      73            0 :         snd_pcm_sframes_t pcm = snd_pcm_writei(data->alsaDeviceHandle, data->buffer, data->samples);
      74            0 :         if (pcm != -EPIPE) {
      75              :         } else {
      76            0 :             snd_pcm_prepare(data->alsaDeviceHandle);
      77              :         }
      78              :     }
      79              : 
      80            0 :     LOGI() << "exit";
      81            0 :     return nullptr;
      82              : }
      83              : 
      84            0 : static void alsaCleanup()
      85              : {
      86            0 :     if (!s_alsaData) {
      87            0 :         return;
      88              :     }
      89              : 
      90            0 :     s_alsaData->audioProcessingDone = true;
      91            0 :     if (s_alsaData->threadHandle) {
      92            0 :         pthread_join(s_alsaData->threadHandle, nullptr);
      93              :     }
      94            0 :     if (nullptr != s_alsaData->alsaDeviceHandle) {
      95            0 :         snd_pcm_drain(s_alsaData->alsaDeviceHandle);
      96            0 :         snd_pcm_close(s_alsaData->alsaDeviceHandle);
      97              :     }
      98              : 
      99            0 :     if (nullptr != s_alsaData->buffer) {
     100            0 :         delete[] s_alsaData->buffer;
     101              :     }
     102              : 
     103            0 :     delete s_alsaData;
     104            0 :     s_alsaData = nullptr;
     105              : }
     106              : }
     107              : 
     108            0 : AlsaAudioDriver::AlsaAudioDriver()
     109              : {
     110            0 : }
     111              : 
     112            0 : AlsaAudioDriver::~AlsaAudioDriver()
     113              : {
     114            0 :     alsaCleanup();
     115            0 : }
     116              : 
     117            0 : void AlsaAudioDriver::init()
     118              : {
     119            0 :     m_devicesListener.startWithCallback([this]() {
     120            0 :         return availableOutputDevices();
     121              :     });
     122              : 
     123            0 :     m_devicesListener.devicesChanged().onNotify(this, [this]() {
     124            0 :         m_availableOutputDevicesChanged.notify();
     125            0 :     });
     126            0 : }
     127              : 
     128            0 : std::string AlsaAudioDriver::name() const
     129              : {
     130            0 :     return "ALSA";
     131              : }
     132              : 
     133            0 : AudioDeviceID AlsaAudioDriver::defaultDevice() const
     134              : {
     135            0 :     return DEFAULT_DEVICE_ID;
     136              : }
     137              : 
     138            0 : bool AlsaAudioDriver::open(const Spec& spec, Spec* activeSpec)
     139              : {
     140            0 :     IF_ASSERT_FAILED(!spec.deviceId.empty()) {
     141            0 :         return false;
     142              :     }
     143              : 
     144            0 :     s_alsaData = new ALSAData();
     145            0 :     s_alsaData->samples = spec.output.samplesPerChannel;
     146            0 :     s_alsaData->channels = spec.output.audioChannelCount;
     147            0 :     s_alsaData->callback = spec.callback;
     148              : 
     149              :     snd_pcm_t* handle;
     150            0 :     int rc = snd_pcm_open(&handle, spec.deviceId.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
     151            0 :     if (rc < 0) {
     152            0 :         LOGE() << "Unable to open device: " << spec.deviceId << ", err code: " << rc;
     153            0 :         return false;
     154              :     }
     155              : 
     156            0 :     s_alsaData->alsaDeviceHandle = handle;
     157              : 
     158              :     snd_pcm_hw_params_t* params;
     159            0 :     snd_pcm_hw_params_alloca(&params);
     160            0 :     snd_pcm_hw_params_any(handle, params);
     161              : 
     162            0 :     snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
     163            0 :     snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_FLOAT_LE);
     164            0 :     snd_pcm_hw_params_set_channels(handle, params, spec.output.audioChannelCount);
     165              : 
     166            0 :     unsigned int aSamplerate = spec.output.sampleRate;
     167            0 :     unsigned int val = aSamplerate;
     168            0 :     int dir = 0;
     169            0 :     rc = snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
     170            0 :     if (rc < 0) {
     171            0 :         LOGE() << "Unable to set sample rate: " << val << ", err code: " << rc;
     172            0 :         return false;
     173              :     }
     174              : 
     175            0 :     rc = snd_pcm_hw_params_set_buffer_size_near(handle, params, &s_alsaData->samples);
     176            0 :     if (rc < 0) {
     177            0 :         LOGE() << "Unable to set buffer size: " << s_alsaData->samples << ", err code: " << rc;
     178              :     }
     179              : 
     180            0 :     rc = snd_pcm_hw_params(handle, params);
     181            0 :     if (rc < 0) {
     182            0 :         LOGE() << "Unable to set params, err code: " << rc;
     183            0 :         return false;
     184              :     }
     185              : 
     186            0 :     snd_pcm_hw_params_get_rate(params, &val, &dir);
     187            0 :     aSamplerate = val;
     188              : 
     189            0 :     s_alsaData->buffer = new float[s_alsaData->samples * s_alsaData->channels];
     190              :     //_alsaData->sampleBuffer = new short[_alsaData->samples * _alsaData->channels];
     191              : 
     192            0 :     s_format = spec;
     193            0 :     s_format.output.sampleRate = aSamplerate;
     194            0 :     m_activeSpecChanged.send(s_format);
     195              : 
     196            0 :     if (activeSpec) {
     197            0 :         *activeSpec = s_format;
     198              :     }
     199              : 
     200            0 :     s_alsaData->threadHandle = 0;
     201            0 :     int ret = pthread_create(&s_alsaData->threadHandle, NULL, alsaThread, (void*)s_alsaData);
     202              : 
     203            0 :     if (0 != ret) {
     204            0 :         LOGE() << "Unable to create audio thread, err code: " << ret;
     205            0 :         return false;
     206              :     }
     207              : 
     208            0 :     LOGI() << "Connected to " << spec.deviceId
     209            0 :            << " with bufferSize " << s_format.output.samplesPerChannel
     210            0 :            << ", sampleRate " << s_format.output.sampleRate
     211            0 :            << ", channels:  " << s_format.output.audioChannelCount;
     212              : 
     213            0 :     return true;
     214              : }
     215              : 
     216            0 : void AlsaAudioDriver::close()
     217              : {
     218            0 :     alsaCleanup();
     219            0 : }
     220              : 
     221            0 : bool AlsaAudioDriver::isOpened() const
     222              : {
     223            0 :     return s_alsaData != nullptr;
     224              : }
     225              : 
     226            0 : const AlsaAudioDriver::Spec& AlsaAudioDriver::activeSpec() const
     227              : {
     228            0 :     return s_format;
     229              : }
     230              : 
     231            0 : async::Channel<IAudioDriver::Spec> AlsaAudioDriver::activeSpecChanged() const
     232              : {
     233            0 :     return m_activeSpecChanged;
     234              : }
     235              : 
     236            0 : AudioDeviceList AlsaAudioDriver::availableOutputDevices() const
     237              : {
     238            0 :     AudioDeviceList devices;
     239            0 :     devices.push_back({ DEFAULT_DEVICE_ID, muse::trc("audio", "System default") });
     240              : 
     241            0 :     return devices;
     242            0 : }
     243              : 
     244            0 : async::Notification AlsaAudioDriver::availableOutputDevicesChanged() const
     245              : {
     246            0 :     return m_availableOutputDevicesChanged;
     247              : }
     248              : 
     249            0 : std::vector<samples_t> AlsaAudioDriver::availableOutputDeviceBufferSizes() const
     250              : {
     251            0 :     std::vector<samples_t> result;
     252              : 
     253            0 :     samples_t n = MAXIMUM_BUFFER_SIZE;
     254            0 :     while (n >= MINIMUM_BUFFER_SIZE) {
     255            0 :         result.push_back(n);
     256            0 :         n /= 2;
     257              :     }
     258              : 
     259            0 :     std::sort(result.begin(), result.end());
     260              : 
     261            0 :     return result;
     262            0 : }
     263              : 
     264            0 : std::vector<sample_rate_t> AlsaAudioDriver::availableOutputDeviceSampleRates() const
     265              : {
     266              :     // ALSA API is not of any help to get sample rates supported by the driver.
     267              :     // (snd_pcm_hw_params_get_rate_[min|max] will return 1 to 384000 Hz)
     268              :     // So just returning a sensible hard-coded list.
     269              :     return {
     270              :         44100,
     271              :         48000,
     272              :         88200,
     273              :         96000,
     274            0 :     };
     275              : }
        

Generated by: LCOV version 2.0-1