/***************************************************************************
                          oss-sound.cpp  -  description
                             -------------------
    begin                : Sun Mar 21 2004
    copyright            : (C) 2004 by Martin Witte
    email                : emw-kradio@nocabal.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "oss-sound.h"

#include "../../src/include/aboutwidget.h"
#include <klocale.h>
#include <kaboutdata.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <errno.h>

#include "oss-sound-configuration.h"
#include "../../src/include/utils.h"

///////////////////////////////////////////////////////////////////////
//// plugin library functions

PLUGIN_LIBRARY_FUNCTIONS(OSSSoundDevice, "kradio-oss-sound", i18n("Open Sound System (OSS) Support"));

/////////////////////////////////////////////////////////////////////////////

struct _lrvol { unsigned char l, r; short dummy; };

OSSSoundDevice::OSSSoundDevice(const QString &name)
    : QObject(NULL, NULL),
      PluginBase(name, i18n("KRadio OSS Sound Plugin")),
      m_DSPDeviceName(""),
      m_MixerDeviceName(""),
      m_DSP_fd(-1),
      m_Mixer_fd(-1),
      m_DuplexMode(DUPLEX_UNKNOWN),
      m_DSPFormat(),
      m_PassivePlaybackStreams(),
      m_PlaybackStreamID(),
      m_CaptureStreamID(),
      m_BufferSize(65536),
      m_PlaybackBuffer(m_BufferSize),
      m_CaptureBuffer(m_BufferSize),
      m_CaptureRequestCounter(0),
      m_CapturePos(0),
      m_CaptureStartTime(0),
      //m_PlaybackSkipCount(0),
      m_CaptureSkipCount(0),
      m_EnablePlayback(true),
      m_EnableCapture(true)
{
    QObject::connect(&m_PollingTimer, SIGNAL(timeout()), this, SLOT(slotPoll()));
}


OSSSoundDevice::~OSSSoundDevice()
{
    stopCapture(m_CaptureStreamID);
    stopPlayback(m_PlaybackStreamID);
    closeDSPDevice();
    closeMixerDevice();
}


bool OSSSoundDevice::connectI(Interface *i)
{
    bool a = PluginBase::connectI(i);
    bool b = ISoundStreamClient::connectI(i);
    return a || b;
}


bool OSSSoundDevice::disconnectI(Interface *i)
{
    bool a = PluginBase::disconnectI(i);
    bool b = ISoundStreamClient::disconnectI(i);
    return a || b;
}

void OSSSoundDevice::noticeConnectedI (ISoundStreamServer *s, bool pointer_valid)
{
    ISoundStreamClient::noticeConnectedI(s, pointer_valid);
    if (s && pointer_valid) {
        s->register4_sendReleasePlayback(this);
        s->register4_sendReleaseCapture(this);
        s->register4_sendPlaybackVolume(this);
        s->register4_sendCaptureVolume(this);
        s->register4_queryPlaybackVolume(this);
        s->register4_queryCaptureVolume(this);
        s->register4_sendStartPlayback(this);
        s->register4_sendPausePlayback(this);
        s->register4_sendResumePlayback(this);
        s->register4_sendStopPlayback(this);
        s->register4_queryIsPlaybackRunning(this);
        s->register4_sendStartCaptureWithFormat(this);
        s->register4_sendStopCapture(this);
        s->register4_queryIsCaptureRunning(this);
        s->register4_notifySoundStreamClosed(this);
        s->register4_notifySoundStreamRedirected(this);
        s->register4_notifySoundStreamData(this);
    }
}

// PluginBase

void OSSSoundDevice::saveState (KConfig *c) const
{
    PluginBase::saveState(c);

    c->setGroup(QString("oss-sound-") + PluginBase::name());

    c->writeEntry("dsp-device",      m_DSPDeviceName);
    c->writeEntry("mixer-device",    m_MixerDeviceName);
    c->writeEntry("enable-playback", m_EnablePlayback);
    c->writeEntry("enable-capture",  m_EnableCapture);
    c->writeEntry("buffer-size",     m_BufferSize);
    c->writeEntry("soundstreamclient-id", m_SoundStreamClientID);
}


void OSSSoundDevice::restoreState (KConfig *c)
{
    PluginBase::restoreState(c);

    c->setGroup(QString("oss-sound-") + PluginBase::name());

    m_EnablePlayback  = c->readBoolEntry("enable-playback",  true);
    m_EnableCapture   = c->readBoolEntry("enable-capture",   true);
    m_BufferSize      = c->readNumEntry ("buffer-size",      65536);

    setDSPDeviceName   (c->readEntry    ("dsp-device",       "/dev/dsp"));
    setMixerDeviceName (c->readEntry    ("mixer-device",     "/dev/mixer"));

    m_PlaybackBuffer.resize(m_BufferSize);
    m_CaptureBuffer.resize(m_BufferSize);

    // must be last. noticeSoundClientConnected will be called by this.
    setSoundStreamClientID(c->readEntry("soundstreamclient-id", getSoundStreamClientID()));


    emit sigUpdateConfig();

}


void OSSSoundDevice::setMixerDeviceName(const QString &dev_name)
{
    if (m_MixerDeviceName != dev_name) {
        m_MixerDeviceName  = dev_name;
        if (m_Mixer_fd >= 0)
            openMixerDevice(true);
        getMixerChannels(SOUND_MIXER_DEVMASK, m_PlaybackChannels, m_revPlaybackChannels);
        getMixerChannels(SOUND_MIXER_RECMASK, m_CaptureChannels, m_revCaptureChannels);
        notifyPlaybackChannelsChanged(m_SoundStreamClientID, m_PlaybackChannels);
        notifyCaptureChannelsChanged(m_SoundStreamClientID,  m_CaptureChannels);
    }
}


ConfigPageInfo  OSSSoundDevice::createConfigurationPage()
{
    OSSSoundConfiguration *conf = new OSSSoundConfiguration(NULL, this);
    QObject::connect(this, SIGNAL(sigUpdateConfig()), conf, SLOT(slotUpdateConfig()));
    return ConfigPageInfo (conf,
                           i18n("OSS Sound"),
                           i18n("OSS Sound Device Options"),
                           "kradio_oss");
}


/*AboutPageInfo OSSSoundDevice::createAboutPage()
{*/
/*    KAboutData aboutData("kradio4",
                         NULL,
                         NULL,
                         I18N_NOOP("OSS Sound Plugin for KRadio"),
                         KAboutData::License_GPL,
                         "(c) 2004 Martin Witte",
                         0,
                         "http://sourceforge.net/projects/kradio",
                         0);
    aboutData.addAuthor("Martin Witte",  "", "emw-kradio@nocabal.de");

    return AboutPageInfo(
              new KRadioAboutWidget(aboutData, KRadioAboutWidget::AbtTabbed),
              i18n("OSS Sound"),
              i18n("OSS Sound"),
              "kradio_oss_sound"
           );
*/
//     return AboutPageInfo();
// }



