/* GStreamer OSS4 audio plugin
 * Copyright (C) 2007-2008 Tim-Philipp Müller <tim centricular net>
 *
 * This library 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 library 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 library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
 * with newer GLib versions (>= 2.31.0) */
#define GLIB_DISABLE_DEPRECATION_WARNINGS

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#include "gst/gst-i18n-plugin.h"
#include <gst/audio/audio.h>

#include "oss4-audio.h"
#include "oss4-property-probe.h"
#include "oss4-sink.h"
#include "oss4-source.h"
#include "oss4-soundcard.h"

GST_DEBUG_CATEGORY (oss4mixer_debug);
GST_DEBUG_CATEGORY (oss4sink_debug);
GST_DEBUG_CATEGORY (oss4src_debug);
GST_DEBUG_CATEGORY (oss4_debug);

#define GST_CAT_DEFAULT oss4_debug

typedef struct
{
  const GstAudioRingBufferFormatType gst_rbfmt;
  const GstAudioFormat gst_rfmt;
  const gint oss_fmt;
  const gchar name[16];
} GstOss4AudioFormat;

/* *INDENT-OFF* */
static const GstOss4AudioFormat fmt_map[] = {
  /* note: keep sorted by preference, prefered formats first */
  {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MU_LAW, 0,
      AFMT_MU_LAW, "audio/x-mulaw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_A_LAW, 0,
      AFMT_A_LAW, "audio/x-alaw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S32LE,
      AFMT_S32_LE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S32BE,
      AFMT_S32_BE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S24_32LE,
      AFMT_S24_LE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S24_32BE,
      AFMT_S24_BE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S24LE,
      AFMT_S24_PACKED, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S16LE,
      AFMT_S16_LE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S16BE,
      AFMT_S16_BE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_U16LE,
      AFMT_U16_LE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_U16BE,
      AFMT_U16_BE, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_S8,
      AFMT_S8, "audio/x-raw"}, {
  GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, GST_AUDIO_FORMAT_U8,
      AFMT_U8, "audio/x-raw"}
};
/* *INDENT-ON* */

/* formats we assume the OSS4 layer can always handle and convert internally */
#define CONVERTIBLE_FORMATS (   \
    AFMT_MU_LAW | AFMT_A_LAW |  \
    AFMT_S32_LE | AFMT_S32_BE | \
    AFMT_S24_LE | AFMT_S24_BE | \
    AFMT_S24_PACKED |           \
    AFMT_S16_LE | AFMT_S16_BE | \
    AFMT_U16_LE | AFMT_U16_BE | \
    AFMT_S8 | AFMT_U8 )

static void
gst_oss4_append_format_to_caps (const GstOss4AudioFormat * fmt, GstCaps * caps)
{
  GstStructure *s;

  s = gst_structure_new_empty (fmt->name);
  if (fmt->gst_rbfmt == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW) {
    gst_structure_set (s, "format", G_TYPE_STRING,
        gst_audio_format_to_string (fmt->gst_rfmt),
        "layout", G_TYPE_STRING, "interleaved", NULL);
  }
  gst_caps_append_structure (caps, s);
}

static gint
gst_oss4_audio_get_oss_format (GstAudioRingBufferFormatType fmt,
    GstAudioFormat rfmt)
{
  guint i;

  for (i = 0; i < G_N_ELEMENTS (fmt_map); ++i) {
    if (fmt_map[i].gst_rbfmt == fmt && fmt_map[i].gst_rfmt == rfmt)
      return fmt_map[i].oss_fmt;
  }
  return 0;
}

/* These are pretty random */
#define GST_OSS4_MIN_SAMPLE_RATE 1
#define GST_OSS4_MAX_SAMPLE_RATE 192000

