/*************************************************************************
         Record-OSS.cpp  -  device for audio recording via OSS
                             -------------------
    begin                : Sun Jul 24 2005
    copyright            : (C) 2005 by Thomas Eschenbacher
    email                : Thomas.Eschenbacher@gmx.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 "config.h"
#ifdef HAVE_OSS_SUPPORT

#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/soundcard.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>

#include <QDir>
#include <QFile>
#include <QLatin1Char>
#include <QtGlobal>

#include "libkwave/Compression.h"
#include "libkwave/SampleFormat.h"
#include "libkwave/String.h"
#include "libkwave/Utils.h"

#include "Record-OSS.h"

// Linux 2.6.24 and above's OSS emulation supports 24 and 32 bit formats
// but did not declare them in <soundcard.h>
#ifndef AFMT_S24_LE
#define AFMT_S24_LE      0x00008000
#endif
#ifndef AFMT_S24_BE
#define AFMT_S24_BE      0x00010000
#endif
#ifndef AFMT_S32_LE
#define AFMT_S32_LE      0x00001000
#endif
#ifndef AFMT_S32_BE
#define AFMT_S32_BE      0x00002000
#endif

#ifndef SNDCTL_DSP_SPEED
#define SNDCTL_DSP_SPEED SOUND_PCM_WRITE_RATE
#endif

#ifndef SNDCTL_DSP_CHANNELS
#define SNDCTL_DSP_CHANNELS SOUND_PCM_WRITE_CHANNELS
#endif

#ifndef SOUND_PCM_SETFMT
#define SOUND_PCM_SETFMT SOUND_PCM_WRITE_BITS
#endif

#ifndef SNDCTL_DSP_SETFMT
#define SNDCTL_DSP_SETFMT SOUND_PCM_SETFMT
#endif


#define MAX_CHANNELS 2 /**< highest available number of channels */

//***************************************************************************
Kwave::RecordOSS::RecordOSS()
    :Kwave::RecordDevice(),
     m_fd(-1), m_rate(0), m_tracks(0), m_oss_version(-1)
{
}

//***************************************************************************
Kwave::RecordOSS::~RecordOSS()
{
    close();
}

//***************************************************************************
QString Kwave::RecordOSS::open(const QString &dev)
{
    // close the device if it is still open
    if (m_fd >= 0) close();
    if (!dev.length()) return QString::number(EINVAL); // no device name

    // first of all: try to open the device itself
    int fd = ::open(qPrintable(dev), O_RDONLY | O_NONBLOCK);
    if (fd < 0) {
        qWarning("open failed, fd=%d, errno=%d (%s)",
                 fd, errno, strerror(errno));

        QString reason;
        switch (errno) {
            case ENOENT:
            case ENODEV:
            case ENXIO:
            case EIO:
                reason = QString::number(ENODEV);
                break;
            case EBUSY:
                reason = QString::number(EBUSY);
                break;
            default:
                reason = QString::fromLocal8Bit(strerror(errno));
                break;
        }
        return reason;
    }

    // Query OSS driver version
    m_oss_version = 0x030000;
#ifdef OSS_GETVERSION
    ioctl(fd, OSS_GETVERSION, &m_oss_version);
#endif
    m_fd = fd;
    return QString();
}