bool OSSSoundDevice::preparePlayback(SoundStreamID id, const QString &channel, bool active_mode, bool start_immediately)
{
    if (id.isValid() && m_revPlaybackChannels.contains(channel)) {
        m_PlaybackStreams.insert(id, SoundStreamConfig(m_revPlaybackChannels[channel], active_mode));
        if (start_immediately)
            startPlayback(id);
        return true;
        // FIXME: what to do if stream is already playing?
    }
    return false;
}


bool OSSSoundDevice::prepareCapture(SoundStreamID id, const QString &channel)
{
    if (id.isValid() && m_revCaptureChannels.contains(channel)) {
        m_CaptureStreams.insert(id, SoundStreamConfig(m_revCaptureChannels[channel]));
        return true;
        // FIXME: what to do if stream is already playing?
    }
    return false;
}

bool OSSSoundDevice::releasePlayback(SoundStreamID id)
{
    if (id.isValid() && m_PlaybackStreams.contains(id)) {
        if (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id)) {
            stopPlayback(id);
        }
        m_PlaybackStreams.remove(id);
        return true;
    }
    return false;
}

bool OSSSoundDevice::releaseCapture(SoundStreamID id)
{
    if (id.isValid() && m_CaptureStreams.contains(id)) {
        if (m_CaptureStreamID == id) {
            stopCapture(id);
        }
        m_CaptureStreams.remove(id);
        return true;
    }
    return false;
}