static gboolean
gst_oss4_audio_detect_rates (GstObject * obj, oss_audioinfo * ai,
    GstCaps * caps)
{
  GValue val = { 0, };
  int minrate, maxrate, i;

  minrate = ai->min_rate;
  maxrate = ai->max_rate;

  /* sanity check */
  if (minrate > maxrate) {
    GST_WARNING_OBJECT (obj, "min_rate %d > max_rate %d (buggy driver?)",
        minrate, maxrate);
    maxrate = ai->min_rate;     /* swap */
    minrate = ai->max_rate;
  }

  /* limit to something sensible */
  if (minrate < GST_OSS4_MIN_SAMPLE_RATE)
    minrate = GST_OSS4_MIN_SAMPLE_RATE;
  if (maxrate > GST_OSS4_MAX_SAMPLE_RATE)
    maxrate = GST_OSS4_MAX_SAMPLE_RATE;

  if (maxrate < GST_OSS4_MIN_SAMPLE_RATE) {
    GST_WARNING_OBJECT (obj, "max_rate < %d, which makes no sense",
        GST_OSS4_MIN_SAMPLE_RATE);
    return FALSE;
  }

  GST_LOG_OBJECT (obj, "min_rate %d, max_rate %d (originally: %d, %d)",
      minrate, maxrate, ai->min_rate, ai->max_rate);

  if ((ai->caps & PCM_CAP_FREERATE)) {
    GST_LOG_OBJECT (obj, "device supports any sample rate between min and max");
    if (minrate == maxrate) {
      g_value_init (&val, G_TYPE_INT);
      g_value_set_int (&val, maxrate);
    } else {
      g_value_init (&val, GST_TYPE_INT_RANGE);
      gst_value_set_int_range (&val, minrate, maxrate);
    }
  } else {
    GST_LOG_OBJECT (obj, "%d sample rates:", ai->nrates);
    g_value_init (&val, GST_TYPE_LIST);
    for (i = 0; i < ai->nrates; ++i) {
      GST_LOG_OBJECT (obj, " rate: %d", ai->rates[i]);

      if (ai->rates[i] >= minrate && ai->rates[i] <= maxrate) {
        GValue rate_val = { 0, };

        g_value_init (&rate_val, G_TYPE_INT);
        g_value_set_int (&rate_val, ai->rates[i]);
        gst_value_list_append_value (&val, &rate_val);
        g_value_unset (&rate_val);
      }
    }

    if (gst_value_list_get_size (&val) == 0) {
      g_value_unset (&val);
      return FALSE;
    }
  }

  for (i = 0; i < gst_caps_get_size (caps); ++i) {
    GstStructure *s;

    s = gst_caps_get_structure (caps, i);
    gst_structure_set_value (s, "rate", &val);
  }

  g_value_unset (&val);

  return TRUE;
}

