1 /*
2 * Copyright 2003-2021 The Music Player Daemon Project
3 * http://www.musicpd.org
4 *
5 * This program 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 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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 along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "SndioOutputPlugin.hxx"
21 #include "mixer/MixerList.hxx"
22 #include "mixer/Listener.hxx"
23 #include "util/Domain.hxx"
24 #include "Log.hxx"
25
26 #include <sndio.h>
27
28 #include <stdexcept>
29
30 #ifndef SIO_DEVANY
31 /* this macro is missing in libroar-dev 1.0~beta2-3 (Debian Wheezy) */
32 #define SIO_DEVANY "default"
33 #endif
34
35 static constexpr unsigned MPD_SNDIO_BUFFER_TIME_MS = 250;
36
37 static constexpr Domain sndio_output_domain("sndio_output");
38
SndioOutput(const ConfigBlock & block)39 SndioOutput::SndioOutput(const ConfigBlock &block)
40 :AudioOutput(0),
41 device(block.GetBlockValue("device", SIO_DEVANY)),
42 buffer_time(block.GetBlockValue("buffer_time",
43 MPD_SNDIO_BUFFER_TIME_MS)),
44 raw_volume(SIO_MAXVOL)
45 {
46 }
47
48 static void
VolumeCallback(void * arg,unsigned int volume)49 VolumeCallback(void *arg, unsigned int volume) {
50 ((SndioOutput *)arg)->VolumeChanged(volume);
51 }
52
53 AudioOutput *
Create(EventLoop &,const ConfigBlock & block)54 SndioOutput::Create(EventLoop &, const ConfigBlock &block) {
55 return new SndioOutput(block);
56 }
57
58 static bool
sndio_test_default_device()59 sndio_test_default_device()
60 {
61 auto *hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
62 if (!hdl) {
63 LogError(sndio_output_domain,
64 "Error opening default sndio device");
65 return false;
66 }
67
68 sio_close(hdl);
69 return true;
70 }
71
72 void
Open(AudioFormat & audio_format)73 SndioOutput::Open(AudioFormat &audio_format)
74 {
75 struct sio_par par;
76 unsigned bits, rate, chans;
77
78 hdl = sio_open(device, SIO_PLAY, 0);
79 if (!hdl)
80 throw std::runtime_error("Failed to open default sndio device");
81
82 switch (audio_format.format) {
83 case SampleFormat::S16:
84 bits = 16;
85 break;
86 case SampleFormat::S24_P32:
87 bits = 24;
88 break;
89 case SampleFormat::S32:
90 bits = 32;
91 break;
92 default:
93 audio_format.format = SampleFormat::S16;
94 bits = 16;
95 break;
96 }
97
98 rate = audio_format.sample_rate;
99 chans = audio_format.channels;
100
101 sio_initpar(&par);
102 par.bits = bits;
103 par.rate = rate;
104 par.pchan = chans;
105 par.sig = 1;
106 par.le = SIO_LE_NATIVE;
107 par.appbufsz = rate * buffer_time / 1000;
108
109 if (!sio_setpar(hdl, &par) ||
110 !sio_getpar(hdl, &par)) {
111 sio_close(hdl);
112 throw std::runtime_error("Failed to set/get audio params");
113 }
114
115 if (par.bits != bits ||
116 par.rate < rate * 995 / 1000 ||
117 par.rate > rate * 1005 / 1000 ||
118 par.pchan != chans ||
119 par.sig != 1 ||
120 par.le != SIO_LE_NATIVE) {
121 sio_close(hdl);
122 throw std::runtime_error("Requested audio params cannot be satisfied");
123 }
124
125 // Set volume after opening fresh audio stream which does
126 // know nothing about previous audio streams.
127 sio_setvol(hdl, raw_volume);
128 // sio_onvol returns 0 if no volume knob is available.
129 // This is the case on raw audio devices rather than
130 // the sndiod audio server.
131 if (sio_onvol(hdl, VolumeCallback, this) == 0)
132 raw_volume = -1;
133
134 if (!sio_start(hdl)) {
135 sio_close(hdl);
136 throw std::runtime_error("Failed to start audio device");
137 }
138 }
139
140 void
Close()141 SndioOutput::Close() noexcept
142 {
143 sio_close(hdl);
144 }
145
146 size_t
Play(const void * chunk,size_t size)147 SndioOutput::Play(const void *chunk, size_t size)
148 {
149 size_t n;
150
151 n = sio_write(hdl, chunk, size);
152 if (n == 0 && sio_eof(hdl) != 0)
153 throw std::runtime_error("sndio write failed");
154 return n;
155 }
156
157 void
SetVolume(unsigned int volume)158 SndioOutput::SetVolume(unsigned int volume)
159 {
160 sio_setvol(hdl, volume * SIO_MAXVOL / 100);
161 }
162
163 static inline unsigned int
RawToPercent(int raw_volume)164 RawToPercent(int raw_volume) {
165 return raw_volume < 0 ? 100 : raw_volume * 100 / SIO_MAXVOL;
166 }
167
168 void
VolumeChanged(int _raw_volume)169 SndioOutput::VolumeChanged(int _raw_volume) {
170 if (raw_volume >= 0 && listener != nullptr && mixer != nullptr) {
171 raw_volume = _raw_volume;
172 listener->OnMixerVolumeChanged(*mixer,
173 RawToPercent(raw_volume));
174 }
175 }
176
177 unsigned int
GetVolume()178 SndioOutput::GetVolume() {
179 return RawToPercent(raw_volume);
180 }
181
182 void
RegisterMixerListener(Mixer * _mixer,MixerListener * _listener)183 SndioOutput::RegisterMixerListener(Mixer *_mixer, MixerListener *_listener) {
184 mixer = _mixer;
185 listener = _listener;
186 }
187
188 constexpr struct AudioOutputPlugin sndio_output_plugin = {
189 "sndio",
190 sndio_test_default_device,
191 SndioOutput::Create,
192 &sndio_mixer_plugin,
193 };
194