bool OSSSoundDevice::supportsPlayback()   const
{
    return m_EnablePlayback;
}


bool OSSSoundDevice::supportsCapture() const
{
    return m_EnableCapture;
}


bool OSSSoundDevice::startPlayback(SoundStreamID id)
{
    if (id.isValid() && m_PlaybackStreams.contains(id) && m_EnablePlayback) {

        SoundStreamConfig &cfg = m_PlaybackStreams[id];

        bool ok = false;
        if (cfg.m_ActiveMode) {
            if (!m_PlaybackStreamID.isValid()) {
                m_PlaybackStreamID = id;
                ok = true;
            }
        } else {
            if (!m_PassivePlaybackStreams.contains(id))
                m_PassivePlaybackStreams.append(id);
            ok = true;
        }

        if (ok) {
            openMixerDevice();
            if (cfg.m_Volume >= 0)
                writeMixerVolume(cfg.m_Channel, cfg.m_Volume);
        }

        // error handling?
        return true;
    } else {
        return false;
    }
}


bool OSSSoundDevice::pausePlayback(SoundStreamID /*id*/)
{
    //return stopPlayback(id);
    return false;
}


bool OSSSoundDevice::resumePlayback(SoundStreamID /*id*/)
{
    return false;
}


bool OSSSoundDevice::stopPlayback(SoundStreamID id)
{
    if (id.isValid() && m_PlaybackStreams.contains(id)) {

        SoundStreamConfig &cfg = m_PlaybackStreams[id];

        if (!cfg.m_ActiveMode) {
            if  (m_PassivePlaybackStreams.contains(id)) {
//                 writeMixerVolume(cfg.m_Channel, 0);
                m_PassivePlaybackStreams.remove(id);
            }
        } else if (m_PlaybackStreamID == id) {
            m_PlaybackStreamID = SoundStreamID::InvalidID;
            m_PlaybackBuffer.clear();
            closeDSPDevice();
        }

        closeMixerDevice();
        return true;
    } else {
        return false;
    }
}

bool OSSSoundDevice::isPlaybackRunning(SoundStreamID id, bool &b) const
{
    if (id.isValid() && m_PlaybackStreams.contains(id)) {
        b = true;
        return true;
    } else {
        return false;
    }
}

bool OSSSoundDevice::startCaptureWithFormat(SoundStreamID      id,
                                  const SoundFormat &proposed_format,
                                  SoundFormat       &real_format,
                                  bool force_format)
{
    if (m_CaptureStreams.contains(id) && m_EnableCapture) {

        if (m_CaptureStreamID != id) {
            m_CapturePos       = 0;
            m_CaptureStartTime = time(NULL);
        }

        if (m_CaptureStreamID != id || force_format) {

            m_CaptureStreamID = id;
            SoundStreamConfig &cfg = m_CaptureStreams[id];

            openMixerDevice();
            selectCaptureChannel(cfg.m_Channel);
            if (cfg.m_Volume >= 0)
                writeMixerVolume(cfg.m_Channel, cfg.m_Volume);

            openDSPDevice(proposed_format);

            // FIXME: error handling?
        }

        real_format = m_DSPFormat;
        m_CaptureRequestCounter++;

        return true;
    } else {
        return false;
    }
}


bool OSSSoundDevice::stopCapture(SoundStreamID id)
{
    if (id.isValid() && m_CaptureStreamID == id) {

        if (--m_CaptureRequestCounter == 0) {
            m_CaptureStreamID = SoundStreamID::InvalidID;
            m_CaptureBuffer.clear();

            closeMixerDevice();
            closeDSPDevice();
        }
        return true;
    } else {
        return false;
    }
}


bool OSSSoundDevice::isCaptureRunning(SoundStreamID id, bool &b, SoundFormat &sf) const
{
    if (id.isValid() && m_CaptureStreamID == id) {
        b  = true;
        sf = m_DSPFormat;
        return true;
    } else {
        return false;
    }
}


