mixer_oss.cpp 15 KB
Newer Older
Stephan Kulow's avatar
Stephan Kulow committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 *              KMix -- KDE's full featured mini mixer
 *
 *              Copyright (C) 1996-2000 Christian Esken
 *                        esken@kde.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free
Dirk Mueller's avatar
Dirk Mueller committed
19
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Stephan Kulow's avatar
Stephan Kulow committed
20 21
 */

22
#include "mixer_oss.h"
23
#include "core/kmixdevicemanager.h"
24
#include "core/mixer.h"
25

Stephan Kulow's avatar
Stephan Kulow committed
26
#include <errno.h>
27 28
#include <fcntl.h>
#include <qplatformdefs.h>
Stephan Kulow's avatar
Stephan Kulow committed
29
#include <sys/ioctl.h>
Stephan Kulow's avatar
Stephan Kulow committed
30
#include <sys/stat.h>
31 32
#include <sys/types.h>
#include <unistd.h>
Stephan Kulow's avatar
Stephan Kulow committed
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>
Stephan Kulow's avatar
Stephan Kulow committed
39 40
#endif

Laurent Montel's avatar
Laurent Montel committed
41
#include <QTimer>
42 43 44 45 46 47 48 49 50 51 52
/*
  I am using a fixed MAX_MIXDEVS #define here.
   People might argue, that I should rather use the SOUND_MIXER_NRDEVICES
   #define used by OSS. But using this #define is not good, because it is
   evaluated during compile time. Compiling on one platform and running
   on another with another version of OSS with a different value of
   SOUND_MIXER_NRDEVICES is very bad. Because of this, usage of
   SOUND_MIXER_NRDEVICES should be discouraged.

   The #define below is only there for internal reasons.
   In other words: Don't play around with this value
Adriaan de Groot's avatar
Adriaan de Groot committed
53 54 55 56 57 58

   FreeBSD, on the other hand, has had SOUND_MIXER_NRDEVICES set to 25
   for at least the last 13 years; it doesn't change. In addition,
   deviceNameDevfs() overruns the alphabet and ends up looking for
   silly mixer names like /dev/sound/mixer\ . All the devices above
   SOUND_MIXER_NRDEVICES simply don't work.
59
 */
Adriaan de Groot's avatar
Adriaan de Groot committed
60 61 62
#if defined(__FreeBSD__) && defined(SOUND_MIXER_NRDEVICES)
#define MAX_MIXDEVS SOUND_MIXER_NRDEVICES
#else
63
#define MAX_MIXDEVS 32
Adriaan de Groot's avatar
Adriaan de Groot committed
64
#endif
Stephan Kulow's avatar
Stephan Kulow committed
65

66
// clang-format off
67
const char* MixerDevNames[MAX_MIXDEVS]={
68 69 70 71 72 73 74 75
    I18N_NOOP("Volume"),   I18N_NOOP("Bass"),       I18N_NOOP("Treble"),
    I18N_NOOP("Synth"),    I18N_NOOP("Pcm"),        I18N_NOOP("Speaker"),
    I18N_NOOP("Line"),     I18N_NOOP("Microphone"), I18N_NOOP("CD"),
    I18N_NOOP("Mix"),      I18N_NOOP("Pcm2"),       I18N_NOOP("RecMon"),
    I18N_NOOP("IGain"),    I18N_NOOP("OGain"),      I18N_NOOP("Line1"),
    I18N_NOOP("Line2"),    I18N_NOOP("Line3"),      I18N_NOOP("Digital1"),
    I18N_NOOP("Digital2"), I18N_NOOP("Digital3"),   I18N_NOOP("PhoneIn"),
    I18N_NOOP("PhoneOut"), I18N_NOOP("Video"),      I18N_NOOP("Radio"),
Adriaan de Groot's avatar
Adriaan de Groot committed
76 77 78 79
    I18N_NOOP("Monitor")
#if MAX_MIXDEVS > 25
    ,
    I18N_NOOP("3D-depth"),   I18N_NOOP("3D-center"),
80
    I18N_NOOP("unknown"),  I18N_NOOP("unknown"),    I18N_NOOP("unknown"),
Adriaan de Groot's avatar
Adriaan de Groot committed
81 82 83
    I18N_NOOP("unknown") , I18N_NOOP("unused")
#endif
};
Stephan Kulow's avatar
Stephan Kulow committed
84