//***************************************************************************
int Kwave::RecordOSS::read(QByteArray &buffer, unsigned int offset)
{
    fd_set rfds;
    struct timeval tv;
    int retval;
    int read_bytes = 0;
    unsigned int length = static_cast<unsigned int>(buffer.size());

//     qDebug("RecordOSS::read(), offset=%d, length=%d", offset, length);
    Q_ASSERT(m_fd >= 0);
    Q_ASSERT(buffer.size());
    Q_ASSERT(length);
    Q_ASSERT(offset < length);
    if (m_fd < 0) return -EBADF; // file not opened
    if (buffer.isEmpty()) return -EINVAL; // buffer is null pointer
    if (!length) return -EINVAL;
    if (offset >= length) return -EINVAL;

    length -= offset;

#if 0
    int blocksize = length;
    int err = ioctl(m_fd, SNDCTL_DSP_GETBLKSIZE, &blocksize);
    Q_ASSERT(!err);
    qDebug("blocksize = %u", blocksize);
    if (err) {
        blocksize = length;
    }

    blocksize = (127 << 16) + 6;
    err = ioctl(m_fd, SNDCTL_DSP_SETFRAGMENT, &blocksize);
#endif

    // determine the timeout for reading, use safety factor 2
    int rate = Kwave::toInt(sampleRate());
    if (rate < 1) rate = 1;

    unsigned int timeout = (length / rate) * 2;
    if (timeout < 2) timeout = 2;
    quint8 *buf = reinterpret_cast<quint8 *>(buffer.data()) + offset;

    int mask = 0;
    retval = ioctl(m_fd, SNDCTL_DSP_SETTRIGGER, &mask);
    Q_ASSERT(!retval);
    mask = PCM_ENABLE_INPUT;
    retval = ioctl(m_fd, SNDCTL_DSP_SETTRIGGER, &mask);
    Q_ASSERT(!retval);

    while (length) {
        FD_ZERO(&rfds);
        FD_SET(m_fd, &rfds);

        tv.tv_sec  = timeout;
        tv.tv_usec = 0;
        retval = select(m_fd+1, &rfds, nullptr, nullptr, &tv);

        if (retval == -1) {
            if (errno == EINTR)
                return -errno; // return without warning

            qWarning("RecordOSS::read() - select() failed errno=%d (%s)",
                     errno, strerror(errno));
            return -errno;
        } else if (retval) {
            ssize_t res = ::read(m_fd, buf, length);

            if ((res == -1) && (errno == EINTR))
                return -errno; // interrupted, return without warning

            if ((res == -1) && (errno == EAGAIN))
                continue;

            if (res < 0) {
                qWarning("RecordOSS::read() - read error %d (%s)",
                         errno, strerror(errno));
                return read_bytes;
            }
            read_bytes += res;
            length -= res;
            buf += res;
        } else {
            qWarning("No data within 5 seconds.\n");
            return -EIO;
        }
    }

    return read_bytes;
}

//***************************************************************************
int Kwave::RecordOSS::close()
{
    if (m_fd < 0) return 0; // already closed
    ::close(m_fd);
    m_fd = -1;
    m_oss_version = -1;

    return 0;
}

//***************************************************************************
static bool addIfExists(QStringList &list, const QString &name)
{
    QFile file;

    if (name.contains(_("%1"))) {
        // test for the name without suffix first
        addIfExists(list, name.arg(_("")));

        // loop over the list and try until a suffix does not exist
        for (unsigned int index = 0; index < 64; index++)
            addIfExists(list, name.arg(index));
    } else {
        // check a single name
        file.setFileName(name);
        if (!file.exists())
            return false;

        if (!list.contains(name))
            list.append(name);
    }

    return true;
}

//***************************************************************************
static void scanFiles(QStringList &list, const QString &dirname,
                      const QString &mask)
{
    QStringList files;
    QDir dir;

    dir.setPath(dirname);
    dir.setNameFilters(mask.split(QLatin1Char(' ')));
    dir.setFilter(QDir::Files | QDir::Readable | QDir::System);
    dir.setSorting(QDir::Name);
    files = dir.entryList();

    for (QString &it : files) {
        QString devicename = dirname + QDir::separator() + it;
        addIfExists(list, devicename);
    }
}

//***************************************************************************
static void scanDirectory(QStringList &list, const QString &dir)
{
    scanFiles(list, dir, _("*audio*"));
    scanFiles(list, dir, _("adsp*"));
    scanFiles(list, dir, _("dsp*"));
    scanFiles(list, dir, _("dio*"));
    scanFiles(list, dir, _("pcm*"));
}

//***************************************************************************
QStringList Kwave::RecordOSS::supportedDevices()
{
    QStringList list, dirlist;

    scanDirectory(list, _("/dev"));
    scanDirectory(list, _("/dev/sound"));
    scanFiles(dirlist, _("/dev/oss"), _("[^.]*"));
    for (QString dir : dirlist)
        scanDirectory(list, dir);
    list.append(_("#EDIT#"));
    list.append(_("#SELECT#"));

    return list;
}

//***************************************************************************
QString Kwave::RecordOSS::fileFilter()
{
    QString filter;

    filter += _("audio*|") + i18n("OSS recording device (audio*)");

    if (filter.length()) filter += _("\n");
    filter += _("dsp*|")   + i18n("OSS recording device (dsp*)");

    if (filter.length()) filter += _("\n");
    filter += _("adsp*|")  + i18n("ALSA recording device (adsp*)");

    if (filter.length()) filter += _("\n");
    filter += _("*|")      + i18n("Any device (*)");

    return filter;
}