bool OSSSoundDevice::noticeSoundStreamClosed(SoundStreamID id)
{
    bool found = false;
    if (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id)) {
        stopPlayback(id);
        found = true;
    }
    if (m_CaptureStreamID == id) {
        stopCapture(id);
        found = true;
    }
    m_PlaybackStreams.remove(id);
    m_CaptureStreams.remove(id);
    return found;
}


bool OSSSoundDevice::noticeSoundStreamRedirected(SoundStreamID oldID, SoundStreamID newID)
{
    bool found = false;
    if (m_PlaybackStreams.contains(oldID)) {
        m_PlaybackStreams.insert(newID, m_PlaybackStreams[oldID]);
        if (newID != oldID)
            m_PlaybackStreams.remove(oldID);
        found = true;
    }
    if (m_CaptureStreams.contains(oldID)) {
        m_CaptureStreams.insert(newID, m_CaptureStreams[oldID]);
        if (newID != oldID)
            m_CaptureStreams.remove(oldID);
        found = true;
    }

    if (m_PlaybackStreamID == oldID)
        m_PlaybackStreamID = newID;
    if (m_CaptureStreamID == oldID)
        m_CaptureStreamID = newID;
    if (m_PassivePlaybackStreams.contains(oldID)) {
        m_PassivePlaybackStreams.remove(oldID);
        m_PassivePlaybackStreams.append(newID);
    }
    return found;
}


bool OSSSoundDevice::noticeSoundStreamData(SoundStreamID id,
                                           const SoundFormat &format,
                                           const char *data, size_t size, size_t &consumed_size,
                                           const SoundMetaData &/*md*/
                                          )
{
    if (!id.isValid() || id != m_PlaybackStreamID)
        return false;

    if (m_DSP_fd < 0) {
        openDSPDevice(format);
    } else if (format != m_DSPFormat) {
        if (m_CaptureStreamID.isValid())
            return false;

        // flush playback buffer
        size_t buffersize = 0;
        char *buffer = m_PlaybackBuffer.getData(buffersize);
        write(m_DSP_fd, buffer, buffersize);

        // if not all could be written, it must be discarded
        m_PlaybackBuffer.clear();

        closeDSPDevice();
        openDSPDevice(format);
        // error handling ?
    }

    size_t n = m_PlaybackBuffer.addData(data, size);
    consumed_size = (consumed_size == SIZE_T_DONT_CARE) ? n : min(consumed_size, n);

//     if (n < size) {
//         m_PlaybackSkipCount += size - n;
//     } else if (m_PlaybackSkipCount > 0) {
//         logWarning(i18n("%1: Playback buffer overflow. Skipped %1 bytes", m_DSPDeviceName, QString::number(m_PlaybackSkipCount)));
//         m_PlaybackSkipCount = 0;
//     }

    return true; //m_PlaybackSkipCount == 0;
}



