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