//***************************************************************************
int Kwave::RecordOSS::detectTracks(unsigned int &min, unsigned int &max)
{
    Q_ASSERT(m_fd >= 0);
    unsigned int t;
    int err = -1;

    // preset
    min = 0;
    max = 0;

    // find the smalles number of tracks, limit to MAX_CHANNELS
    for (t = 1; t < MAX_CHANNELS; t++) {
        int real_tracks = t;
        err = ioctl(m_fd, SNDCTL_DSP_CHANNELS, &real_tracks);
        if ((err >= 0) && (real_tracks == Kwave::toInt(t))) {
            min = real_tracks;
            break;
        }
    }
    if (t >= MAX_CHANNELS) {
        // no minimum track number found :-o
        qWarning("no minimum track number found, err=%d", err);
        min = 0;
        max = 0;
        return err;
    }

    // find the highest number of tracks, start from MAX_CHANNELS downwards
    max = min;
    for (t = MAX_CHANNELS; t >= min; t--) {
        int real_tracks = t;
        err = ioctl(m_fd, SNDCTL_DSP_CHANNELS, &real_tracks);
        if ((err >= 0) && (real_tracks == Kwave::toInt(t))) {
            max = real_tracks;
            break;
        }
    }
    m_tracks = max;

    qDebug("RecordOSS::detectTracks, min=%u, max=%u", min, max);

    return (max > 0) ? 0 : -1;
}

//***************************************************************************
int Kwave::RecordOSS::setTracks(unsigned int &tracks)
{
    Q_ASSERT(m_fd >= 0);

    // set the number of tracks in the device (must already be opened)
    int t = tracks;
    int err = ioctl(m_fd, SNDCTL_DSP_CHANNELS, &t);
    if (err < 0) return err;

    m_tracks = t;
    // return the number of tracks if succeeded
    tracks = t;

    return 0;
}

//***************************************************************************
int Kwave::RecordOSS::tracks()
{
    return m_tracks;
}

//***************************************************************************
QList<double> Kwave::RecordOSS::detectSampleRates()
{
    QList<double> list;
    Q_ASSERT(m_fd >= 0);

    static const int known_rates[] = {
          1000, // (just for testing)
          2000, // (just for testing)
          4000, // standard OSS
          5125, // seen in Harmony driver (HP712, 715/new)
          5510, // seen in AD1848 driver
          5512, // seen in ES1370 driver
          6215, // seen in ES188X driver
          6615, // seen in Harmony driver (HP712, 715/new)
          6620, // seen in AD1848 driver
          7350, // seen in AWACS and Burgundy sound driver
          8000, // standard OSS
          8820, // seen in AWACS and Burgundy sound driver
          9600, // seen in AD1848 driver
         11025, // soundblaster
         14700, // seen in AWACS and Burgundy sound driver
         16000, // standard OSS
         17640, // seen in AWACS and Burgundy sound driver
         18900, // seen in Harmony driver (HP712, 715/new)
         22050, // soundblaster
         24000, // seen in NM256 driver
         27428, // seen in Harmony driver (HP712, 715/new)
         29400, // seen in AWACS and Burgundy sound driver
         32000, // standard OSS
         32768, // seen in CS4299 driver
         33075, // seen in Harmony driver (HP712, 715/new)
         37800, // seen in Harmony driver (HP712, 715/new)
         44100, // soundblaster
         48000, // AC97
         64000, // AC97
         88200, // seen in RME96XX driver
         96000, // AC97
        128000, // (just for testing)
        176400, // Envy24ht
        192000, // AC97
        196000, // (just for testing)
        200000, // Lynx2
        256000  // (just for testing)
    };

    // try all known sample rates
    for (unsigned int i=0; i < sizeof(known_rates)/sizeof(int); i++) {
        int rate = known_rates[i];
        int err = ioctl(m_fd, SNDCTL_DSP_SPEED, &rate);
        if (err < 0) {
//          qDebug("RecordOSS::detectSampleRates(): "
//                 "sample rate %d Hz not supported", known_rates[i]);
            continue;
        }

        // do not produce duplicates
        bool is_duplicate = false;
        for (const double &r : list)
            if (qFuzzyCompare(rate, r)) { is_duplicate = true; break; }
        if (is_duplicate) continue;

        // qDebug("found rate %d Hz", rate);
        list.append(rate);
        m_rate = rate;
    }

    return list;
}