void OSSSoundDevice::slotPoll()
{
    int err = 0;

    if (m_CaptureStreamID.isValid() && m_DSP_fd >= 0) {

        size_t bufferSize = 0;
        char *buffer = m_CaptureBuffer.getFreeSpace(bufferSize);

        int bytesRead = read(m_DSP_fd, buffer, bufferSize);

        if (bytesRead > 0) {
            m_CaptureBuffer.removeFreeSpace(bytesRead);
        } else if (bytesRead < 0 && errno == EAGAIN) {
            bytesRead = 0;
        } else if (bytesRead == 0) {
            err = -1;
            logError(i18n("OSS device %1: No data to record", m_DSPDeviceName));
        } else {
            err = errno;
        }

        while (m_CaptureBuffer.getFillSize() > m_CaptureBuffer.getSize() / 3) {
            size_t size = 0;
            buffer = m_CaptureBuffer.getData(size);
            time_t cur_time = time(NULL);
            size_t consumed_size = SIZE_T_DONT_CARE;
            notifySoundStreamData(m_CaptureStreamID, m_DSPFormat, buffer, size, consumed_size, SoundMetaData(m_CapturePos, cur_time - m_CaptureStartTime, cur_time, i18n("internal stream, not stored (%1)", m_DSPDeviceName)));
            if (consumed_size == SIZE_T_DONT_CARE)
                consumed_size = size;
            m_CaptureBuffer.removeData(consumed_size);
            m_CapturePos += consumed_size;
            if (consumed_size < size)
                break;
        }
    }

    if (m_PlaybackStreamID.isValid()/* && m_DSP_fd >= 0*/) {

        if (m_PlaybackBuffer.getFillSize() > 0 && m_DSP_fd >= 0) {

            size_t buffersize = 0;
            char *buffer = m_PlaybackBuffer.getData(buffersize);
            int bytesWritten  = write(m_DSP_fd, buffer, buffersize);

            if (bytesWritten > 0) {
                m_PlaybackBuffer.removeData(bytesWritten);
            } else if (bytesWritten < 0 && errno == EAGAIN) {
                bytesWritten = 0;
            } else {
                err = errno;
            }
        }

        if (m_PlaybackBuffer.getFreeSize() > 0)
            notifyReadyForPlaybackData(m_PlaybackStreamID, m_PlaybackBuffer.getFreeSize());
    }

    if (err) {
        logError(i18n("Error %1 while handling OSS device %2", QString().setNum(err), m_DSPDeviceName));
    }

    if (m_PlaybackStreamID.isValid())
        checkMixerVolume(m_PlaybackStreamID);
    if (m_CaptureStreamID.isValid())
        checkMixerVolume(m_CaptureStreamID);

    QValueListConstIterator<SoundStreamID> end = m_PassivePlaybackStreams.end();
    for (QValueListConstIterator<SoundStreamID> it = m_PassivePlaybackStreams.begin(); it != end; ++it)
        checkMixerVolume(*it);

}