static void
gst_oss4_audio_get_channel_layout (GstObject * obj, guint64 layout,
    guint num_channels, GstAudioChannelPosition * ch_layout)
{
  const GstAudioChannelPosition pos_map[16] = {
    GST_AUDIO_CHANNEL_POSITION_NONE,    /* 0 = dunno          */
    GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,      /* 1 = left           */
    GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,     /* 2 = right          */
    GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,    /* 3 = center         */
    GST_AUDIO_CHANNEL_POSITION_LFE1,    /* 4 = lfe            */
    GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,       /* 5 = left surround  */
    GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT,      /* 6 = right surround */
    GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,       /* 7 = left rear      */
    GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,      /* 8 = right rear     */
    GST_AUDIO_CHANNEL_POSITION_NONE,
    GST_AUDIO_CHANNEL_POSITION_NONE,
    GST_AUDIO_CHANNEL_POSITION_NONE,
    GST_AUDIO_CHANNEL_POSITION_NONE,
    GST_AUDIO_CHANNEL_POSITION_NONE,
    GST_AUDIO_CHANNEL_POSITION_NONE,
    GST_AUDIO_CHANNEL_POSITION_NONE
  };
  guint speaker_pos;            /* speaker position as defined by OSS */
  guint i;

  for (i = 0; i < num_channels; ++i) {
    /* layout contains up to 16 speaker positions, with each taking up 4 bits */
    speaker_pos = (guint) ((layout >> (i * 4)) & 0x0f);

    /* if it's a channel position that's unknown to us, set all to NONE and
     * bail out */
    if (G_UNLIKELY (pos_map[speaker_pos] == GST_AUDIO_CHANNEL_POSITION_NONE))
      goto no_layout;

    ch_layout[i] = pos_map[speaker_pos];
  }

  return;

no_layout:
  {
    /* only warn if it's really unknown, position 0 is ok and represents NONE
     * (in which case we also just set all others to NONE ignoring the other
     * positions in the OSS-given layout, because that's what we currently
     * require in GStreamer) */
    if (speaker_pos != 0) {
      GST_WARNING_OBJECT (obj, "unknown OSS channel position %x", ch_layout[i]);
    }
    for (i = 0; i < num_channels; ++i) {
      ch_layout[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
    }
    return;
  }
}

static void
gst_oss4_audio_set_ringbuffer_channel_layout (GstObject * obj, gint fd,
    GstAudioRingBufferSpec * spec)
{
  guint num_channels;
  guint64 layout = 0;
  GstAudioRingBuffer *rb;
  GstAudioChannelPosition ch_layout[8] = { 0, };

  num_channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
  if (num_channels < 3 || num_channels > 8)
    return;

  if (spec->type != GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW)
    return;

  if (GST_IS_OSS4_SINK (obj)) {
    rb = GST_AUDIO_BASE_SINK (obj)->ringbuffer;
  } else if (GST_IS_OSS4_SOURCE (obj)) {
    rb = GST_AUDIO_BASE_SRC (obj)->ringbuffer;
  } else
    g_return_if_reached ();

  /* -1 = get info for currently open device (fd). This will fail with
   * OSS build <= 1013 because of a bug in OSS */
  if (ioctl (fd, SNDCTL_DSP_GET_CHNORDER, &layout) == -1) {
    GST_WARNING_OBJECT (obj, "couldn't query channel layout, assuming default");
    layout = CHNORDER_NORMAL;
  }
  GST_DEBUG_OBJECT (obj, "channel layout: %08" G_GINT64_MODIFIER "x", layout);


  gst_oss4_audio_get_channel_layout (obj, layout, num_channels, ch_layout);
  gst_audio_ring_buffer_set_channel_positions (rb, ch_layout);

  return;
}

static void
gst_oss4_audio_add_channel_layout (GstObject * obj, guint64 layout,
    guint num_channels, GstStructure * s)
{
  GstAudioChannelPosition ch_layout[8] = { 0, };
  guint64 mask;

  g_return_if_fail (num_channels <= G_N_ELEMENTS (ch_layout));

  gst_oss4_audio_get_channel_layout (obj, layout, num_channels, ch_layout);
  if (gst_audio_channel_positions_to_mask (ch_layout, num_channels, FALSE,
          &mask))
    gst_structure_set (s, "channel-mask", GST_TYPE_BITMASK, mask, NULL);

  return;
}

/* arbitrary max. limit */
#define GST_OSS4_MIN_CHANNELS 1
#define GST_OSS4_MAX_CHANNELS 4096

/* takes ownership of the input caps */
static GstCaps *
gst_oss4_audio_detect_channels (GstObject * obj, int fd, oss_audioinfo * ai,
    GstCaps * in_caps)
{
  const gchar *forced_layout;
  GstStructure *s = NULL;
  guint64 layout = 0;
  GstCaps *chan_caps = NULL;
  GstCaps *out_caps = NULL;
  int minchans, maxchans;
  int c, i, j;

  /* GST_OSS4_CHANNEL_LAYOUT environment variable: may be used to force a
   * particular channel layout (if it contains an odd number of channel
   * positions it will also make us advertise a channel layout for that
   * channel count, even if we'd usually skip it; this is especially useful
   * for folks with 2.1 speakers, I guess) */
  forced_layout = g_getenv ("GST_OSS4_CHANNEL_LAYOUT");

  minchans = ai->min_channels;
  maxchans = ai->max_channels;

  /* sanity check */
  if (minchans > maxchans) {
    GST_WARNING_OBJECT (obj, "min_chans %d > max_chans %d (buggy driver?)",
        minchans, maxchans);
    maxchans = ai->min_channels;        /* swap */
    minchans = ai->max_channels;
  }

  /* limit to something sensible */
  if (minchans < GST_OSS4_MIN_CHANNELS)
    minchans = GST_OSS4_MIN_CHANNELS;
  if (maxchans > GST_OSS4_MAX_CHANNELS)
    maxchans = GST_OSS4_MAX_CHANNELS;

  if (maxchans < GST_OSS4_MIN_CHANNELS) {
    GST_WARNING_OBJECT (obj, "max_chans < %d, which makes no sense",
        GST_OSS4_MIN_CHANNELS);
    gst_caps_unref (in_caps);
    return NULL;
  }

  GST_LOG_OBJECT (obj, "min_channels %d, max_channels %d (originally: %d, %d)",
      minchans, maxchans, ai->min_channels, ai->max_channels);

  chan_caps = gst_caps_new_empty ();

  /* first do the simple cases: mono + stereo (channel layout implied) */
  if (minchans == 1 && maxchans == 1)
    s = gst_structure_new ("x", "channels", G_TYPE_INT, 1, NULL);
  else if (minchans == 2 && maxchans >= 2)
    s = gst_structure_new ("x", "channels", G_TYPE_INT, 2, NULL);
  else if (minchans == 1 && maxchans >= 2)
    s = gst_structure_new ("x", "channels", GST_TYPE_INT_RANGE, 1, 2, NULL);
  gst_caps_append_structure (chan_caps, s);
  s = NULL;

  /* TODO: we assume all drivers use a left/right layout for stereo here */
  if (maxchans <= 2)
    goto done;

  if (ioctl (fd, SNDCTL_DSP_GET_CHNORDER, &layout) == -1) {
    GST_WARNING_OBJECT (obj, "couldn't query channel layout, assuming default");
    layout = CHNORDER_NORMAL;
  }
  GST_DEBUG_OBJECT (obj, "channel layout: %08" G_GINT64_MODIFIER "x", layout);

  /* e.g. forced 2.1 layout would be GST_OSS4_CHANNEL_LAYOUT=421 */
  if (forced_layout != NULL && *forced_layout != '\0') {
    guint layout_len;

    layout_len = strlen (forced_layout);
    if (layout_len >= minchans && layout_len <= maxchans) {
      layout = g_ascii_strtoull (forced_layout, NULL, 16);
      maxchans = layout_len;
      GST_DEBUG_OBJECT (obj, "forced channel layout: %08" G_GINT64_MODIFIER "x"
          " ('%s'), maxchans now %d", layout, forced_layout, maxchans);
    } else {
      GST_WARNING_OBJECT (obj, "ignoring forced channel layout: layout has %d "
          "channel positions but maxchans is %d", layout_len, maxchans);
    }
  }

  /* need to advertise channel layouts for anything >2 and <=8 channels */
  for (c = MAX (3, minchans); c <= MIN (maxchans, 8); c++) {
    /* "The min_channels and max_channels fields define the limits for the
     * number of channels. However some devices don't support all channels
     * within this range. It's possible that the odd values (3, 5, 7, 9, etc).
     * are not supported. There is currently no way to check for this other
     * than checking if SNDCTL_DSP_CHANNELS accepts the requested value.
     * Another approach is trying to avoid using odd number of channels."
     *
     * So, we don't know for sure if these odd values are supported:
     */
    if ((c == 3 || c == 5 || c == 7) && (c != maxchans)) {
      GST_LOG_OBJECT (obj, "not adding layout with %d channels", c);
      continue;
    }

    s = gst_structure_new ("x", "channels", G_TYPE_INT, c, NULL);
    gst_oss4_audio_add_channel_layout (obj, layout, c, s);
    GST_LOG_OBJECT (obj, "c=%u, appending struct %" GST_PTR_FORMAT, c, s);
    gst_caps_append_structure (chan_caps, s);
    s = NULL;
  }

  if (maxchans <= 8)
    goto done;

  /* for everything >8 channels, CHANNEL_POSITION_NONE is implied. */
  if (minchans == maxchans || maxchans == 9) {
    s = gst_structure_new ("x", "channels", G_TYPE_INT, maxchans, NULL);
  } else {
    s = gst_structure_new ("x", "channels", GST_TYPE_INT_RANGE,
        MAX (9, minchans), maxchans, NULL);
  }
  gst_caps_append_structure (chan_caps, s);
  s = NULL;

done:

  GST_LOG_OBJECT (obj, "channel structures: %" GST_PTR_FORMAT, chan_caps);

  out_caps = gst_caps_new_empty ();

  /* combine each structure in the input caps with each channel caps struct */
  for (i = 0; i < gst_caps_get_size (in_caps); ++i) {
    const GstStructure *in_s;

    in_s = gst_caps_get_structure (in_caps, i);

    for (j = 0; j < gst_caps_get_size (chan_caps); ++j) {
      const GstStructure *chan_s;
      const GValue *val;

      s = gst_structure_copy (in_s);
      chan_s = gst_caps_get_structure (chan_caps, j);
      if ((val = gst_structure_get_value (chan_s, "channels")))
        gst_structure_set_value (s, "channels", val);
      if ((val = gst_structure_get_value (chan_s, "channel-mask")))
        gst_structure_set_value (s, "channel-mask", val);

      gst_caps_append_structure (out_caps, s);
      s = NULL;
    }
  }

  gst_caps_unref (in_caps);
  gst_caps_unref (chan_caps);
  return out_caps;
}

GstCaps *
gst_oss4_audio_probe_caps (GstObject * obj, int fd)
{
  oss_audioinfo ai = { 0, };
  gboolean output;
  GstCaps *caps;
  int nonnative_formats = 0;
  int formats, i;

  output = GST_IS_OSS4_SINK (obj);

  /* -1 = get info for currently open device (fd). This will fail with
   * OSS build <= 1013 because of a bug in OSS */
  ai.dev = -1;
  if (ioctl (fd, SNDCTL_ENGINEINFO, &ai) == -1)
    goto engineinfo_failed;

  formats = (output) ? ai.oformats : ai.iformats;

  GST_LOG_OBJECT (obj, "%s formats : 0x%08x", (output) ? "out" : "in", formats);

  caps = gst_caps_new_empty ();

  /* first list all the formats natively supported */
  for (i = 0; i < G_N_ELEMENTS (fmt_map); ++i) {
    if ((formats & fmt_map[i].oss_fmt)) {
      gst_oss4_append_format_to_caps (&fmt_map[i], caps);
    } else if ((fmt_map[i].oss_fmt & CONVERTIBLE_FORMATS)) {
      nonnative_formats |= fmt_map[i].oss_fmt;
    }
  }

  GST_LOG_OBJECT (obj, "adding non-native %s formats : 0x%08x",
      (output) ? "out" : "in", nonnative_formats);

  /* now append non-native formats for which conversion would be needed */
  for (i = 0; i < G_N_ELEMENTS (fmt_map); ++i) {
    if ((nonnative_formats & fmt_map[i].oss_fmt)) {
      gst_oss4_append_format_to_caps (&fmt_map[i], caps);
    }
  }

  caps = gst_caps_simplify (caps);
  GST_LOG_OBJECT (obj, "formats: %" GST_PTR_FORMAT, caps);

  if (!gst_oss4_audio_detect_rates (obj, &ai, caps))
    goto detect_rates_failed;

  caps = gst_oss4_audio_detect_channels (obj, fd, &ai, caps);
  if (caps == NULL)
    goto detect_channels_failed;

  GST_LOG_OBJECT (obj, "probed caps: %" GST_PTR_FORMAT, caps);

  return caps;

/* ERRORS */
engineinfo_failed:
  {
    GST_WARNING ("ENGINEINFO supported formats probe failed: %s",
        g_strerror (errno));
    return NULL;
  }
detect_rates_failed:
  {
    GST_WARNING_OBJECT (obj, "failed to detect supported sample rates");
    gst_caps_unref (caps);
    return NULL;
  }
detect_channels_failed:
  {
    GST_WARNING_OBJECT (obj, "failed to detect supported channels");
    gst_caps_unref (caps);
    return NULL;
  }
}

GstCaps *
gst_oss4_audio_get_template_caps (void)
{
  GstCaps *caps;
  gint i;

  caps = gst_caps_new_empty ();

  for (i = 0; i < G_N_ELEMENTS (fmt_map); ++i) {
    gst_oss4_append_format_to_caps (&fmt_map[i], caps);
  }

  caps = gst_caps_simplify (caps);

  for (i = 0; i < gst_caps_get_size (caps); ++i) {
    GstStructure *s;

    s = gst_caps_get_structure (caps, i);
    gst_structure_set (s, "rate", GST_TYPE_INT_RANGE, GST_OSS4_MIN_SAMPLE_RATE,
        GST_OSS4_MAX_SAMPLE_RATE, "channels", GST_TYPE_INT_RANGE,
        GST_OSS4_MIN_CHANNELS, GST_OSS4_MAX_CHANNELS, NULL);
  }

  return caps;
}

/* called by gst_oss4_sink_prepare() and gst_oss4_source_prepare() */
gboolean
gst_oss4_audio_set_format (GstObject * obj, int fd,
    GstAudioRingBufferSpec * spec)
{
  struct audio_buf_info info = { 0, };
  int ofmt, fmt, chans, rate, width;

  fmt = gst_oss4_audio_get_oss_format (spec->type,
      GST_AUDIO_INFO_FORMAT (&spec->info));

  if (fmt == 0)
    goto wrong_format;

  ofmt = fmt;
  chans = GST_AUDIO_INFO_CHANNELS (&spec->info);
  rate = GST_AUDIO_INFO_RATE (&spec->info);
  width = GST_AUDIO_INFO_WIDTH (&spec->info);

  if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW &&
      width != 32 && width != 24 && width != 16 && width != 8) {
    goto dodgy_width;
  }

  /* format */
  GST_LOG_OBJECT (obj, "setting format: %d", fmt);
  if (ioctl (fd, SNDCTL_DSP_SETFMT, &fmt) == -1)
    goto set_format_failed;

  /* channels */
  GST_LOG_OBJECT (obj, "setting channels: %d", chans);
  if (ioctl (fd, SNDCTL_DSP_CHANNELS, &chans) == -1)
    goto set_channels_failed;

  /* rate */
  GST_LOG_OBJECT (obj, "setting rate: %d", rate);
  if (ioctl (fd, SNDCTL_DSP_SPEED, &rate) == -1)
    goto set_rate_failed;

  GST_DEBUG_OBJECT (obj, "effective format   : %d", fmt);
  GST_DEBUG_OBJECT (obj, "effective channels : %d", chans);
  GST_DEBUG_OBJECT (obj, "effective rate     : %d", rate);

  /* make sure format, channels, and rate are the ones we requested */
  if (fmt != ofmt || chans != GST_AUDIO_INFO_CHANNELS (&spec->info) ||
      rate != GST_AUDIO_INFO_RATE (&spec->info)) {
    /* This shouldn't happen, but hey */
    goto format_not_what_was_requested;
  }

  if (GST_IS_OSS4_SOURCE (obj)) {
    if (ioctl (fd, SNDCTL_DSP_GETISPACE, &info) == -1)
      goto get_ispace_failed;
  } else {
    if (ioctl (fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
      goto get_ospace_failed;
  }

  spec->segsize = info.fragsize;

  /* we add some extra fragments -- this helps us account for delays due to
   * conversion buffer, streams queueing, etc.  It is important that these
   * be taken into account because otherwise the delay counter can wind up
   * being too large, and the buffer will wrap.  */
  spec->segtotal = info.fragstotal + 4;

  GST_DEBUG_OBJECT (obj, "got segsize: %d, segtotal: %d, value: %08x",
      spec->segsize, spec->segtotal, info.fragsize);

  gst_oss4_audio_set_ringbuffer_channel_layout (obj, fd, spec);

  return TRUE;

/* ERRORS */
wrong_format:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("Unable to get format (%d, %d)", spec->type,
            GST_AUDIO_INFO_FORMAT (&spec->info)));
    return FALSE;
  }
dodgy_width:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("unexpected width %d", width));
    return FALSE;
  }