//***************************************************************************
int Kwave::RecordOSS::setSampleRate(double &new_rate)
{
    Q_ASSERT(m_fd >= 0);
    // OSS supports only integer rates
    int rate = Kwave::toInt(rint(new_rate));

    // set the rate of the device (must already be opened)
    int err = ioctl(m_fd, SNDCTL_DSP_SPEED, &rate);
    if (err < 0) return err;

    m_rate = rate;
    // return the sample rate if succeeded
    new_rate = static_cast<double>(rate);

    return 0;
}

//***************************************************************************
double Kwave::RecordOSS::sampleRate()
{
    Q_ASSERT(m_fd >= 0);

    return m_rate;
}

//***************************************************************************
void Kwave::RecordOSS::format2mode(int format,
                                   Kwave::Compression::Type &compression,
                                   int &bits,
                                   Kwave::SampleFormat::Format &sample_format)
{

    switch (format) {
        case AFMT_MU_LAW:
            compression   = Kwave::Compression::G711_ULAW;
            sample_format = Kwave::SampleFormat::Signed;
            bits          = 16;
            break;
        case AFMT_A_LAW:
            compression   = Kwave::Compression::G711_ALAW;
            sample_format = Kwave::SampleFormat::Unsigned;
            bits          = 16;
            break;
        case AFMT_IMA_ADPCM:
            compression   = Kwave::Compression::MS_ADPCM;
            sample_format = Kwave::SampleFormat::Signed;
            bits          = 16;
            break;
        case AFMT_U8:
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Unsigned;
            bits          = 8;
            break;
        case AFMT_S16_LE: /* FALLTHROUGH */
        case AFMT_S16_BE:
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Signed;
            bits          = 16;
            break;
        case AFMT_S8:
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Signed;
            bits          = 8;
            break;
        case AFMT_U16_LE: /* FALLTHROUGH */
        case AFMT_U16_BE:
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Unsigned;
            bits          = 16;
            break;
        case AFMT_MPEG:
            compression   = Kwave::Compression::MPEG_LAYER_II;
            sample_format = Kwave::SampleFormat::Signed;
            bits          = 16;
            break;
#if 0
        case AFMT_AC3: /* Dolby Digital AC3 */
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Unknown;
            bits          = 16;
            break;
#endif
        case AFMT_S24_LE: /* FALLTHROUGH */
        case AFMT_S24_BE:
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Signed;
            bits          = 24;
            break;
        case AFMT_S32_LE: /* FALLTHROUGH */
        case AFMT_S32_BE:
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Signed;
            bits          = 32;
            break;
        default:
            compression   = Kwave::Compression::NONE;
            sample_format = Kwave::SampleFormat::Unknown;
            bits          = -1;
    }

}

//***************************************************************************
int Kwave::RecordOSS::mode2format(Kwave::Compression::Type compression,
                                  int bits,
                                  Kwave::SampleFormat::Format sample_format)
{
    // first level: compression
    if (compression == Kwave::Compression::G711_ULAW) return AFMT_MU_LAW;
    if (compression == Kwave::Compression::G711_ALAW) return AFMT_A_LAW;
    if (compression == Kwave::Compression::MS_ADPCM)  return AFMT_IMA_ADPCM;
    if (compression == static_cast<int>(Kwave::Compression::MPEG_LAYER_II))
        return AFMT_MPEG;

    // non-compressed: switch by sample format
    if ((sample_format == Kwave::SampleFormat::Unsigned) && (bits == 8))
        return AFMT_U8;
    if ((sample_format == Kwave::SampleFormat::Signed) && (bits == 8))
        return AFMT_S8;

    // get supported modes, maybe one endianness is not supported
    int mask = 0;
    int err = ioctl(m_fd, SNDCTL_DSP_GETFMTS, &mask);
    if (err < 0) return bits;

    // unsigned 16 bit mode
    // note: we prefer the machine's native endianness if both are supported !
    if ((sample_format == Kwave::SampleFormat::Unsigned) && (bits == 16)) {
        mask &= (AFMT_U16_LE | AFMT_U16_BE);
        if (mask != (AFMT_U16_LE | AFMT_U16_BE)) return mask;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
        return AFMT_U16_BE;
#else
        return AFMT_U16_LE;
#endif
    }

    // signed 16 bit mode
    if ((sample_format == Kwave::SampleFormat::Signed) && (bits == 16)) {
        mask &= (AFMT_S16_LE | AFMT_S16_BE);
        if (mask != (AFMT_S16_LE | AFMT_S16_BE)) return mask;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
        return AFMT_S16_BE;
#else
        return AFMT_S16_LE;
#endif
    }

    if ((sample_format == Kwave::SampleFormat::Signed) && (bits == 24)) {
        mask &= (AFMT_S24_LE | AFMT_S24_BE);
        if (mask != (AFMT_S24_LE | AFMT_S24_BE)) return mask;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
        return AFMT_S24_BE;
#else
        return AFMT_S24_LE;
#endif
    }

    if ((sample_format == Kwave::SampleFormat::Signed) && (bits == 32)) {
        mask &= (AFMT_S32_LE | AFMT_S32_BE);
        if (mask != (AFMT_S32_LE | AFMT_S32_BE)) return mask;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
        return AFMT_S32_BE;
#else
        return AFMT_S32_LE;
#endif
    }

    qWarning("RecordOSS: unknown format: sample_format=%d, bits=%d",
             static_cast<int>(sample_format), bits);
    return 0;
}