bool OSSSoundDevice::openDSPDevice(const SoundFormat &format, bool reopen)
{
    if (m_DSP_fd >= 0) {

        if (reopen) {

            closeDSPDevice ( /* force = */ true);

        } else {

            if (format != m_DSPFormat)
                return false;

            if (m_DuplexMode != DUPLEX_FULL && m_CaptureStreamID.isValid() && m_PlaybackStreamID.isValid())
                return false;

            return true;
        }
    } else {
        if (reopen)
            return true;
    }

    m_DSPFormat = format;

    // first testopen for CAPS
    m_DSP_fd = open(m_DSPDeviceName.ascii(), O_NONBLOCK | O_RDONLY);
    bool err = m_DSP_fd < 0;
    if (err) {
        logError(i18n("Cannot open DSP device %1", m_DSPDeviceName));
        return false;
    }
    int caps = 0;
    err |= (ioctl (m_DSP_fd, SNDCTL_DSP_GETCAPS, &caps) != 0);
    if (err)
        logError(i18n("Cannot read DSP capabilities for %1", m_DSPDeviceName));

    m_DuplexMode = (caps & DSP_CAP_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
    close (m_DSP_fd);
    m_DSP_fd = -1;

    // opening and seeting up the device file
    int mode = O_NONBLOCK;
    if (m_DuplexMode == DUPLEX_FULL) {
        mode |= O_RDWR;
    } else if (m_CaptureStreamID.isValid()) {
        mode |= O_RDONLY;
    } else {
        mode |= O_WRONLY;
    }

    m_DSP_fd = open(m_DSPDeviceName.ascii(), mode);

    err = m_DSP_fd < 0;
    if (err) {
        logError(i18n("Cannot open DSP device %1", m_DSPDeviceName));
        return false;
    }

    int oss_format = getOSSFormat(m_DSPFormat);
    err |= (ioctl(m_DSP_fd, SNDCTL_DSP_SETFMT, &oss_format) != 0);
    if (err)
        logError(i18n("Cannot set DSP sample format for %1", m_DSPDeviceName));

    int channels = m_DSPFormat.m_Channels;
    err |= (ioctl(m_DSP_fd, SNDCTL_DSP_CHANNELS, &channels) != 0);
    if (err)
        logError(i18n("Cannot set number of channels for %1", m_DSPDeviceName));

    int rate = m_DSPFormat.m_SampleRate;
    err |= (ioctl(m_DSP_fd, SNDCTL_DSP_SPEED, &rate) != 0);
    if (err)
        logError(i18n("Cannot set sampling rate for %1", m_DSPDeviceName));
    if (rate != (int)m_DSPFormat.m_SampleRate) {
        logWarning(i18n("Asking for %1 Hz but %2 uses %3 Hz",
                        QString::number(m_DSPFormat.m_SampleRate),
                        m_DSPDeviceName,
                        QString::number(rate)));
        m_DSPFormat.m_SampleRate = rate;
    }

    int stereo = m_DSPFormat.m_Channels == 2;
    err |= (ioctl(m_DSP_fd, SNDCTL_DSP_STEREO, &stereo) != 0);
    if (err)
        logError(i18n("Cannot set stereo mode for %1", m_DSPDeviceName));

    unsigned sampleSize = m_DSPFormat.m_SampleBits;
    err |= (ioctl(m_DSP_fd, SNDCTL_DSP_SAMPLESIZE, &sampleSize) != 0);
    if (err || sampleSize != m_DSPFormat.m_SampleBits)
        logError(i18n("Cannot set sample size for %1", m_DSPDeviceName));

    // setup buffer, ask for 40ms latency
    int tmp  = (400 * m_DSPFormat.frameSize() * m_DSPFormat.m_SampleRate) / 1000;
    int mask = -1;    for (; tmp; tmp >>= 1) ++mask;
    if (mask < 8) mask = 12;  // default 4kB
    mask |= 0x7FFF0000;
    err |= ioctl (m_DSP_fd, SNDCTL_DSP_SETFRAGMENT, &mask);
    if (err)
        logError(i18n("Cannot set buffers for %1", m_DSPDeviceName));

    int bufferBlockSize = 0;
    err |= ioctl (m_DSP_fd, SNDCTL_DSP_GETBLKSIZE, &bufferBlockSize);
    if (err) {
        logError(i18n("Cannot read buffer size for %1", m_DSPDeviceName));
    } else {
        logInfo(i18n("%1 uses buffer blocks of %2 bytes", m_DSPDeviceName, QString::number(bufferBlockSize)));
        size_t  tmp = (((m_BufferSize - 1) / bufferBlockSize) + 1) * bufferBlockSize;
        setBufferSize(tmp);
        logInfo(i18n("adjusted own buffer size to %1 bytes", QString::number(tmp)));
    }

    int trigger = ~PCM_ENABLE_INPUT & ~PCM_ENABLE_OUTPUT;
    ioctl(m_DSP_fd, SNDCTL_DSP_SETTRIGGER, &trigger);
    trigger = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
    ioctl(m_DSP_fd, SNDCTL_DSP_SETTRIGGER, &trigger);

    if (!err) {
        m_PollingTimer.start(40);
    } else {
        closeDSPDevice();
    }

    m_CaptureSkipCount  = 0;
    //m_PlaybackSkipCount = 0;

    return !err;
}


bool OSSSoundDevice::closeDSPDevice(bool force)
{
    if ((!m_PlaybackStreamID.isValid() && !m_CaptureStreamID.isValid()) || force) {

        if (m_Mixer_fd < 0)
            m_PollingTimer.stop();

        if (m_DSP_fd >= 0)
            close (m_DSP_fd);
        m_DSP_fd = -1;

        m_PlaybackBuffer.clear();
        m_CaptureBuffer.clear();
    }
    return true;
}


bool OSSSoundDevice::openMixerDevice(bool reopen)
{
    if (reopen) {
        if (m_Mixer_fd >= 0)
            closeMixerDevice(/* force = */ true);
        else
            return true;
    }

    if (m_Mixer_fd < 0)
        m_Mixer_fd = open(m_MixerDeviceName.ascii(), O_RDONLY);

    if (m_Mixer_fd < 0) {
        logError(i18n("Cannot open mixer device %1", m_MixerDeviceName));
    } else {
        m_PollingTimer.start(40);
    }
    return m_Mixer_fd >= 0;
}


bool OSSSoundDevice::closeMixerDevice(bool force)
{
    if ((!m_PlaybackStreamID.isValid() && !m_CaptureStreamID.isValid()) || force) {

        if (m_DSP_fd < 0)
            m_PollingTimer.stop();

        if (m_Mixer_fd >= 0)
            close (m_Mixer_fd);
        m_Mixer_fd = -1;
    }
    return m_Mixer_fd < 0;
}


void OSSSoundDevice::getMixerChannels(int query, QStringList &retval, QMap<QString, int> &revmap) const
{
    retval.clear();
    revmap.clear();

    int fd = m_Mixer_fd;
    if (fd < 0)
        fd = open(m_MixerDeviceName.ascii(), O_RDONLY);

    if (fd < 0) {
        logError(i18n("OSSSoundDevice::getMixerChannels: Cannot open mixer device %1", m_MixerDeviceName));
    }

    if (fd >= 0) {
        int mask = 0;
        if ( ioctl(fd, MIXER_READ(query), &mask) == 0 ) {
            for (int i = 0; i < SOUND_MIXER_NRDEVICES; ++i) {
                if (mask & (1 << i)) {
                    static const char *labels[] = SOUND_DEVICE_LABELS;
                    retval.append(i18n(labels[i]));
                    revmap.insert(i18n(labels[i]), i);
                }
            }
        } else {
            logError(i18n("OSSSoundDevice::getMixerChannels: Cannot read mixer device mask on device %1", m_MixerDeviceName));
        }
    }
    if (fd != m_Mixer_fd)
        close(fd);
}


const QStringList &OSSSoundDevice::getPlaybackChannels() const
{
    return m_PlaybackChannels;
}


const QStringList &OSSSoundDevice::getCaptureChannels() const
{
    return m_CaptureChannels;
}


bool OSSSoundDevice::setPlaybackVolume(SoundStreamID id, float volume)
{
    if (id.isValid() && (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id))) {
        SoundStreamConfig &cfg = m_PlaybackStreams[id];

        if (rint(100*volume) != rint(100*cfg.m_Volume)) {
            cfg.m_Volume = writeMixerVolume(cfg.m_Channel, volume);
            notifyPlaybackVolumeChanged(id, cfg.m_Volume);
        }
        return true;
    }
    return false;
}


