oss4sink: implement GstStreamVolume interface and add mute and volume properties
authorBrian Cameron <brian.cameron@oracle.com>
Thu, 29 Apr 2010 12:18:58 +0000 (13:18 +0100)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Tue, 11 May 2010 18:02:55 +0000 (19:02 +0100)
OSS4 supports per-stream volume control, so expose this using the right
API, so that playbin2 and applications like totem can make use of it
(instead of using a volume element for volume control).

Fixes #614305.

sys/oss4/oss4-sink.c
sys/oss4/oss4-sink.h

index 7454af4..e59d878 100644 (file)
@@ -59,6 +59,7 @@
 #include <string.h>
 
 #include <gst/gst-i18n-plugin.h>
+#include <gst/interfaces/streamvolume.h>
 
 #define NO_LEGACY_MIXER
 #include "oss4-audio.h"
@@ -93,12 +94,18 @@ static void gst_oss4_sink_reset (GstAudioSink * asink);
 
 #define DEFAULT_DEVICE      NULL
 #define DEFAULT_DEVICE_NAME NULL
+#define DEFAULT_MUTE        FALSE
+#define DEFAULT_VOLUME      1.0
+#define MAX_VOLUME          10.0
 
 enum
 {
   PROP_0,
   PROP_DEVICE,
-  PROP_DEVICE_NAME
+  PROP_DEVICE_NAME,
+  PROP_VOLUME,
+  PROP_MUTE,
+  PROP_LAST
 };
 
 GST_BOILERPLATE_FULL (GstOss4Sink, gst_oss4_sink, GstAudioSink,
@@ -156,6 +163,18 @@ gst_oss4_sink_class_init (GstOss4SinkClass * klass)
           "Human-readable name of the sound device", DEFAULT_DEVICE_NAME,
           G_PARAM_READABLE));
 
+  g_object_class_install_property (gobject_class,
+      PROP_VOLUME,
+      g_param_spec_double ("volume", "Volume",
+          "Linear volume of this stream, 1.0=100%", 0.0, MAX_VOLUME,
+          DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_MUTE,
+      g_param_spec_boolean ("mute", "Mute",
+          "Mute state of this stream", DEFAULT_MUTE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_oss4_sink_getcaps);
 
   audiosink_class->open = GST_DEBUG_FUNCPTR (gst_oss4_sink_open_func);
@@ -180,6 +199,7 @@ gst_oss4_sink_init (GstOss4Sink * osssink, GstOss4SinkClass * klass)
   osssink->bytes_per_sample = 0;
   osssink->probed_caps = NULL;
   osssink->device_name = NULL;
+  osssink->mute_volume = 100 | (100 << 8);
 }
 
 static void
@@ -197,6 +217,96 @@ gst_oss4_sink_finalise (GObject * object)
 }
 
 static void