//***************************************************************************
QList<Kwave::Compression::Type> Kwave::RecordOSS::detectCompressions()
{
    Q_ASSERT(m_fd >= 0);
    QList<Kwave::Compression::Type> compressions;
    int err = 0;
    int mask = AFMT_QUERY;

    err = ioctl(m_fd, SNDCTL_DSP_GETFMTS, &mask);
    if (err < 0) return compressions;

    if (mask & AFMT_MPEG)    compressions += Kwave::Compression::MPEG_LAYER_II;
    if (mask & AFMT_A_LAW)   compressions += Kwave::Compression::G711_ALAW;
    if (mask & AFMT_MU_LAW)  compressions += Kwave::Compression::G711_ULAW;
    if (mask & AFMT_IMA_ADPCM) compressions += Kwave::Compression::MS_ADPCM;
    if (mask & (AFMT_U16_LE | AFMT_U16_BE | AFMT_S16_LE | AFMT_S16_BE |
                AFMT_S24_LE | AFMT_S24_BE | AFMT_S32_LE | AFMT_S32_BE |
                AFMT_S8 | AFMT_U8))
        compressions += Kwave::Compression::NONE;

    return compressions;
}

//***************************************************************************
int Kwave::RecordOSS::setCompression(Kwave::Compression::Type new_compression)
{
    Q_ASSERT(m_fd >= 0);
    Kwave::Compression::Type comp;
    int bits, format = AFMT_QUERY;
    Kwave::SampleFormat::Format sample_format;

    // read back current format
    int err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &format);
    if (err < 0) return -1;
    format2mode(format, comp, bits, sample_format);

    // modify the compression
    comp = new_compression;

    // activate new format
    format = mode2format(comp, bits, sample_format);
    err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &format);
    if (err < 0) return -1;

    return 0;
}

//***************************************************************************
Kwave::Compression::Type Kwave::RecordOSS::compression()
{
    Q_ASSERT(m_fd >= 0);
    int mask = AFMT_QUERY;
    int err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &mask);
    if (err < 0) return Kwave::Compression::NONE;

    Kwave::Compression::Type c;
    int b;
    Kwave::SampleFormat::Format s;
    format2mode(mask, c, b, s);
    return c;
}

//***************************************************************************
QList<unsigned int> Kwave::RecordOSS::supportedBits()
{
    Q_ASSERT(m_fd >= 0);
    QList<unsigned int> bits;
    bits.clear();
    int err = 0;
    int mask = AFMT_QUERY;

    err = ioctl(m_fd, SNDCTL_DSP_GETFMTS, &mask);
    if (err < 0) return bits;

    // mask out all modes that do not match the current compression
    const int comp = this->compression();
    for (unsigned int bit=0; bit < (sizeof(mask) << 3); bit++) {
        if (!(mask & (1U << bit))) continue;

        // format is supported, split into compression, bits, sample format
        Kwave::Compression::Type c(Kwave::Compression::NONE);
        int b;
        Kwave::SampleFormat::Format s;
        format2mode(1U << bit, c, b, s);
        if (b < 0) continue; // unknown -> skip

        // take the mode if compression matches and it is not already known
        if ((c == comp) && !(bits.contains(b))) {
            bits += b;
        }
    }

#if 0
    if (mask | AFMT_AC3)
        qDebug("RecordOSS: your device supports AC3 which is not "\
               "yet supported, sorry :-(");
#endif
    return bits;
}