set_format_failed:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("DSP_SETFMT(%d) failed: %s", fmt, g_strerror (errno)));
    return FALSE;
  }
set_channels_failed:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("DSP_CHANNELS(%d) failed: %s", chans, g_strerror (errno)));
    return FALSE;
  }
set_rate_failed:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("DSP_SPEED(%d) failed: %s", rate, g_strerror (errno)));
    return FALSE;
  }
get_ospace_failed:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("DSP_GETOSPACE failed: %s", g_strerror (errno)));
    return FALSE;
  }
get_ispace_failed:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("DSP_GETISPACE failed: %s", g_strerror (errno)));
    return FALSE;
  }
format_not_what_was_requested:
  {
    GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL),
        ("Format actually configured wasn't the one we requested. This is "
            "probably either a bug in the driver or in the format probing code."));
    return FALSE;
  }
}

int
gst_oss4_audio_get_version (GstObject * obj, int fd)
{
  gint ver = 0;

  /* we use the old ioctl here on purpose instead of SNDCTL_SYSINFO */
  if (ioctl (fd, OSS_GETVERSION, &ver) < 0) {
    GST_LOG_OBJECT (obj, "OSS_GETVERSION failed: %s", g_strerror (errno));
    return -1;
  }
  GST_LOG_OBJECT (obj, "OSS version: 0x%08x", ver);
  return ver;
}

