1 /*
2 * KMix -- KDE's full featured mini mixer
3 *
4 * Copyright (C) 1996-2000 Christian Esken
5 * esken@kde.org
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this program; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include "mixer_oss.h"
23 #include "core/kmixdevicemanager.h"
24 #include "core/mixer.h"
25
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <qplatformdefs.h>
29 #include <sys/ioctl.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33
34 // Since we're guaranteed an OSS setup here, let's make life easier
35 #if !defined(__NetBSD__) && !defined(__OpenBSD__)
36 #include <sys/soundcard.h>
37 #else
38 #include <soundcard.h>
39 #endif
40
41 #include <QTimer>
42 /*
43 I am using a fixed MAX_MIXDEVS #define here.
44 People might argue, that I should rather use the SOUND_MIXER_NRDEVICES
45 #define used by OSS. But using this #define is not good, because it is
46 evaluated during compile time. Compiling on one platform and running
47 on another with another version of OSS with a different value of
48 SOUND_MIXER_NRDEVICES is very bad. Because of this, usage of
49 SOUND_MIXER_NRDEVICES should be discouraged.
50
51 The #define below is only there for internal reasons.
52 In other words: Don't play around with this value
53
54 FreeBSD, on the other hand, has had SOUND_MIXER_NRDEVICES set to 25
55 for at least the last 13 years; it doesn't change. In addition,
56 deviceNameDevfs() overruns the alphabet and ends up looking for
57 silly mixer names like /dev/sound/mixer\ . All the devices above
58 SOUND_MIXER_NRDEVICES simply don't work.
59 */
60 #if defined(__FreeBSD__) && defined(SOUND_MIXER_NRDEVICES)
61 #define MAX_MIXDEVS SOUND_MIXER_NRDEVICES
62 #else
63 #define MAX_MIXDEVS 32
64 #endif
65
66 // clang-format off
67 const char* MixerDevNames[MAX_MIXDEVS]={
68 I18N_NOOP("Volume"), I18N_NOOP("Bass"), I18N_NOOP("Treble"),
69 I18N_NOOP("Synth"), I18N_NOOP("Pcm"), I18N_NOOP("Speaker"),
70 I18N_NOOP("Line"), I18N_NOOP("Microphone"), I18N_NOOP("CD"),
71 I18N_NOOP("Mix"), I18N_NOOP("Pcm2"), I18N_NOOP("RecMon"),
72 I18N_NOOP("IGain"), I18N_NOOP("OGain"), I18N_NOOP("Line1"),
73 I18N_NOOP("Line2"), I18N_NOOP("Line3"), I18N_NOOP("Digital1"),
74 I18N_NOOP("Digital2"), I18N_NOOP("Digital3"), I18N_NOOP("PhoneIn"),
75 I18N_NOOP("PhoneOut"), I18N_NOOP("Video"), I18N_NOOP("Radio"),
76 I18N_NOOP("Monitor")
77 #if MAX_MIXDEVS > 25
78 ,
79 I18N_NOOP("3D-depth"), I18N_NOOP("3D-center"),
80 I18N_NOOP("unknown"), I18N_NOOP("unknown"), I18N_NOOP("unknown"),
81 I18N_NOOP("unknown") , I18N_NOOP("unused")
82 #endif
83 };
84
85 const MixDevice::ChannelType MixerChannelTypes[MAX_MIXDEVS] = {
86 MixDevice::VOLUME, MixDevice::BASS, MixDevice::TREBLE,
87 MixDevice::MIDI, MixDevice::AUDIO, MixDevice::SPEAKER,
88 MixDevice::EXTERNAL, MixDevice::MICROPHONE, MixDevice::CD,
89 MixDevice::VOLUME, MixDevice::AUDIO, MixDevice::RECMONITOR,
90 MixDevice::VOLUME, MixDevice::RECMONITOR, MixDevice::EXTERNAL,
91 MixDevice::EXTERNAL, MixDevice::EXTERNAL, MixDevice::DIGITAL,
92 MixDevice::DIGITAL, MixDevice::DIGITAL, MixDevice::EXTERNAL,
93 MixDevice::EXTERNAL, MixDevice::VIDEO, MixDevice::EXTERNAL,
94 MixDevice::EXTERNAL
95 #if MAX_MIXDEVS > 25
96 ,
97 MixDevice::VOLUME, MixDevice::VOLUME,
98 MixDevice::UNKNOWN, MixDevice::UNKNOWN, MixDevice::UNKNOWN,
99 MixDevice::UNKNOWN, MixDevice::UNKNOWN
100 #endif
101 };
102 // clang-format on
103
OSS_getMixer(Mixer * mixer,int device)104 Mixer_Backend *OSS_getMixer(Mixer *mixer, int device)
105 {
106 Mixer_Backend *l_mixer;
107 l_mixer = new Mixer_OSS(mixer, device);
108 return l_mixer;
109 }
110
Mixer_OSS(Mixer * mixer,int device)111 Mixer_OSS::Mixer_OSS(Mixer *mixer, int device)
112 : Mixer_Backend(mixer, device)
113 {
114 if (device == -1) {
115 m_devnum = 0;
116 }
117 m_fd = -1; // point to an invalid FD
118 }
119
~Mixer_OSS()120 Mixer_OSS::~Mixer_OSS()
121 {
122 close();
123 }
124
open()125 int Mixer_OSS::open()
126 {
127 QString finalDeviceName;
128 finalDeviceName = deviceName(m_devnum);
129 qCDebug(KMIX_LOG) << "OSS open() " << finalDeviceName;
130 if ((m_fd = QT_OPEN(finalDeviceName.toLatin1().data(), O_RDWR)) < 0) {
131 if (errno == EACCES)
132 return Mixer::ERR_PERM;
133 else {
134 finalDeviceName = deviceNameDevfs(m_devnum);
135 if ((m_fd = QT_OPEN(finalDeviceName.toLatin1().data(), O_RDWR)) < 0) {
136 if (errno == EACCES)
137 return Mixer::ERR_PERM;
138 else
139 return Mixer::ERR_OPEN;
140 }
141 }
142 }
143
144 _udi = KMixDeviceManager::instance()->getUDI_OSS(finalDeviceName);
145 if (_udi.isEmpty()) {
146 QString msg("No UDI found for '");
147 msg += finalDeviceName;
148 msg += "'. Hotplugging not possible";
149 qCDebug(KMIX_LOG) << msg;
150 }
151 int devmask, recmask, i_recsrc, stereodevs;
152 // Mixer is open. Now define properties
153 if (ioctl(m_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
154 return Mixer::ERR_READ;
155 if (ioctl(m_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1)
156 return Mixer::ERR_READ;
157 if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
158 return Mixer::ERR_READ;
159 if (ioctl(m_fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1)
160 return Mixer::ERR_READ;
161
162 int idx = 0;
163 while (devmask && idx < MAX_MIXDEVS) {
164 if (devmask & (1 << idx)) // device active?
165 {
166 Volume playbackVol(100, 0, true, false);
167 playbackVol.addVolumeChannel(VolumeChannel(Volume::LEFT));
168 if (stereodevs & (1 << idx))
169 playbackVol.addVolumeChannel(VolumeChannel(Volume::RIGHT));
170
171 QString id;
172 id.setNum(idx);
173 MixDevice *md = new MixDevice(_mixer, id, i18n(MixerDevNames[idx]), MixerChannelTypes[idx]);
174 md->addPlaybackVolume(playbackVol);
175
176 // Tutorial: Howto add a simple capture switch
177 if (recmask & (1 << idx)) {
178 // can be captured => add capture volume, with no capture volume
179 Volume captureVol(100, 0, true, true);
180 md->addCaptureVolume(captureVol);
181 }
182
183 m_mixDevices.append(md->addToPool());
184 }
185 idx++;
186 }
187
188 #if defined(SOUND_MIXER_INFO)
189 struct mixer_info l_mix_info;
190 if (ioctl(m_fd, SOUND_MIXER_INFO, &l_mix_info) != -1) {
191 registerCard(l_mix_info.name);
192 } else
193 #endif
194 {
195 registerCard("OSS Audio Mixer");
196 }
197
198 m_isOpen = true;
199 return 0;
200 }
201
close()202 int Mixer_OSS::close()
203 {
204 _pollingTimer->stop();
205 m_isOpen = false;
206 int l_i_ret = ::close(m_fd);
207 closeCommon();
208 return l_i_ret;
209 }
210
deviceName(int devnum)211 QString Mixer_OSS::deviceName(int devnum)
212 {
213 switch (devnum) {
214 case 0:
215 return QString("/dev/mixer");
216 break;
217
218 default:
219 QString devname("/dev/mixer%1");
220 return devname.arg(devnum);
221 }
222 }
223
deviceNameDevfs(int devnum)224 QString Mixer_OSS::deviceNameDevfs(int devnum)
225 {
226 switch (devnum) {
227 case 0:
228 return QString("/dev/sound/mixer");
229 break;
230
231 default:
232 QString devname("/dev/sound/mixer");
233 devname += ('0' + devnum);
234 return devname;
235 }
236 }
237
errorText(int mixer_error)238 QString Mixer_OSS::errorText(int mixer_error)
239 {
240 QString l_s_errmsg;
241 switch (mixer_error) {
242 case Mixer::ERR_PERM:
243 l_s_errmsg = i18n("kmix: You do not have permission to access the mixer device.\n"
244 "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access.");
245 break;
246 case Mixer::ERR_OPEN:
247 l_s_errmsg = i18n("kmix: Mixer cannot be found.\n"
248 "Please check that the soundcard is installed and the\n"
249 "soundcard driver is loaded.\n"
250 "On Linux you might need to use 'insmod' to load the driver.\n"
251 "Use 'soundon' when using commercial OSS.");
252 break;
253 default:
254 l_s_errmsg = Mixer_Backend::errorText(mixer_error);
255 break;
256 }
257 return l_s_errmsg;
258 }
259
print_recsrc(int recsrc)260 void print_recsrc(int recsrc)
261 {
262 int i;
263
264 QString msg;
265 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
266 if ((1 << i) & recsrc)
267 msg += '+';
268 else
269 msg += '.';
270 }
271 qCDebug(KMIX_LOG) << msg;
272 }
273
setRecsrcToOSS(const QString & id,bool on)274 int Mixer_OSS::setRecsrcToOSS(const QString &id, bool on)
275 {
276 int i_recsrc; //, oldrecsrc;
277 int devnum = id2num(id);
278 if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) {
279 errormsg(Mixer::ERR_READ);
280 return Mixer::ERR_READ;
281 }
282
283 // oldrecsrc = i_recsrc = on ?
284 // (i_recsrc | (1 << devnum )) :
285 // (i_recsrc & ~(1 << devnum ));
286
287 // Change status of record source(s)
288 if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1) {
289 errormsg(Mixer::ERR_WRITE);
290 // don't return here. It is much better to re-read the capture switch states.
291 }
292
293 /* The following if {} patch was submitted by Tim McCormick <tim@pcbsd.org>. */
294 /* Comment (cesken): This patch fixes an issue with mutual exclusive recording sources.
295 Actually the kernel soundcard driver *could* "do the right thing" by examining the change
296 (old-recsrc XOR new-recsrc), and knowing which sources are mutual exclusive.
297 The OSS v3 API docs indicate that the behaviour is undefined for this case, and it is not
298 clearly documented how and whether SOUND_MIXER_CAP_EXCL_INPUT is evaluated in the OSS driver.
299 Evaluating that in the application (KMix) could help, but the patch will work independent
300 on whether SOUND_MIXER_CAP_EXCL_INPUT is set or not.
301
302 In any case this patch is a superb workaround for a shortcoming of the OSS v3 API.
303 */
304 // If the record source is supposed to be on, but wasn't set, explicitly
305 // set the record source. Not all cards support multiple record sources.
306 // As a result, we also need to do the read & write again.
307 if (((i_recsrc & (1 << devnum)) == 0) && on) {
308 // Setting the new device failed => Try to enable it *exclusively*
309
310 // oldrecsrc = i_recsrc = 1 << devnum;
311
312 if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1)
313 errormsg(Mixer::ERR_WRITE);
314 if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
315 errormsg(Mixer::ERR_READ);
316 }
317
318 // Re-read status of record source(s). Just in case the hardware/driver has
319 // some limitation (like exclusive switches)
320 int recsrcMask;
321 if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1)
322 errormsg(Mixer::ERR_READ);
323 else {
324 for (int i = 0; i < m_mixDevices.count(); i++) {
325 shared_ptr<MixDevice> md = m_mixDevices[i];
326 bool isRecsrc = ((recsrcMask & (1 << devnum)) != 0);
327 md->setRecSource(isRecsrc);
328 } // for all controls
329 } // reading newrecsrcmask is OK
330
331 return Mixer::OK;
332 }
333
id2num(const QString & id)334 int Mixer_OSS::id2num(const QString &id)
335 {
336 return id.toInt();
337 }
338
339 /**
340 * Prints out a translated error text for the given error number on stderr
341 */
errormsg(int mixer_error)342 void Mixer_OSS::errormsg(int mixer_error)
343 {
344 QString l_s_errText;
345 l_s_errText = errorText(mixer_error);
346 qCCritical(KMIX_LOG) << l_s_errText << "\n";
347 }
348
readVolumeFromHW(const QString & id,shared_ptr<MixDevice> md)349 int Mixer_OSS::readVolumeFromHW(const QString &id, shared_ptr<MixDevice> md)
350 {
351 int ret = 0;
352
353 // --- VOLUME ---
354 Volume &vol = md->playbackVolume();
355 int devnum = id2num(id);
356
357 bool controlChanged = false;
358
359 if (vol.hasVolume()) {
360 int volume;
361 if (ioctl(m_fd, MIXER_READ(devnum), &volume) == -1) {
362 /* Oops, can't read mixer */
363 errormsg(Mixer::ERR_READ);
364 ret = Mixer::ERR_READ;
365 } else {
366
367 int volLeft = (volume & 0x7f);
368 int volRight = ((volume >> 8) & 0x7f);
369 //
370 // if ( md->id() == "0" )
371 // qCDebug(KMIX_LOG) << md->id() << ": " << "volLeft=" << volLeft << ", volRight" << volRight;
372
373 bool isMuted = volLeft == 0 && (vol.count() < 2 || volRight == 0); // muted is "left and right muted" or "left muted when mono"
374 md->setMuted(isMuted);
375 if (!isMuted) {
376 // Muted is represented in OSS by value 0. We don't want to write the value 0 as a volume,
377 // but instead we only mark it muted (see setMuted() above).
378
379 for (const VolumeChannel &vc : qAsConst(vol.getVolumes()))
380 {
381 long volOld = 0;
382 long volNew = 0;
383 switch (vc.chid) {
384 case Volume::LEFT:
385 volOld = vol.getVolume(Volume::LEFT);
386 volNew = volLeft;
387 vol.setVolume(Volume::LEFT, volNew);
388 break;
389 case Volume::RIGHT:
390 volOld = vol.getVolume(Volume::RIGHT);
391 volNew = volRight;
392 vol.setVolume(Volume::RIGHT, volNew);
393 break;
394 default:
395 // not supported by OSSv3
396 break;
397 }
398
399 if (volOld != volNew) {
400 controlChanged = true;
401 // if ( md->id() == "0" ) qCDebug(KMIX_LOG) << "changed";
402 }
403 } // for
404 } // muted
405 }
406 }
407
408 // --- RECORD SWITCH ---
409 // Volume& captureVol = md->captureVolume();
410 int recsrcMask;
411 if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1)
412 ret = Mixer::ERR_READ;
413 else {
414 bool isRecsrcOld = md->isRecSource();
415 // test if device bit is set in record bit mask
416 bool isRecsrc = ((recsrcMask & (1 << devnum)) != 0);
417 md->setRecSource(isRecsrc);
418 if (isRecsrcOld != isRecsrc)
419 controlChanged = true;
420 }
421
422 if (ret == 0) {
423 if (controlChanged) {
424 // qCDebug(KMIX_LOG) << "FINE! " << ret;
425 return Mixer::OK;
426 } else {
427 return Mixer::OK_UNCHANGED;
428 }
429 } else {
430 // qCDebug(KMIX_LOG) << "SHIT! " << ret;
431 return ret;
432 }
433 }
434
writeVolumeToHW(const QString & id,shared_ptr<MixDevice> md)435 int Mixer_OSS::writeVolumeToHW(const QString &id, shared_ptr<MixDevice> md)
436 {
437 int volume;
438 int devnum = id2num(id);
439
440 Volume &vol = md->playbackVolume();
441 if (md->isMuted())
442 volume = 0;
443 else {
444 if (vol.getVolumes().count() > 1)
445 volume = (vol.getVolume(Volume::LEFT) + (vol.getVolume(Volume::RIGHT) << 8));
446 else
447 volume = vol.getVolume(Volume::LEFT);
448 }
449
450 if (ioctl(m_fd, MIXER_WRITE(devnum), &volume) == -1)
451 return Mixer::ERR_WRITE;
452
453 setRecsrcToOSS(id, md->isRecSource());
454
455 return 0;
456 }
457
OSS_getDriverName()458 QString OSS_getDriverName()
459 {
460 return "OSS";
461 }
462
getDriverName()463 QString Mixer_OSS::getDriverName()
464 {
465 return "OSS";
466 }
467