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