1 /*
2  * Strawberry Music Player
3  * Copyright 2017-2021, Jonas Kvinge <jonas@jkvinge.net>
4  *
5  * Strawberry is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Strawberry is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include "config.h"
21 
22 #include <cstdio>
23 #include <cerrno>
24 #include <alsa/asoundlib.h>
25 #include <boost/scope_exit.hpp>
26 
27 #include <QList>
28 #include <QVariant>
29 #include <QString>
30 #include <QtDebug>
31 
32 #include <core/logging.h>
33 
34 #include "devicefinder.h"
35 #include "alsadevicefinder.h"
36 
AlsaDeviceFinder()37 AlsaDeviceFinder::AlsaDeviceFinder() : DeviceFinder("alsa", { "alsa","alsasink" }) {}
38 
ListDevices()39 QList<DeviceFinder::Device> AlsaDeviceFinder::ListDevices() {
40 
41   QList<Device> ret;
42 
43   snd_pcm_stream_name(SND_PCM_STREAM_PLAYBACK);
44 
45   int card = -1;
46   snd_ctl_card_info_t *cardinfo = nullptr;
47   snd_ctl_card_info_alloca(&cardinfo);
48   while (true) {
49 
50     int result = snd_card_next(&card);
51     if (result < 0) {
52       qLog(Error) << "Unable to get soundcard:" << snd_strerror(result);
53       break;
54     }
55     if (card < 0) break;
56 
57     char str[32];
58     snprintf(str, sizeof(str) - 1, "hw:%d", card);
59 
60     snd_ctl_t *handle = nullptr;
61     result = snd_ctl_open(&handle, str, 0);
62     if (result < 0) {
63       qLog(Error) << "Unable to open soundcard" << card << ":" << snd_strerror(result);
64       continue;
65     }
66     BOOST_SCOPE_EXIT(&handle) { snd_ctl_close(handle); }  // clazy:exclude=rule-of-three NOLINT(google-explicit-constructor)
67     BOOST_SCOPE_EXIT_END
68 
69     result = snd_ctl_card_info(handle, cardinfo);
70     if (result < 0) {
71       qLog(Error) << "Control hardware failure for card" << card << ":" << snd_strerror(result);
72       continue;
73     }
74 
75     int dev = -1;
76     snd_pcm_info_t *pcminfo = nullptr;
77     snd_pcm_info_alloca(&pcminfo);
78     while (true) {
79 
80       result = snd_ctl_pcm_next_device(handle, &dev);
81       if (result < 0) {
82         qLog(Error) << "Unable to get PCM for card" << card << ":" << snd_strerror(result);
83         continue;
84       }
85       if (dev < 0) break;
86 
87       snd_pcm_info_set_device(pcminfo, dev);
88       snd_pcm_info_set_subdevice(pcminfo, 0);
89       snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
90 
91       result = snd_ctl_pcm_info(handle, pcminfo);
92       if (result < 0) {
93         if (result != -ENOENT) qLog(Error) << "Unable to get control digital audio info for card" << card << ":" << snd_strerror(result);
94         continue;
95       }
96 
97       Device device;
98       device.description = QString("%1 %2").arg(snd_ctl_card_info_get_name(cardinfo), snd_pcm_info_get_name(pcminfo));
99       device.iconname = GuessIconName(device.description);
100       device.card = card;
101       device.device = dev;
102 
103       device.value = QString("hw:%1,%2").arg(card).arg(dev);
104       ret.append(device);
105       device.value = QString("plughw:%1,%2").arg(card).arg(dev);
106       ret.append(device);
107 
108     }
109   }
110 
111   snd_config_update_free_global();
112 
113   return ret;
114 }
115