bool OSSSoundDevice::setCaptureVolume(SoundStreamID id, float volume)
{
    if (id.isValid() && m_CaptureStreamID == id) {
        SoundStreamConfig &cfg = m_CaptureStreams[id];

        if (rint(100*volume) != rint(100*cfg.m_Volume)) {
            cfg.m_Volume = writeMixerVolume(cfg.m_Channel, volume);
            notifyCaptureVolumeChanged(id, cfg.m_Volume);
        }
        return true;
    }
    return false;
}


bool OSSSoundDevice::getPlaybackVolume(SoundStreamID id, float &volume) const
{
    if (id.isValid() && (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id))) {
        const SoundStreamConfig &cfg = m_PlaybackStreams[id];
        volume = cfg.m_Volume;
        return true;
    }
    return false;
}


bool OSSSoundDevice::getCaptureVolume(SoundStreamID id, float &volume) const
{
    if (id.isValid() && m_CaptureStreamID == id) {
        const SoundStreamConfig &cfg = m_CaptureStreams[id];
        volume = cfg.m_Volume;
        return true;
    }
    return false;
}


void OSSSoundDevice::checkMixerVolume(SoundStreamID id)
{
    if (m_Mixer_fd >= 0 && id.isValid()) {

        if (m_PassivePlaybackStreams.contains(id) || m_PlaybackStreamID == id) {
            SoundStreamConfig &cfg = m_PlaybackStreams[id];

            float v = readMixerVolume(cfg.m_Channel);
            if (rint(100*cfg.m_Volume) != rint(100*v)) {
                cfg.m_Volume = v;
                notifyPlaybackVolumeChanged(id, v);
            }
        }

        if (m_CaptureStreamID == id) {
            SoundStreamConfig &cfg = m_CaptureStreams[id];

            float v = readMixerVolume(cfg.m_Channel);
            if (rint(100*cfg.m_Volume) != rint(100*v)) {
                cfg.m_Volume = v;
                notifyCaptureVolumeChanged(id, v);
            }
        }
    }
}