+gst_oss4_sink_set_volume (GstOss4Sink * oss, gdouble volume)
+{
+  int ivol;
+
+  volume = volume * 100.0;
+  ivol = (int) volume | ((int) volume << 8);
+  GST_OBJECT_LOCK (oss);
+  if (ioctl (oss->fd, SNDCTL_DSP_SETPLAYVOL, &ivol) < 0) {
+    GST_LOG_OBJECT (oss, "SETPLAYVOL failed");
+  }
+  GST_OBJECT_UNLOCK (oss);
+}
+
+static gdouble
+gst_oss4_sink_get_volume (GstOss4Sink * oss)
+{
+  int ivol, lvol, rvol;
+  gdouble dvol = DEFAULT_VOLUME;
+
+  GST_OBJECT_LOCK (oss);
+  if (ioctl (oss->fd, SNDCTL_DSP_GETPLAYVOL, &ivol) < 0) {
+    GST_LOG_OBJECT (oss, "GETPLAYVOL failed");
+  } else {
+    /* Return the higher of the two volume channels, if different */
+    lvol = ivol & 0xff;
+    rvol = (ivol >> 8) & 0xff;
+    dvol = MAX (lvol, rvol) / 100.0;
+  }
+  GST_OBJECT_UNLOCK (oss);
+
+  return dvol;
+}
+
+static void
+gst_oss4_sink_set_mute (GstOss4Sink * oss, gboolean mute)
+{
+  int ivol;
+
+  if (mute) {
+    /*
+     * OSSv4 does not have a per-channel mute, so simulate by setting
+     * the value to 0.  Save the volume before doing a mute so we can
+     * reset the value when the user un-mutes.
+     */
+    ivol = 0;
+
+    GST_OBJECT_LOCK (oss);
+    if (ioctl (oss->fd, SNDCTL_DSP_GETPLAYVOL, &oss->mute_volume) < 0) {
+      GST_LOG_OBJECT (oss, "GETPLAYVOL failed");
+    }
+    if (ioctl (oss->fd, SNDCTL_DSP_SETPLAYVOL, &ivol) < 0) {
+      GST_LOG_OBJECT (oss, "SETPLAYVOL failed");
+    }
+    GST_OBJECT_UNLOCK (oss);
+  } else {
+    /*
+     * If the saved volume is 0, then reset it to 100.  Otherwise the mute
+     * can get stuck.  This can happen, for example, due to rounding
+     * errors in converting from the float to an integer.
+     */
+    if (oss->mute_volume == 0) {
+      oss->mute_volume = 100 | (100 << 8);
+    }
+    GST_OBJECT_LOCK (oss);
+    if (ioctl (oss->fd, SNDCTL_DSP_SETPLAYVOL, &oss->mute_volume) < 0) {
+      GST_LOG_OBJECT (oss, "SETPLAYVOL failed");
+    }
+    GST_OBJECT_UNLOCK (oss);
+  }
+}
+
+static gboolean
+gst_oss4_sink_get_mute (GstOss4Sink * oss)
+{
+  int ivol, lvol, rvol;
+
+  GST_OBJECT_LOCK (oss);
+  if (ioctl (oss->fd, SNDCTL_DSP_GETPLAYVOL, &ivol) < 0) {
+    GST_LOG_OBJECT (oss, "GETPLAYVOL failed");
+    lvol = rvol = 100;
+  } else {
+    lvol = ivol & 0xff;
+    rvol = (ivol >> 8) & 0xff;
+  }
+  GST_OBJECT_UNLOCK (oss);
+
+  return (lvol == 0 && rvol == 0);
+}
+
+static void
 gst_oss4_sink_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec)
 {
@@ -220,6 +330,12 @@ gst_oss4_sink_set_property (GObject * object, guint prop_id,
       }
       GST_OBJECT_UNLOCK (oss);
       break;
+    case PROP_VOLUME:
+      gst_oss4_sink_set_volume (oss, g_value_get_double (value));
+      break;
+    case PROP_MUTE:
+      gst_oss4_sink_set_mute (oss, g_value_get_boolean (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -258,6 +374,12 @@ gst_oss4_sink_get_property (GObject * object, guint prop_id,
       }
       GST_OBJECT_UNLOCK (oss);
       break;
+    case PROP_VOLUME:
+      g_value_set_double (value, gst_oss4_sink_get_volume (oss));
+      break;
+    case PROP_MUTE:
+      g_value_set_boolean (value, gst_oss4_sink_get_mute (oss));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -544,10 +666,14 @@ gst_oss4_sink_delay (GstAudioSink * asink)
 
   oss = GST_OSS4_SINK_CAST (asink);
 
+  GST_OBJECT_LOCK (oss);
   if (ioctl (oss->fd, SNDCTL_DSP_GETODELAY, &delay) < 0 || delay < 0) {
     GST_LOG_OBJECT (oss, "GETODELAY failed");
-    return 0;
   }
+  GST_OBJECT_UNLOCK (oss);
+
+  if (G_UNLIKELY (delay < 0))   /* error case */
+    return 0;
 
   return delay / oss->bytes_per_sample;
 }
@@ -563,5 +689,11 @@ gst_oss4_sink_reset (GstAudioSink * asink)
 static void
 gst_oss4_sink_init_interfaces (GType type)
 {
+  static const GInterfaceInfo svol_iface_info = {
+    NULL, NULL, NULL
+  };
+
+  g_type_add_interface_static (type, GST_TYPE_STREAM_VOLUME, &svol_iface_info);
+
   gst_oss4_add_property_probe_interface (type);
 }
index 0120598..b1489a7 100644 (file)
@@ -44,6 +44,7 @@ struct _GstOss4Sink {
   gchar       * device_name;        /* set if the device is open */
   gint          fd;                 /* -1 if not open            */
   gint          bytes_per_sample;
+  gint          mute_volume;
 
   GstCaps     * probed_caps;