85
const MixDevice::ChannelType MixerChannelTypes[MAX_MIXDEVS] = {
86 87 88 89 90 91 92 93
    MixDevice::VOLUME,   MixDevice::BASS,       MixDevice::TREBLE,
    MixDevice::MIDI,     MixDevice::AUDIO,      MixDevice::SPEAKER,
    MixDevice::EXTERNAL, MixDevice::MICROPHONE, MixDevice::CD,
    MixDevice::VOLUME,   MixDevice::AUDIO,      MixDevice::RECMONITOR,
    MixDevice::VOLUME,   MixDevice::RECMONITOR, MixDevice::EXTERNAL,
    MixDevice::EXTERNAL, MixDevice::EXTERNAL,   MixDevice::DIGITAL,
    MixDevice::DIGITAL,  MixDevice::DIGITAL,    MixDevice::EXTERNAL,
    MixDevice::EXTERNAL, MixDevice::VIDEO,      MixDevice::EXTERNAL,
Adriaan de Groot's avatar
Adriaan de Groot committed
94 95 96 97
    MixDevice::EXTERNAL
#if MAX_MIXDEVS > 25
    ,
    MixDevice::VOLUME,   MixDevice::VOLUME,
98
    MixDevice::UNKNOWN,  MixDevice::UNKNOWN,    MixDevice::UNKNOWN,
Adriaan de Groot's avatar
Adriaan de Groot committed
99 100 101
    MixDevice::UNKNOWN,  MixDevice::UNKNOWN
#endif
};
102
// clang-format on
Stephan Kulow's avatar
Stephan Kulow committed
103

104
Mixer_Backend *OSS_getMixer(Mixer *mixer, int device)
Stephan Kulow's avatar
Stephan Kulow committed
105
{
106 107 108
    Mixer_Backend *l_mixer;
    l_mixer = new Mixer_OSS(mixer, device);
    return l_mixer;
Stephan Kulow's avatar
Stephan Kulow committed
109 110
}

111 112
Mixer_OSS::Mixer_OSS(Mixer *mixer, int device)
    : Mixer_Backend(mixer, device)
Stephan Kulow's avatar
Stephan Kulow committed
113
{
114 115 116 117
    if (device == -1) {
        m_devnum = 0;
    }
    m_fd = -1; // point to an invalid FD
Stephan Kulow's avatar
Stephan Kulow committed
118 119
}

120
Mixer_OSS::~Mixer_OSS()
Stephan Kulow's avatar
Stephan Kulow committed
121
{
122
    close();
123
}
Stephan Kulow's avatar
Stephan Kulow committed
124