gboolean
gst_oss4_audio_check_version (GstObject * obj, int fd)
{
  return (gst_oss4_audio_get_version (obj, fd) >= GST_MIN_OSS4_VERSION);
}

gchar *
gst_oss4_audio_find_device (GstObject * oss)
{
  GValueArray *arr;
  gchar *ret = NULL;

  arr = gst_oss4_property_probe_get_values (GST_OBJECT (oss), "device");

  if (arr != NULL) {
    if (arr->n_values > 0) {
      const GValue *val;

      val = g_value_array_get_nth (arr, 0);
      ret = g_value_dup_string (val);
    }
    g_value_array_free (arr);
  }

  GST_LOG_OBJECT (oss, "first device found: %s", GST_STR_NULL (ret));

  return ret;
}

static gboolean
plugin_init (GstPlugin * plugin)
{
  gint rank;

  GST_DEBUG_CATEGORY_INIT (oss4sink_debug, "oss4sink", 0, "OSS4 audio sink");
  GST_DEBUG_CATEGORY_INIT (oss4src_debug, "oss4src", 0, "OSS4 audio src");
  GST_DEBUG_CATEGORY_INIT (oss4mixer_debug, "oss4mixer", 0, "OSS4 mixer");
  GST_DEBUG_CATEGORY_INIT (oss4_debug, "oss4", 0, "OSS4 plugin");

#ifdef ENABLE_NLS
  GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
      LOCALEDIR);
  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#endif

  /* we want a higher rank than the legacy OSS elements have now */
  rank = GST_RANK_SECONDARY + 1;

  if (!gst_element_register (plugin, "oss4sink", rank, GST_TYPE_OSS4_SINK) ||
      !gst_element_register (plugin, "oss4src", rank, GST_TYPE_OSS4_SOURCE)) {
    return FALSE;
  }

  return TRUE;
}

GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    oss4,
    "Open Sound System (OSS) version 4 support for GStreamer",
    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