//***************************************************************************
int Kwave::RecordOSS::setBitsPerSample(unsigned int new_bits)
{
    Q_ASSERT(m_fd >= 0);
    Kwave::Compression::Type comp;
    int bits, format = AFMT_QUERY;
    Kwave::SampleFormat::Format sample_format;

    // read back current format
    int err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &format);
    if (err < 0) return err;
    format2mode(format, comp, bits, sample_format);

    // modify the bits per sample
    bits = new_bits;

    // activate new format
    int oldformat = format;
    format = mode2format(comp, bits, sample_format);
    err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &format);
    if (err < 0) return err;
    if (oldformat != format) return -1;

    return 0;
}

//***************************************************************************
int Kwave::RecordOSS::bitsPerSample()
{
    Q_ASSERT(m_fd >= 0);
    int mask = AFMT_QUERY;
    int err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &mask);
    if (err < 0) return err;

    Kwave::Compression::Type c;
    int b;
    Kwave::SampleFormat::Format s;
    format2mode(mask, c, b, s);
    return b;
}

//***************************************************************************
QList<Kwave::SampleFormat::Format> Kwave::RecordOSS::detectSampleFormats()
{
    Q_ASSERT(m_fd >= 0);
    QList<Kwave::SampleFormat::Format> formats;
    formats.clear();
    int err = 0;
    int mask = AFMT_QUERY;

    err = ioctl(m_fd, SNDCTL_DSP_GETFMTS, &mask);
    if (err < 0) return formats;

    // mask out all modes that do not match the current compression
    // and bits per sample
    const Kwave::Compression::Type comp = this->compression();
    const int bits_per_sample = this->bitsPerSample();
    for (unsigned int bit = 0; bit < (sizeof(mask) << 3U); bit++) {
        if (!(mask & (1U << bit))) continue;

        // format is supported, split into compression, bits, sample format
        Kwave::Compression::Type c;
        int b;
        Kwave::SampleFormat::Format s;
        format2mode(1U << bit, c, b, s);
        if (c < 0) continue; // unknown -> skip

        if ((c == comp) && (b == bits_per_sample)) {
            // this mode matches -> append it if not already known
            if (!formats.contains(s)) formats += s;
        }
    }

    return formats;
}

//***************************************************************************
int Kwave::RecordOSS::setSampleFormat(Kwave::SampleFormat::Format new_format)
{
    Q_ASSERT(m_fd >= 0);
    Kwave::Compression::Type comp;
    int bits, format = AFMT_QUERY;
    Kwave::SampleFormat::Format sample_format;

    // read back current format
    int err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &format);
    if (err < 0) return err;
    format2mode(format, comp, bits, sample_format);

    // modify the sample format
    sample_format = new_format;

    // activate new format
    int oldformat = format;
    format = mode2format(comp, bits, sample_format);
    err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &format);
    if (err < 0) return err;
    if (oldformat != format) return -1;

    return 0;
}

//***************************************************************************
Kwave::SampleFormat::Format Kwave::RecordOSS::sampleFormat()
{
    Q_ASSERT(m_fd >= 0);
    int mask = AFMT_QUERY;
    int err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &mask);
    if (err < 0) return Kwave::SampleFormat::Unknown;

    Kwave::Compression::Type c;
    int b;
    Kwave::SampleFormat::Format s;
    format2mode(mask, c, b, s);
    return s;
}

//***************************************************************************
Kwave::byte_order_t Kwave::RecordOSS::endianness()
{
    Q_ASSERT(m_fd >= 0);
    int mask = AFMT_QUERY;
    int err = ioctl(m_fd, SNDCTL_DSP_SETFMT, &mask);
    if (err < 0) return Kwave::UnknownEndian;

    if (mask & (AFMT_U16_LE | AFMT_S16_LE | AFMT_S24_LE | AFMT_S32_LE))
        return Kwave::LittleEndian;

    if (mask & (AFMT_U16_BE | AFMT_S16_BE | AFMT_S24_BE | AFMT_S32_BE))
        return Kwave::BigEndian;

    if (mask & (AFMT_S8 | AFMT_U8))
        return Kwave::CpuEndian;

    return Kwave::UnknownEndian;
}

#endif /* HAVE_OSS_SUPPORT */

//***************************************************************************
//***************************************************************************