125 126
int Mixer_OSS::open()
{
127
    QString finalDeviceName;
128 129 130 131 132
    finalDeviceName = deviceName(m_devnum);
    qCDebug(KMIX_LOG) << "OSS open() " << finalDeviceName;
    if ((m_fd = QT_OPEN(finalDeviceName.toLatin1().data(), O_RDWR)) < 0) {
        if (errno == EACCES)
            return Mixer::ERR_PERM;
133
        else {
134 135 136
            finalDeviceName = deviceNameDevfs(m_devnum);
            if ((m_fd = QT_OPEN(finalDeviceName.toLatin1().data(), O_RDWR)) < 0) {
                if (errno == EACCES)
137
                    return Mixer::ERR_PERM;
138 139
                else
                    return Mixer::ERR_OPEN;
140 141
            }
        }
Stephan Kulow's avatar
Stephan Kulow committed
142
    }
143

144
    _udi = KMixDeviceManager::instance()->getUDI_OSS(finalDeviceName);
145
    if (_udi.isEmpty()) {
146 147 148
        QString msg("No UDI found for '");
        msg += finalDeviceName;
        msg += "'. Hotplugging not possible";
149
        qCDebug(KMIX_LOG) << msg;
150
    }
151 152 153
    int devmask, recmask, i_recsrc, stereodevs;
    // Mixer is open. Now define properties
    if (ioctl(m_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
Stephan Kulow's avatar
Stephan Kulow committed
154
        return Mixer::ERR_READ;
155
    if (ioctl(m_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1)
Stephan Kulow's avatar
Stephan Kulow committed
156
        return Mixer::ERR_READ;
157
    if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
Stephan Kulow's avatar
Stephan Kulow committed
158
        return Mixer::ERR_READ;
159
    if (ioctl(m_fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1)
Stephan Kulow's avatar
Stephan Kulow committed
160
        return Mixer::ERR_READ;
Christian Esken's avatar
Christian Esken committed
161

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    int idx = 0;
    while (devmask && idx < MAX_MIXDEVS) {
        if (devmask & (1 << idx)) // device active?
        {
            Volume playbackVol(100, 0, true, false);
            playbackVol.addVolumeChannel(VolumeChannel(Volume::LEFT));
            if (stereodevs & (1 << idx))
                playbackVol.addVolumeChannel(VolumeChannel(Volume::RIGHT));

            QString id;
            id.setNum(idx);
            MixDevice *md = new MixDevice(_mixer, id, i18n(MixerDevNames[idx]), MixerChannelTypes[idx]);
            md->addPlaybackVolume(playbackVol);

            // Tutorial: Howto add a simple capture switch
            if (recmask & (1 << idx)) {
                // can be captured => add capture volume, with no capture volume
                Volume captureVol(100, 0, true, true);
                md->addCaptureVolume(captureVol);
Stephan Kulow's avatar
Stephan Kulow committed
181 182
            }

183
            m_mixDevices.append(md->addToPool());
Stephan Kulow's avatar
Stephan Kulow committed
184
        }
185 186 187 188 189 190 191 192
        idx++;
    }

#if defined(SOUND_MIXER_INFO)
    struct mixer_info l_mix_info;
    if (ioctl(m_fd, SOUND_MIXER_INFO, &l_mix_info) != -1) {
        registerCard(l_mix_info.name);
    } else
Bradley T.  Hughes's avatar
Bradley T. Hughes committed
193
#endif
194 195 196
    {
        registerCard("OSS Audio Mixer");
    }
Stephan Kulow's avatar
Stephan Kulow committed
197

198 199
    m_isOpen = true;
    return 0;
Stephan Kulow's avatar
Stephan Kulow committed
200 201
}

202
int Mixer_OSS::close()
Stephan Kulow's avatar
Stephan Kulow committed
203
{
Christian Esken's avatar
Christian Esken committed
204 205 206
    _pollingTimer->stop();
    m_isOpen = false;
    int l_i_ret = ::close(m_fd);
207
    closeCommon();
Christian Esken's avatar
Christian Esken committed
208
    return l_i_ret;
Stephan Kulow's avatar
Stephan Kulow committed
209 210 211 212
}

QString Mixer_OSS::deviceName(int devnum)
{
213 214 215 216 217 218 219 220 221
    switch (devnum) {
    case 0:
        return QString("/dev/mixer");
        break;

    default:
        QString devname("/dev/mixer%1");
        return devname.arg(devnum);
    }
Stephan Kulow's avatar
Stephan Kulow committed
222 223
}

224 225
QString Mixer_OSS::deviceNameDevfs(int devnum)
{
226 227 228 229 230 231 232 233 234 235
    switch (devnum) {
    case 0:
        return QString("/dev/sound/mixer");
        break;

    default:
        QString devname("/dev/sound/mixer");
        devname += ('0' + devnum);
        return devname;
    }
236 237
}

Stephan Kulow's avatar
Stephan Kulow committed
238 239
QString Mixer_OSS::errorText(int mixer_error)
{
240 241
    QString l_s_errmsg;
    switch (mixer_error) {
242
    case Mixer::ERR_PERM:
243 244 245
        l_s_errmsg = i18n("kmix: You do not have permission to access the mixer device.\n"
                          "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access.");
        break;
246
    case Mixer::ERR_OPEN:
247 248 249 250 251 252
        l_s_errmsg = i18n("kmix: Mixer cannot be found.\n"
                          "Please check that the soundcard is installed and the\n"
                          "soundcard driver is loaded.\n"
                          "On Linux you might need to use 'insmod' to load the driver.\n"
                          "Use 'soundon' when using commercial OSS.");
        break;
Stephan Kulow's avatar
Stephan Kulow committed
253
    default:
254 255
        l_s_errmsg = Mixer_Backend::errorText(mixer_error);
        break;
Stephan Kulow's avatar
Stephan Kulow committed
256
    }
257
    return l_s_errmsg;
Stephan Kulow's avatar
Stephan Kulow committed
258 259
}

Christian Esken's avatar
Christian Esken committed
260 261
void print_recsrc(int recsrc)
{
262 263 264 265 266 267 268 269 270 271
    int i;

    QString msg;
    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
        if ((1 << i) & recsrc)
            msg += '+';
        else
            msg += '.';
    }
    qCDebug(KMIX_LOG) << msg;
Christian Esken's avatar
Christian Esken committed
272
}
Christian Esken's avatar
Christian Esken committed
273

274
int Mixer_OSS::setRecsrcToOSS(const QString &id, bool on)
Stephan Kulow's avatar
Stephan Kulow committed
275
{
Christian Esken's avatar
Christian Esken committed
276
    int i_recsrc; //, oldrecsrc;
Christian Esken's avatar
Christian Esken committed
277
    int devnum = id2num(id);
278
    if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) {
Christian Esken's avatar
Christian Esken committed
279
        errormsg(Mixer::ERR_READ);
Christian Esken's avatar
Christian Esken committed
280 281
        return Mixer::ERR_READ;
    }
Stephan Kulow's avatar
Stephan Kulow committed
282

283 284 285
    //    oldrecsrc = i_recsrc = on ?
    //             (i_recsrc | (1 << devnum )) :
    //             (i_recsrc & ~(1 << devnum ));
Christian Esken's avatar
Christian Esken committed
286 287

    // Change status of record source(s)
288 289
    if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1) {
        errormsg(Mixer::ERR_WRITE);
Christian Esken's avatar
Christian Esken committed
290
        // don't return here. It is much better to re-read the capture switch states.
Stephan Kulow's avatar
Stephan Kulow committed
291
    }
Christian Esken's avatar
Christian Esken committed
292

293 294 295 296 297 298 299
    /* The following if {} patch was submitted by Tim McCormick <tim@pcbsd.org>. */
    /*   Comment (cesken): This patch fixes an issue with mutual exclusive recording sources.
         Actually the kernel soundcard driver *could* "do the right thing" by examining the change
         (old-recsrc XOR new-recsrc), and knowing which sources are mutual exclusive.
         The OSS v3 API docs indicate that the behaviour is undefined for this case, and it is not
         clearly documented how and whether SOUND_MIXER_CAP_EXCL_INPUT is evaluated in the OSS driver.
         Evaluating that in the application (KMix) could help, but the patch will work independent
Frederik Schwarzer's avatar
Frederik Schwarzer committed
300
         on whether SOUND_MIXER_CAP_EXCL_INPUT is set or not.
301 302 303 304 305 306

         In any case this patch is a superb workaround for a shortcoming of the OSS v3 API.
     */
    // If the record source is supposed to be on, but wasn't set, explicitly
    // set the record source. Not all cards support multiple record sources.
    // As a result, we also need to do the read & write again.
307 308
    if (((i_recsrc & (1 << devnum)) == 0) && on) {
        // Setting the new device failed => Try to enable it *exclusively*
Christian Esken's avatar
Christian Esken committed
309

310
        //       oldrecsrc = i_recsrc = 1 << devnum;
Christian Esken's avatar
Christian Esken committed
311

312 313 314 315
        if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1)
            errormsg(Mixer::ERR_WRITE);
        if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1)
            errormsg(Mixer::ERR_READ);
Stephan Kulow's avatar
Stephan Kulow committed
316
    }