float OSSSoundDevice::readMixerVolume(int channel) const
{
    _lrvol tmpvol;
    int err = ioctl(m_Mixer_fd, MIXER_READ(channel), &tmpvol);
    if (err) {
        logError("OSSSound::readMixerVolume: " +
                i18n("error %1 while reading volume from %2",
                     QString().setNum(err),
                     m_MixerDeviceName));
        tmpvol.l = tmpvol.r = 0;
    }
    return float(tmpvol.l) / 100.0;
}


float OSSSoundDevice::writeMixerVolume (int channel, float vol)
{
    if (vol > 1.0) vol = 1.0;
    if (vol < 0) vol = 0.0;

    const int divs = 100;
    vol = rint(vol * divs) / float(divs);

    if (m_Mixer_fd >= 0) {
        _lrvol tmpvol;
        tmpvol.r = tmpvol.l = (unsigned int)(rint(vol * divs));
        int err = ioctl(m_Mixer_fd, MIXER_WRITE(channel), &tmpvol);
        if (err != 0) {
            logError("OSSSoundDevice::writeMixerVolume: " +
                    i18n("error %1 while setting volume to %2 on device %3",
                         QString().setNum(err),
                         QString().setNum(vol),
                         m_MixerDeviceName));
            return -1;
        }
    }
    return vol;
}


void OSSSoundDevice::selectCaptureChannel (int channel)
{
    int x = 1 << channel;
    int err = ioctl(m_Mixer_fd, SOUND_MIXER_WRITE_RECSRC, &x);
    if (err)
        logError(i18n("Selecting recording source on device %1 failed with error code %2",
                      m_MixerDeviceName,
                      QString::number(err)));
    _lrvol tmpvol;
    err = ioctl(m_Mixer_fd, MIXER_READ(SOUND_MIXER_IGAIN), &tmpvol);
    if (err)
        logError(i18n("Reading igain volume on device %1 failed with error code %2",
                      m_MixerDeviceName,
                      QString::number(err)));
    if (tmpvol.r == 0 && tmpvol.l == 0) {
        tmpvol.r = tmpvol.l = 1;
        err = ioctl(m_Mixer_fd, MIXER_WRITE(SOUND_MIXER_IGAIN), &tmpvol);
        if (err)
            logError(i18n("Setting igain volume on device %1 failed with error code %2",
                          m_MixerDeviceName,
                          QString::number(err)));
    }
}


int OSSSoundDevice::getOSSFormat(const SoundFormat &f)
{
    if (f.m_SampleBits == 16) {
        switch (2 * f.m_IsSigned + (f.m_Endianess == LITTLE_ENDIAN)) {
            case 0: return AFMT_U16_BE;
            case 1: return AFMT_U16_LE;
            case 2: return AFMT_S16_BE;
            case 3: return AFMT_S16_LE;
        }
    }
    if (f.m_SampleBits == 8) {
        switch (f.m_IsSigned) {
            case 0: return AFMT_U8;
            case 1: return AFMT_S8;
        }
    }
    return 0;
}


void OSSSoundDevice::setBufferSize(int s)
{
    m_BufferSize = s;
    m_PlaybackBuffer.resize(m_BufferSize);
    m_CaptureBuffer.resize(m_BufferSize);
}


void OSSSoundDevice::enablePlayback(bool on)
{
    m_EnablePlayback = on;
}


void OSSSoundDevice::enableCapture(bool on)
{
    m_EnableCapture = on;
}


void OSSSoundDevice::setDSPDeviceName(const QString &s)
{
    m_DSPDeviceName = s;
    SoundFormat f = m_DSPFormat;
    if (m_DSP_fd >= 0)
        openDSPDevice(f, /* reopen = */ true);
}


QString OSSSoundDevice::getSoundStreamClientDescription() const
{
    return i18n("OSS Sound Device %1", PluginBase::name());
}



#include "oss-sound.moc"