317 318

    // Re-read status of record source(s). Just in case the hardware/driver has
319
    // some limitation (like exclusive switches)
320 321 322
    int recsrcMask;
    if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1)
        errormsg(Mixer::ERR_READ);
323 324 325 326
    else {
        for (int i = 0; i < m_mixDevices.count(); i++) {
            shared_ptr<MixDevice> md = m_mixDevices[i];
            bool isRecsrc = ((recsrcMask & (1 << devnum)) != 0);
327 328 329
            md->setRecSource(isRecsrc);
        } // for all controls
    } // reading newrecsrcmask is OK
Christian Esken's avatar
Christian Esken committed
330

331
    return Mixer::OK;
Stephan Kulow's avatar
Stephan Kulow committed
332 333
}

334
int Mixer_OSS::id2num(const QString &id)
335
{
336
    return id.toInt();
337 338 339 340 341 342 343
}

/**
 * Prints out a translated error text for the given error number on stderr
 */
void Mixer_OSS::errormsg(int mixer_error)
{
344 345 346
    QString l_s_errText;
    l_s_errText = errorText(mixer_error);
    qCCritical(KMIX_LOG) << l_s_errText << "\n";
347
}
Christian Esken's avatar
Christian Esken committed
348

349
int Mixer_OSS::readVolumeFromHW(const QString &id, shared_ptr<MixDevice> md)
Stephan Kulow's avatar
Stephan Kulow committed
350
{
351
    int ret = 0;
352

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
    // --- VOLUME ---
    Volume &vol = md->playbackVolume();
    int devnum = id2num(id);

    bool controlChanged = false;

    if (vol.hasVolume()) {
        int volume;
        if (ioctl(m_fd, MIXER_READ(devnum), &volume) == -1) {
            /* Oops, can't read mixer */
            errormsg(Mixer::ERR_READ);
            ret = Mixer::ERR_READ;
        } else {

            int volLeft = (volume & 0x7f);
            int volRight = ((volume >> 8) & 0x7f);
            //
            //			if ( md->id() == "0" )
            //				qCDebug(KMIX_LOG) << md->id() << ": " << "volLeft=" << volLeft << ", volRight" << volRight;

            bool isMuted = volLeft == 0 && (vol.count() < 2 || volRight == 0); // muted is "left and right muted" or "left muted when mono"
            md->setMuted(isMuted);
            if (!isMuted) {
                // Muted is represented in OSS by value 0. We don't want to write the value 0 as a volume,
                // but instead we only mark it muted (see setMuted() above).

                foreach (VolumeChannel vc, vol.getVolumes()) {
                    long volOld = 0;
                    long volNew = 0;
                    switch (vc.chid) {
                    case Volume::LEFT:
                        volOld = vol.getVolume(Volume::LEFT);
                        volNew = volLeft;
                        vol.setVolume(Volume::LEFT, volNew);
                        break;
                    case Volume::RIGHT:
                        volOld = vol.getVolume(Volume::RIGHT);
                        volNew = volRight;
                        vol.setVolume(Volume::RIGHT, volNew);
                        break;
                    default:
                        // not supported by OSSv3
                        break;
                    }

                    if (volOld != volNew) {
                        controlChanged = true;
                        // if ( md->id() == "0" ) qCDebug(KMIX_LOG) << "changed";
                    }
                } // foreach
            } // muted
        }
    }
Stephan Kulow's avatar
Stephan Kulow committed
406

Christian Esken's avatar
Christian Esken committed
407
    // --- RECORD SWITCH ---
408
    // Volume& captureVol = md->captureVolume();
Christian Esken's avatar
Christian Esken committed
409 410 411
    int recsrcMask;
    if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1)
        ret = Mixer::ERR_READ;
412 413
    else {
        bool isRecsrcOld = md->isRecSource();
Christian Esken's avatar
Christian Esken committed
414
        // test if device bit is set in record bit mask
415
        bool isRecsrc = ((recsrcMask & (1 << devnum)) != 0);
416
        md->setRecSource(isRecsrc);
417 418
        if (isRecsrcOld != isRecsrc)
            controlChanged = true;
Christian Esken's avatar
Christian Esken committed
419 420
    }

421 422 423 424 425 426 427 428 429 430 431
    if (ret == 0) {
        if (controlChanged) {
            // qCDebug(KMIX_LOG) << "FINE! " << ret;
            return Mixer::OK;
        } else {
            return Mixer::OK_UNCHANGED;
        }
    } else {
        // qCDebug(KMIX_LOG) << "SHIT! " << ret;
        return ret;
    }
Christian Esken's avatar
Christian Esken committed
432 433
}

434
int Mixer_OSS::writeVolumeToHW(const QString &id, shared_ptr<MixDevice> md)
Christian Esken's avatar
Christian Esken committed
435 436 437 438
{
    int volume;
    int devnum = id2num(id);

439 440 441 442 443 444
    Volume &vol = md->playbackVolume();
    if (md->isMuted())
        volume = 0;
    else {
        if (vol.getVolumes().count() > 1)
            volume = (vol.getVolume(Volume::LEFT) + (vol.getVolume(Volume::RIGHT) << 8));
Christian Esken's avatar
Christian Esken committed
445
        else
446
            volume = vol.getVolume(Volume::LEFT);
Christian Esken's avatar
Christian Esken committed
447
    }
Stephan Kulow's avatar
Stephan Kulow committed
448

449 450
    if (ioctl(m_fd, MIXER_WRITE(devnum), &volume) == -1)
        return Mixer::ERR_WRITE;
Christian Esken's avatar
Christian Esken committed
451

452
    setRecsrcToOSS(id, md->isRecSource());
Christian Esken's avatar
Christian Esken committed
453

Christian Esken's avatar
Christian Esken committed
454
    return 0;
Stephan Kulow's avatar
Stephan Kulow committed
455
}
456

457 458 459
QString OSS_getDriverName()
{
    return "OSS";
460 461
}

462 463 464
QString Mixer_OSS::getDriverName()
{
    return "OSS";
465
}