diff --git a/ChangeLog b/ChangeLog index 88958d19dd..085a8be170 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,28 @@ +2008-04-02 Tim-Philipp Müller + + * configure.ac: + * sys/Makefile.am: + * sys/oss4/Makefile.am: + * sys/oss4/oss4-audio.c: + * sys/oss4/oss4-audio.h: + * sys/oss4/oss4-mixer-enum.c: + * sys/oss4/oss4-mixer-enum.h: + * sys/oss4/oss4-mixer-slider.c: + * sys/oss4/oss4-mixer-slider.h: + * sys/oss4/oss4-mixer-switch.c: + * sys/oss4/oss4-mixer-switch.h: + * sys/oss4/oss4-mixer.c: + * sys/oss4/oss4-mixer.h: + * sys/oss4/oss4-property-probe.c: + * sys/oss4/oss4-property-probe.h: + * sys/oss4/oss4-sink.c: + * sys/oss4/oss4-sink.h: + * sys/oss4/oss4-soundcard.h: + * sys/oss4/oss4-source.c: + * sys/oss4/oss4-source.h: + Add initial support for OSSv4. Mixer still needs a bit more love, + but even magic has its limits. + 2008-04-01 Tim-Philipp Müller * configure.ac: diff --git a/configure.ac b/configure.ac index 295a15640d..c2c0989c05 100644 --- a/configure.ac +++ b/configure.ac @@ -1030,6 +1030,14 @@ AG_GST_CHECK_FEATURE(DVB, [DVB Source], dvb, [ AC_CHECK_HEADER(linux/dvb/frontend.h, [HAVE_DVB="yes"], [HAVE_DVB="no"]) ]) +dnl *** oss4 *** +translit(dnm, m, l) AM_CONDITIONAL(USE_OSS4, true) +AG_GST_CHECK_FEATURE(OSS4, [Open Sound System 4], oss4, [ + AC_MSG_CHECKING([Checking if we can build the OSS4 elements]) + AC_CHECK_HEADERS([fcntl.h sys/ioctl.h sys/stat.h sys/types.h], + [HAVE_OSS4="yes"], [HAVE_OSS4="no"]) +]) + else dnl not building plugins with external dependencies, @@ -1046,6 +1054,7 @@ AM_CONDITIONAL(USE_DIRECTFB, false) AM_CONDITIONAL(USE_DTS, false) AM_CONDITIONAL(USE_DIRAC, false) AM_CONDITIONAL(USE_DIVX, false) +AM_CONDITIONAL(USE_DVB, false) AM_CONDITIONAL(USE_FAAC, false) AM_CONDITIONAL(USE_FAAD, false) AM_CONDITIONAL(USE_FBDEV, false) @@ -1063,6 +1072,7 @@ AM_CONDITIONAL(USE_MYTHTV, false) AM_CONDITIONAL(USE_NAS, false) AM_CONDITIONAL(USE_NEON, false) AM_CONDITIONAL(USE_OFA, false) +AM_CONDITIONAL(USE_OSS4, false) AM_CONDITIONAL(USE_SOUP, false) AM_CONDITIONAL(USE_SDL, false) AM_CONDITIONAL(USE_SNDFILE, false) @@ -1074,7 +1084,6 @@ AM_CONDITIONAL(USE_TIMIDITY, false) AM_CONDITIONAL(USE_X264, false) AM_CONDITIONAL(USE_XVID, false) AM_CONDITIONAL(USE_WILDMIDI, false) -AM_CONDITIONAL(USE_DVB, false) fi dnl of EXT plugins @@ -1179,8 +1188,9 @@ gst-libs/gst/dshow/Makefile sys/Makefile sys/dshowdecwrapper/Makefile sys/dshowsrcwrapper/Makefile -sys/fbdev/Makefile sys/dvb/Makefile +sys/fbdev/Makefile +sys/oss4/Makefile sys/qtwrapper/Makefile sys/vcd/Makefile examples/Makefile diff --git a/sys/Makefile.am b/sys/Makefile.am index d7730426f8..55a013740e 100644 --- a/sys/Makefile.am +++ b/sys/Makefile.am @@ -34,13 +34,19 @@ else DVB_DIR= endif +if USE_OSS4 +OSS4_DIR=oss4 +else +OSS4_DIR= +endif + if USE_QUICKTIME QT_DIR=qtwrapper else QT_DIR= endif -SUBDIRS = $(FBDEV_DIR) $(DVB_DIR) $(VCD_DIR) $(QT_DIR) +SUBDIRS = $(FBDEV_DIR) $(DVB_DIR) $(VCD_DIR) $(QT_DIR) $(OSS4_DIR) -DIST_SUBDIRS = dvb fbdev vcd qtwrapper dshowdecwrapper dshowsrcwrapper +DIST_SUBDIRS = dvb fbdev dshowdecwrapper dshowsrcwrapper oss4 qtwrapper vcd diff --git a/sys/oss4/Makefile.am b/sys/oss4/Makefile.am new file mode 100644 index 0000000000..34472c0a54 --- /dev/null +++ b/sys/oss4/Makefile.am @@ -0,0 +1,32 @@ +plugin_LTLIBRARIES = libgstoss4audio.la + +libgstoss4audio_la_SOURCES = \ + oss4-audio.c \ + oss4-mixer.c \ + oss4-mixer-enum.c \ + oss4-mixer-slider.c \ + oss4-mixer-switch.c \ + oss4-property-probe.c \ + oss4-sink.c \ + oss4-source.c + +libgstoss4audio_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstoss4audio_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstinterfaces-$(GST_MAJORMINOR) \ + -lgstaudio-$(GST_MAJORMINOR) \ + $(GST_LIBS) +libgstoss4audio_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = \ + oss4-audio.h \ + oss4-mixer.h \ + oss4-mixer-enum.h \ + oss4-mixer-slider.h \ + oss4-mixer-switch.h \ + oss4-property-probe.h \ + oss4-sink.h \ + oss4-soundcard.h \ + oss4-source.h + + diff --git a/sys/oss4/oss4-audio.c b/sys/oss4/oss4-audio.c new file mode 100644 index 0000000000..a2404cc888 --- /dev/null +++ b/sys/oss4/oss4-audio.c @@ -0,0 +1,721 @@ +/* GStreamer OSS4 audio plugin + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "gst/gst-i18n-plugin.h" +#include + +#include "oss4-audio.h" +#include "oss4-mixer.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 + +static const struct +{ + const GstBufferFormat gst_fmt; + const gint oss_fmt; + const gchar name[16]; + const gint depth; + const gint width; + const gint endianness; + const gboolean signedness; +} fmt_map[] = { + /* note: keep sorted by preference, prefered formats first */ + { + GST_MU_LAW, AFMT_MU_LAW, "audio/x-mulaw", 0, 0, 0, FALSE}, { + GST_A_LAW, AFMT_A_LAW, "audio/x-alaw", 0, 0, 0, FALSE}, { + GST_S32_LE, AFMT_S32_LE, "audio/x-raw-int", 32, 32, G_LITTLE_ENDIAN, TRUE}, { + GST_S32_BE, AFMT_S32_BE, "audio/x-raw-int", 32, 32, G_BIG_ENDIAN, TRUE}, { + GST_S24_LE, AFMT_S24_LE, "audio/x-raw-int", 24, 32, G_LITTLE_ENDIAN, TRUE}, { + GST_S24_BE, AFMT_S24_BE, "audio/x-raw-int", 24, 32, G_BIG_ENDIAN, TRUE}, { + GST_S24_3LE, AFMT_S24_PACKED, "audio/x-raw-int", 24, 24, G_LITTLE_ENDIAN, + TRUE}, { + GST_S16_LE, AFMT_S16_LE, "audio/x-raw-int", 16, 16, G_LITTLE_ENDIAN, TRUE}, { + GST_S16_BE, AFMT_S16_BE, "audio/x-raw-int", 16, 16, G_BIG_ENDIAN, TRUE}, { + GST_U16_LE, AFMT_U16_LE, "audio/x-raw-int", 16, 16, G_LITTLE_ENDIAN, FALSE}, { + GST_U16_BE, AFMT_U16_BE, "audio/x-raw-int", 16, 16, G_BIG_ENDIAN, FALSE}, { + GST_S8, AFMT_S8, "audio/x-raw-int", 8, 8, 0, TRUE}, { + GST_U8, AFMT_U8, "audio/x-raw-int", 8, 8, 0, FALSE} +}; + +static gboolean +gst_oss4_append_format_to_caps (gint fmt, GstCaps * caps) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (fmt_map); ++i) { + if (fmt_map[i].oss_fmt == fmt) { + GstStructure *s; + + s = gst_structure_empty_new (fmt_map[i].name); + if (fmt_map[i].width != 0 && fmt_map[i].depth != 0) { + gst_structure_set (s, "width", G_TYPE_INT, fmt_map[i].width, + "depth", G_TYPE_INT, fmt_map[i].depth, "endianness", G_TYPE_INT, + fmt_map[i].endianness, "signed", G_TYPE_BOOLEAN, + fmt_map[i].signedness, NULL); + } + gst_caps_append_structure (caps, s); + return TRUE; + } + } + return FALSE; +} + +static gint +gst_oss4_audio_get_oss_format (GstBufferFormat fmt) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (fmt_map); ++i) { + if (fmt_map[i].gst_fmt == fmt) + 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_add_channel_layout (GstObject * obj, guint64 layout, + guint num_channels, GstStructure * s) +{ + 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_LFE, /* 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 + }; + GstAudioChannelPosition ch_layout[8] = { 0, }; + guint speaker_pos; /* speaker position as defined by OSS */ + guint i; + + g_return_if_fail (num_channels <= G_N_ELEMENTS (ch_layout)); + + 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]; + } + gst_audio_set_channel_positions (s, ch_layout); + 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; + } + gst_audio_set_channel_positions (s, ch_layout); + 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-positions"))) + gst_structure_set_value (s, "channel-positions", 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 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 (); + + 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].oss_fmt, caps); + } + } + + gst_caps_do_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].oss_fmt, caps); + } + + gst_caps_do_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; +} + +static gint +gst_oss4_audio_ilog2 (gint x) +{ + /* well... hacker's delight explains... */ + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0f0f0f0f; + x = x + (x >> 8); + x = x + (x >> 16); + return (x & 0x0000003f) - 1; +} + +/* called by gst_oss4_sink_prepare() and gst_oss4_source_prepare() */ +gboolean +gst_oss4_audio_set_format (GstObject * obj, int fd, GstRingBufferSpec * spec) +{ + struct audio_buf_info info = { 0, }; + int fmt, chans, rate, fragsize; + + fmt = gst_oss4_audio_get_oss_format (spec->format); + if (fmt == 0) + goto wrong_format; + + if (spec->type == GST_BUFTYPE_LINEAR && spec->width != 32 && + spec->width != 24 && spec->width != 16 && spec->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", spec->channels); + chans = spec->channels; + if (ioctl (fd, SNDCTL_DSP_CHANNELS, &chans) == -1) + goto set_channels_failed; + + /* rate */ + GST_LOG_OBJECT (obj, "setting rate: %d", spec->rate); + rate = spec->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 != gst_oss4_audio_get_oss_format (spec->format) || + chans != spec->channels || rate != spec->rate) { + /* This shouldn't happen, but hey */ + goto format_not_what_was_requested; + } + + /* CHECKME: maybe we should just leave the fragsize alone? (tpm) */ + fragsize = gst_oss4_audio_ilog2 (spec->segsize); + fragsize = ((spec->segtotal & 0x7fff) << 16) | fragsize; + GST_DEBUG_OBJECT (obj, "setting segsize: %d, segtotal: %d, value: %08x", + spec->segsize, spec->segtotal, fragsize); + + /* we could also use the new SNDCTL_DSP_POLICY if there's something in + * particular we're trying to achieve here */ + if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &fragsize) == -1) + goto set_fragsize_failed; + + 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; + spec->segtotal = info.fragstotal; + + spec->bytes_per_sample = (spec->width / 8) * spec->channels; + + GST_DEBUG_OBJECT (obj, "got segsize: %d, segtotal: %d, value: %08x", + spec->segsize, spec->segtotal, fragsize); + + return TRUE; + +/* ERRORS */ +wrong_format: + { + GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL), + ("Unable to get format %d", spec->format)); + return FALSE; + } +dodgy_width: + { + GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL), + ("unexpected width %d", spec->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; + } +set_fragsize_failed: + { + GST_ELEMENT_ERROR (obj, RESOURCE, SETTINGS, (NULL), + ("DSP_SETFRAGMENT(%d) failed: %s", fragsize, 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_property_probe_probe_and_get_values_name (GST_PROPERTY_PROBE (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); +#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) || + !gst_element_register (plugin, "oss4mixer", rank, GST_TYPE_OSS4_MIXER)) { + 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) diff --git a/sys/oss4/oss4-audio.h b/sys/oss4/oss4-audio.h new file mode 100644 index 0000000000..d220364173 --- /dev/null +++ b/sys/oss4/oss4-audio.h @@ -0,0 +1,43 @@ +/* GStreamer OSS4 audio plugin + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_OSS4_AUDIO_H +#define GST_OSS4_AUDIO_H_ + +#include +#include + +/* This is the minimum version we require */ +#define GST_MIN_OSS4_VERSION 0x040003 + +int gst_oss4_audio_get_version (GstObject * obj, int fd); + +gboolean gst_oss4_audio_check_version (GstObject * obj, int fd); + +GstCaps * gst_oss4_audio_probe_caps (GstObject * obj, int fd); + +gboolean gst_oss4_audio_set_format (GstObject * obj, int fd, GstRingBufferSpec * spec); + +GstCaps * gst_oss4_audio_get_template_caps (void); + +gchar * gst_oss4_audio_find_device (GstObject * oss); + +#endif /* GST_OSS4_AUDIO_H */ + + diff --git a/sys/oss4/oss4-mixer-enum.c b/sys/oss4/oss4-mixer-enum.c new file mode 100644 index 0000000000..edd0d7bb83 --- /dev/null +++ b/sys/oss4/oss4-mixer-enum.c @@ -0,0 +1,269 @@ +/* GStreamer OSS4 mixer enumeration control + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* An 'enum' in gnome-volume-control / GstMixer is represented by a + * GstMixerOptions object + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#define NO_LEGACY_MIXER +#include "oss4-mixer.h" +#include "oss4-mixer-enum.h" +#include "oss4-soundcard.h" + +GST_DEBUG_CATEGORY_EXTERN (oss4mixer_debug); +#define GST_CAT_DEFAULT oss4mixer_debug + +static GList *gst_oss4_mixer_enum_get_values (GstMixerOptions * options); + +/* GstMixerTrack is a plain GObject, so let's just use the GLib macro here */ +G_DEFINE_TYPE (GstOss4MixerEnum, gst_oss4_mixer_enum, GST_TYPE_MIXER_OPTIONS); + +static void +gst_oss4_mixer_enum_init (GstOss4MixerEnum * e) +{ + e->need_update = TRUE; +} + +static void +gst_oss4_mixer_enum_dispose (GObject * obj) +{ + GstMixerOptions *options = GST_MIXER_OPTIONS (obj); + + /* our list is a flat list with constant strings, but the GstMixerOptions + * dispose will try to g_free the contained strings, so clean up the list + * before chaining up to GstMixerOptions */ + g_list_free (options->values); + options->values = NULL; + + G_OBJECT_CLASS (gst_oss4_mixer_enum_parent_class)->dispose (obj); +} + +static void +gst_oss4_mixer_enum_class_init (GstOss4MixerEnumClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstMixerOptionsClass *mixeroptions_class = (GstMixerOptionsClass *) klass; + + gobject_class->dispose = gst_oss4_mixer_enum_dispose; + mixeroptions_class->get_values = gst_oss4_mixer_enum_get_values; +} + +static GList * +gst_oss4_mixer_enum_get_values_locked (GstMixerOptions * options) +{ + GstOss4MixerEnum *e = GST_OSS4_MIXER_ENUM_CAST (options); + GList *oldlist, *list = NULL; + int i; + + /* if current list of values is empty, update/re-check in any case */ + if (!e->need_update && options->values != NULL) + return options->values; + + GST_LOG_OBJECT (e, "updating available values for %s", e->mc->mixext.extname); + + for (i = 0; i < e->mc->mixext.maxvalue; ++i) { + if (MIXEXT_ENUM_IS_AVAILABLE (e->mc->mixext, i)) { + const gchar *s; + + s = g_quark_to_string (e->mc->enum_vals[i]); + GST_LOG_OBJECT (e, "option '%s' is available", s); + list = g_list_prepend (list, (gpointer) s); + } else { + GST_LOG_OBJECT (e, "option '%s' is currently not available"); + } + } + + list = g_list_reverse (list); + + /* this is not thread-safe, but then the entire GstMixer API isn't really, + * since we return foo->list and not a copy and don't take any locks, so + * not much we can do here but pray; we're usually either called from _new() + * or from within _get_values() though, so it should be okay. We could use + * atomic ops here, but I'm not sure how much more that really buys us.*/ + oldlist = options->values; /* keep window small */ + options->values = list; + g_list_free (oldlist); + + e->need_update = FALSE; + + return options->values; +} + +static GList * +gst_oss4_mixer_enum_get_values (GstMixerOptions * options) +{ + GstOss4MixerEnum *e = GST_OSS4_MIXER_ENUM (options); + GList *list; + + /* we take the lock here mostly to serialise ioctls with the watch thread */ + GST_OBJECT_LOCK (e->mixer); + + list = gst_oss4_mixer_enum_get_values_locked (options); + + GST_OBJECT_UNLOCK (e->mixer); + + return list; +} + +static const gchar * +gst_oss4_mixer_enum_get_current_value (GstOss4MixerEnum * e) +{ + const gchar *cur_val = NULL; + + if (e->mc->enum_vals != NULL && e->mc->last_val < e->mc->mixext.maxvalue) { + cur_val = g_quark_to_string (e->mc->enum_vals[e->mc->last_val]); + } + + return cur_val; +} + +static gboolean +gst_oss4_mixer_enum_update_current (GstOss4MixerEnum * e) +{ + int cur = -1; + + if (!gst_oss4_mixer_get_control_val (e->mixer, e->mc, &cur)) + return FALSE; + + if (cur < 0 || cur >= e->mc->mixext.maxvalue) { + GST_WARNING_OBJECT (e, "read value %d out of bounds [0-%d]", cur, + e->mc->mixext.maxvalue - 1); + e->mc->last_val = 0; + return FALSE; + } + + return TRUE; +} + +gboolean +gst_oss4_mixer_enum_set_option (GstOss4MixerEnum * e, const gchar * value) +{ + GQuark q; + int i; + + q = g_quark_try_string (value); + if (q == 0) { + GST_WARNING_OBJECT (e, "unknown option '%s'", value); + return FALSE; + } + + for (i = 0; i < e->mc->mixext.maxvalue; ++i) { + if (q == e->mc->enum_vals[i]) + break; + } + + if (i >= e->mc->mixext.maxvalue) { + GST_WARNING_OBJECT (e, "option '%s' is not valid for this control", value); + return FALSE; + } + + GST_LOG_OBJECT (e, "option '%s' = %d", value, i); + + if (!MIXEXT_ENUM_IS_AVAILABLE (e->mc->mixext, i)) { + GST_WARNING_OBJECT (e, "option '%s' is not selectable currently", value); + return FALSE; + } + + if (!gst_oss4_mixer_set_control_val (e->mixer, e->mc, i)) { + GST_WARNING_OBJECT (e, "could not set option '%s' (%d)", value, i); + return FALSE; + } + + /* and re-read current value with sanity checks (or could just assign here) */ + gst_oss4_mixer_enum_update_current (e); + + return TRUE; +} + +const gchar * +gst_oss4_mixer_enum_get_option (GstOss4MixerEnum * e) +{ + const gchar *cur_str = NULL; + + if (!gst_oss4_mixer_enum_update_current (e)) { + GST_WARNING_OBJECT (e, "failed to read current value"); + return NULL; + } + + cur_str = gst_oss4_mixer_enum_get_current_value (e); + GST_LOG_OBJECT (e, "%s (%d)", GST_STR_NULL (cur_str), e->mc->last_val); + return cur_str; +} + +GstMixerTrack * +gst_oss4_mixer_enum_new (GstOss4Mixer * mixer, GstOss4MixerControl * mc) +{ + GstOss4MixerEnum *e; + GstMixerTrack *track; + + e = g_object_new (GST_TYPE_OSS4_MIXER_ENUM, "untranslated-label", + mc->mixext.extname, NULL); + e->mixer = mixer; + e->mc = mc; + + track = GST_MIXER_TRACK (e); + + /* caller will set track->label and track->flags */ + + track->num_channels = 0; + track->min_volume = 0; + track->max_volume = 0; + + (void) gst_oss4_mixer_enum_get_values_locked (GST_MIXER_OPTIONS (track)); + + if (!gst_oss4_mixer_enum_update_current (e)) { + GST_WARNING_OBJECT (track, "failed to read current value, returning NULL"); + g_object_unref (track); + track = NULL; + } + + GST_LOG_OBJECT (e, "current value: %d (%s)", e->mc->last_val, + gst_oss4_mixer_enum_get_current_value (e)); + + return track; +} + +/* This is called from the watch thread */ +void +gst_oss4_mixer_enum_process_change_unlocked (GstMixerTrack * track) +{ + GstOss4MixerEnum *e = GST_OSS4_MIXER_ENUM_CAST (track); + + gchar *cur; + + if (!e->mc->changed && !e->mc->list_changed) + return; + + if (e->mc->list_changed) { + gst_mixer_options_list_changed (GST_MIXER (e->mixer), + GST_MIXER_OPTIONS (e)); + } + + GST_OBJECT_LOCK (e->mixer); + cur = (gchar *) gst_oss4_mixer_enum_get_current_value (e); + GST_OBJECT_UNLOCK (e->mixer); + + gst_mixer_option_changed (GST_MIXER (e->mixer), GST_MIXER_OPTIONS (e), cur); +} diff --git a/sys/oss4/oss4-mixer-enum.h b/sys/oss4/oss4-mixer-enum.h new file mode 100644 index 0000000000..9fd8166907 --- /dev/null +++ b/sys/oss4/oss4-mixer-enum.h @@ -0,0 +1,67 @@ +/* GStreamer OSS4 mixer on/off enum control + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_OSS4_MIXER_ENUM_H +#define GST_OSS4_MIXER_ENUM_H + +#include +#include + +#include "oss4-mixer.h" + +G_BEGIN_DECLS + +#define GST_TYPE_OSS4_MIXER_ENUM (gst_oss4_mixer_enum_get_type()) +#define GST_OSS4_MIXER_ENUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSS4_MIXER_ENUM,GstOss4MixerEnum)) +#define GST_OSS4_MIXER_ENUM_CAST(obj) ((GstOss4MixerEnum *)(obj)) +#define GST_OSS4_MIXER_ENUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSS4_MIXER_ENUM,GstOss4MixerEnumClass)) +#define GST_IS_OSS4_MIXER_ENUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSS4_MIXER_ENUM)) +#define GST_IS_OSS4_MIXER_ENUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSS4_MIXER_ENUM)) + +typedef struct _GstOss4MixerEnum GstOss4MixerEnum; +typedef struct _GstOss4MixerEnumClass GstOss4MixerEnumClass; + +struct _GstOss4MixerEnum { + GstMixerOptions mixer_option; + + GstOss4MixerControl * mc; + GstOss4Mixer * mixer; /* the mixer we belong to (no ref taken) */ + + gboolean need_update; +}; + +struct _GstOss4MixerEnumClass { + GstMixerOptionsClass mixer_option_class; +}; + +GType gst_oss4_mixer_enum_get_type (void); + +gboolean gst_oss4_mixer_enum_set_option (GstOss4MixerEnum * e, const gchar * value); + +const gchar * gst_oss4_mixer_enum_get_option (GstOss4MixerEnum * e); + +GstMixerTrack * gst_oss4_mixer_enum_new (GstOss4Mixer * mixer, GstOss4MixerControl * mc); + +void gst_oss4_mixer_enum_process_change_unlocked (GstMixerTrack * track); + +G_END_DECLS + +#endif /* GST_OSS4_MIXER_ENUM_H */ + + diff --git a/sys/oss4/oss4-mixer-slider.c b/sys/oss4/oss4-mixer-slider.c new file mode 100644 index 0000000000..a56dcae85a --- /dev/null +++ b/sys/oss4/oss4-mixer-slider.c @@ -0,0 +1,296 @@ +/* GStreamer OSS4 mixer slider control + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* A 'slider' in gnome-volume-control / GstMixer is represented by a + * GstMixerTrack with one or more channels. + * + * A slider should be either flagged as INPUT or OUTPUT (mostly because of + * gnome-volume-control being littered with g_asserts for everything it doesn't + * expect). + * + * From mixertrack.h: + * "Input tracks can have 'recording' enabled, which means that any input will + * be hearable into the speakers that are attached to the output. Mute is + * obvious." + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NO_LEGACY_MIXER +#include "oss4-mixer-slider.h" + +GST_DEBUG_CATEGORY_EXTERN (oss4mixer_debug); +#define GST_CAT_DEFAULT oss4mixer_debug + +/* GstMixerTrack is a plain GObject, so let's just use the GLib macro here */ +G_DEFINE_TYPE (GstOss4MixerSlider, gst_oss4_mixer_slider, GST_TYPE_MIXER_TRACK); + +static void +gst_oss4_mixer_slider_class_init (GstOss4MixerSliderClass * klass) +{ + /* nothing to do here */ +} + +static void +gst_oss4_mixer_slider_init (GstOss4MixerSlider * s) +{ + /* nothing to do here */ +} + +static int +gst_oss4_mixer_slider_pack_volume (GstOss4MixerSlider * s, const gint * volumes) +{ + int val = 0; + + switch (s->mc->mixext.type) { + case MIXT_MONOSLIDER: + case MIXT_MONOSLIDER16: + case MIXT_SLIDER: + val = volumes[0]; + break; + case MIXT_STEREOSLIDER: + val = ((volumes[1] & 0xff) << 8) | (volumes[0] & 0xff); + break; + case MIXT_STEREOSLIDER16: + val = ((volumes[1] & 0xffff) << 16) | (volumes[0] & 0xffff); + break; + default: + g_return_val_if_reached (0); + } + return val; +} + +static void +gst_oss4_mixer_slider_unpack_volume (GstOss4MixerSlider * s, int v, + gint * volumes) +{ + guint32 val; /* use uint so bitshifting the highest bit works right */ + + val = (guint32) v; + switch (s->mc->mixext.type) { + case MIXT_MONOSLIDER: + case MIXT_MONOSLIDER16: + case MIXT_SLIDER: + volumes[0] = val; + break; + case MIXT_STEREOSLIDER: + volumes[0] = (val & 0x00ff); + volumes[1] = (val & 0xff00) >> 8; + break; + case MIXT_STEREOSLIDER16: + volumes[0] = (val & 0x0000ffff); + volumes[1] = (val & 0xffff0000) >> 16; + break; + default: + g_return_if_reached (); + } +} + +gboolean +gst_oss4_mixer_slider_get_volume (GstOss4MixerSlider * s, gint * volumes) +{ + GstMixerTrack *track = GST_MIXER_TRACK (s); + int v = 0; + + /* if we're supposed to be muted, and don't have an actual mute control + * (ie. 'simulate' the mute), then just return the volume as saved, not + * the actually set volume which is most likely 0 */ + if (GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_MUTE) && !s->mc->mute) { + volumes[0] = s->volumes[0]; + if (track->num_channels == 2) + volumes[1] = s->volumes[1]; + return TRUE; + } + + if (!gst_oss4_mixer_get_control_val (s->mixer, s->mc, &v)) + return FALSE; + + gst_oss4_mixer_slider_unpack_volume (s, v, volumes); + + if (track->num_channels > 1) { + GST_LOG_OBJECT (s, "volume: left=%d, right=%d", volumes[0], volumes[1]); + } else { + GST_LOG_OBJECT (s, "volume: mono=%d", volumes[0]); + } + + return TRUE; +} + +gboolean +gst_oss4_mixer_slider_set_volume (GstOss4MixerSlider * s, const gint * volumes) +{ + GstMixerTrack *track = GST_MIXER_TRACK (s); + int val = 0; + + /* if we're supposed to be muted, and are 'simulating' the mute because + * we don't have a mute control, don't actually change the volume, just + * save it as the new desired volume for later when we get unmuted again */ + if (GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_MUTE) && !s->mc->mute) + goto done; + + val = gst_oss4_mixer_slider_pack_volume (s, volumes); + + if (track->num_channels > 1) { + GST_LOG_OBJECT (s, "left=%d, right=%d", volumes[0], volumes[1]); + } else { + GST_LOG_OBJECT (s, "mono=%d", volumes[0]); + } + + if (!gst_oss4_mixer_set_control_val (s->mixer, s->mc, val)) + return FALSE; + +done: + + s->volumes[0] = volumes[0]; + if (track->num_channels == 2) + s->volumes[1] = volumes[1]; + + return TRUE; +} + +gboolean +gst_oss4_mixer_slider_set_record (GstOss4MixerSlider * s, gboolean record) +{ + /* There doesn't seem to be a way to do this using the OSS4 mixer API, so + * just do nothing here for now. */ + return FALSE; +} + +gboolean +gst_oss4_mixer_slider_set_mute (GstOss4MixerSlider * s, gboolean mute) +{ + GstMixerTrack *track = GST_MIXER_TRACK (s); + gboolean ret; + + /* if we don't have a mute control, simulate mute (which is a bit broken, + * since we can't differentiate between capture/playback volume etc., so + * we just assume that setting the volume to 0 would be the same as muting + * this control) */ + if (s->mc->mute == NULL) { + int volume; + + if (mute) { + volume = 0; + } else { + volume = gst_oss4_mixer_slider_pack_volume (s, s->volumes); + } + ret = gst_oss4_mixer_set_control_val (s->mixer, s->mc, volume); + } else { + ret = gst_oss4_mixer_set_control_val (s->mixer, s->mc->mute, !!mute); + } + + if (mute) { + track->flags |= GST_MIXER_TRACK_MUTE; + } else { + track->flags &= ~GST_MIXER_TRACK_MUTE; + } + + return FALSE; +} + +GstMixerTrack * +gst_oss4_mixer_slider_new (GstOss4Mixer * mixer, GstOss4MixerControl * mc) +{ + GstOss4MixerSlider *s; + GstMixerTrack *track; + gint volumes[2] = { 0, }; + + s = g_object_new (GST_TYPE_OSS4_MIXER_SLIDER, "untranslated-label", + mc->mixext.extname, NULL); + + track = GST_MIXER_TRACK (s); + + /* caller will set track->label and track->flags */ + + s->mc = mc; + s->mixer = mixer; + + /* we don't do value scaling but just present a scale of 0-maxvalue */ + track->min_volume = 0; + track->max_volume = mc->mixext.maxvalue; + + switch (mc->mixext.type) { + case MIXT_MONOSLIDER: + case MIXT_MONOSLIDER16: + case MIXT_SLIDER: + track->num_channels = 1; + break; + case MIXT_STEREOSLIDER: + case MIXT_STEREOSLIDER16: + track->num_channels = 2; + break; + default: + g_return_val_if_reached (NULL); + } + + GST_LOG_OBJECT (track, "min=%d, max=%d, channels=%d", track->min_volume, + track->max_volume, track->num_channels); + + if (!gst_oss4_mixer_slider_get_volume (s, volumes)) { + GST_WARNING_OBJECT (track, "failed to read volume, returning NULL"); + g_object_unref (track); + track = NULL; + } + + return track; +} + +/* This is called from the watch thread */ +void +gst_oss4_mixer_slider_process_change_unlocked (GstMixerTrack * track) +{ + GstOss4MixerSlider *s = GST_OSS4_MIXER_SLIDER_CAST (track); + + if (s->mc->mute != NULL && s->mc->mute->changed) { + gst_mixer_mute_toggled (GST_MIXER (s->mixer), track, + !!s->mc->mute->last_val); + } else { + /* nothing to do here, since we don't/can't easily implement the record + * flag */ + } + + if (s->mc->changed) { + gint volumes[2] = { 0, 0 }; + + gst_oss4_mixer_slider_unpack_volume (s, s->mc->last_val, volumes); + + /* if we 'simulate' the mute, update flag when the volume changes */ + if (s->mc->mute == NULL) { + if (volumes[0] == 0 && volumes[1] == 0) { + track->flags |= GST_MIXER_TRACK_MUTE; + } else { + track->flags &= ~GST_MIXER_TRACK_MUTE; + } + } + + gst_mixer_volume_changed (GST_MIXER (s->mixer), track, volumes); + } +} diff --git a/sys/oss4/oss4-mixer-slider.h b/sys/oss4/oss4-mixer-slider.h new file mode 100644 index 0000000000..3bee33f806 --- /dev/null +++ b/sys/oss4/oss4-mixer-slider.h @@ -0,0 +1,70 @@ +/* GStreamer OSS4 mixer slider control + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_OSS4_MIXER_SLIDER_H +#define GST_OSS4_MIXER_SLIDER_H + +#include +#include + +#include "oss4-mixer.h" + +G_BEGIN_DECLS + +#define GST_TYPE_OSS4_MIXER_SLIDER (gst_oss4_mixer_slider_get_type()) +#define GST_OSS4_MIXER_SLIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSS4_MIXER_SLIDER,GstOss4MixerSlider)) +#define GST_OSS4_MIXER_SLIDER_CAST(obj) ((GstOss4MixerSlider *)(obj)) +#define GST_OSS4_MIXER_SLIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSS4_MIXER_SLIDER,GstOss4MixerSliderClass)) +#define GST_IS_OSS4_MIXER_SLIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSS4_MIXER_SLIDER)) +#define GST_IS_OSS4_MIXER_SLIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSS4_MIXER_SLIDER)) + +typedef struct _GstOss4MixerSlider GstOss4MixerSlider; +typedef struct _GstOss4MixerSliderClass GstOss4MixerSliderClass; + +struct _GstOss4MixerSlider { + GstMixerTrack mixer_track; + + GstOss4MixerControl * mc; + GstOss4Mixer * mixer; /* the mixer we belong to (no ref taken) */ + gint volumes[2]; /* left/mono, right */ +}; + +struct _GstOss4MixerSliderClass { + GstMixerTrackClass mixer_track_class; +}; + +GType gst_oss4_mixer_slider_get_type (void); + +GstMixerTrack * gst_oss4_mixer_slider_new (GstOss4Mixer * mixer, GstOss4MixerControl * mc); + +gboolean gst_oss4_mixer_slider_get_volume (GstOss4MixerSlider * s, gint * volumes); + +gboolean gst_oss4_mixer_slider_set_volume (GstOss4MixerSlider * s, const gint * volumes); + +gboolean gst_oss4_mixer_slider_set_record (GstOss4MixerSlider * s, gboolean record); + +gboolean gst_oss4_mixer_slider_set_mute (GstOss4MixerSlider * s, gboolean mute); + +void gst_oss4_mixer_slider_process_change_unlocked (GstMixerTrack * track); + +G_END_DECLS + +#endif /* GST_OSS4_MIXER_SLIDER_H */ + + diff --git a/sys/oss4/oss4-mixer-switch.c b/sys/oss4/oss4-mixer-switch.c new file mode 100644 index 0000000000..403abbc58e --- /dev/null +++ b/sys/oss4/oss4-mixer-switch.c @@ -0,0 +1,169 @@ +/* GStreamer OSS4 mixer on/off switch control + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* A simple ON/OFF 'switch' in gnome-volume-control / GstMixer is represented + * by a GstMixerTrack with no channels. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#define NO_LEGACY_MIXER +#include "oss4-mixer-switch.h" +#include "oss4-soundcard.h" + +GST_DEBUG_CATEGORY_EXTERN (oss4mixer_debug); +#define GST_CAT_DEFAULT oss4mixer_debug + +/* GstMixerTrack is a plain GObject, so let's just use the GLib macro here */ +G_DEFINE_TYPE (GstOss4MixerSwitch, gst_oss4_mixer_switch, GST_TYPE_MIXER_TRACK); + +static void +gst_oss4_mixer_switch_class_init (GstOss4MixerSwitchClass * klass) +{ + /* nothing to do here */ +} + +static void +gst_oss4_mixer_switch_init (GstOss4MixerSwitch * s) +{ + /* nothing to do here */ +} + +static GstMixerTrackFlags +gst_oss4_mixer_switch_get_switch_flag (GstMixerTrack * track) +{ + if ((track->flags & GST_MIXER_TRACK_INPUT)) { + return GST_MIXER_TRACK_RECORD; + } else if ((track->flags & GST_MIXER_TRACK_OUTPUT)) { + return GST_MIXER_TRACK_MUTE; + } else { + GST_ERROR_OBJECT (track, "switch neither input nor output track!?"); + } + return 0; +} + +gboolean +gst_oss4_mixer_switch_set (GstOss4MixerSwitch * s, gboolean enabled) +{ + GstMixerTrackFlags switch_flag; + GstMixerTrack *track; + int newval; + + track = GST_MIXER_TRACK (s); + switch_flag = gst_oss4_mixer_switch_get_switch_flag (track); + + newval = (enabled) ? 1 : 0; + + if (!!newval == !!(track->flags & switch_flag)) { + GST_LOG_OBJECT (s, "switch is already %d, doing nothing", newval); + return TRUE; + } + + if (!gst_oss4_mixer_set_control_val (s->mixer, s->mc, newval)) { + GST_WARNING_OBJECT (s, "could not set switch to %d", newval); + return FALSE; + } + + if (newval) { + track->flags |= switch_flag; + } else { + track->flags &= ~switch_flag; + } + + GST_LOG_OBJECT (s, "set switch to %d", newval); + + return TRUE; +} + +gboolean +gst_oss4_mixer_switch_get (GstOss4MixerSwitch * s, gboolean * enabled) +{ + GstMixerTrackFlags switch_flag; + GstMixerTrack *track; + int val = -1; + + track = GST_MIXER_TRACK (s); + switch_flag = gst_oss4_mixer_switch_get_switch_flag (track); + + if (!gst_oss4_mixer_get_control_val (s->mixer, s->mc, &val) || val < 0) { + GST_WARNING_OBJECT (s, "could not get switch state"); + return FALSE; + } + + *enabled = (val != 0); + + if (!!val != !!(track->flags & switch_flag)) { + GST_INFO_OBJECT (s, "updating inconsistent switch state to %d", !!val); + if (*enabled) { + track->flags |= switch_flag; + } else { + track->flags &= ~switch_flag; + } + } + + + return TRUE; +} + +GstMixerTrack * +gst_oss4_mixer_switch_new (GstOss4Mixer * mixer, GstOss4MixerControl * mc) +{ + GstOss4MixerSwitch *s; + GstMixerTrack *track; + int cur = -1; + + s = g_object_new (GST_TYPE_OSS4_MIXER_SWITCH, "untranslated-label", + mc->mixext.extname, NULL); + + s->mixer = mixer; + s->mc = mc; + + track = GST_MIXER_TRACK (s); + + /* caller will set track->label and track->flags */ + + track->num_channels = 0; + track->min_volume = 0; + track->max_volume = 0; + + if (!gst_oss4_mixer_get_control_val (s->mixer, s->mc, &cur) || cur < 0) + return NULL; + + return track; +} + +/* This is called from the watch thread */ +void +gst_oss4_mixer_switch_process_change_unlocked (GstMixerTrack * track) +{ + GstOss4MixerSwitch *s = GST_OSS4_MIXER_SWITCH_CAST (track); + + if (!s->mc->changed) + return; + + if ((track->flags & GST_MIXER_TRACK_INPUT)) { + gst_mixer_record_toggled (GST_MIXER (s->mixer), track, !!s->mc->last_val); + } else { + gst_mixer_mute_toggled (GST_MIXER (s->mixer), track, !!s->mc->last_val); + } +} diff --git a/sys/oss4/oss4-mixer-switch.h b/sys/oss4/oss4-mixer-switch.h new file mode 100644 index 0000000000..a8ab83ffab --- /dev/null +++ b/sys/oss4/oss4-mixer-switch.h @@ -0,0 +1,65 @@ +/* GStreamer OSS4 mixer on/off switch control + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_OSS4_MIXER_SWITCH_H +#define GST_OSS4_MIXER_SWITCH_H + +#include +#include + +#include "oss4-mixer.h" + +G_BEGIN_DECLS + +#define GST_TYPE_OSS4_MIXER_SWITCH (gst_oss4_mixer_switch_get_type()) +#define GST_OSS4_MIXER_SWITCH(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSS4_MIXER_SWITCH,GstOss4MixerSwitch)) +#define GST_OSS4_MIXER_SWITCH_CAST(obj) ((GstOss4MixerSwitch *)(obj)) +#define GST_OSS4_MIXER_SWITCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSS4_MIXER_SWITCH,GstOss4MixerSwitchClass)) +#define GST_IS_OSS4_MIXER_SWITCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSS4_MIXER_SWITCH)) +#define GST_IS_OSS4_MIXER_SWITCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSS4_MIXER_SWITCH)) + +typedef struct _GstOss4MixerSwitch GstOss4MixerSwitch; +typedef struct _GstOss4MixerSwitchClass GstOss4MixerSwitchClass; + +struct _GstOss4MixerSwitch { + GstMixerTrack mixer_track; + + GstOss4MixerControl * mc; + GstOss4Mixer * mixer; /* the mixer we belong to (no ref taken) */ +}; + +struct _GstOss4MixerSwitchClass { + GstMixerTrackClass mixer_track_class; +}; + +GType gst_oss4_mixer_switch_get_type (void); + +gboolean gst_oss4_mixer_switch_set (GstOss4MixerSwitch * s, gboolean enabled); + +gboolean gst_oss4_mixer_switch_get (GstOss4MixerSwitch * s, gboolean * enabled); + +GstMixerTrack * gst_oss4_mixer_switch_new (GstOss4Mixer * mixer, GstOss4MixerControl * mc); + +void gst_oss4_mixer_switch_process_change_unlocked (GstMixerTrack * track); + +G_END_DECLS + +#endif /* GST_OSS4_MIXER_SWITCH_H */ + + diff --git a/sys/oss4/oss4-mixer.c b/sys/oss4/oss4-mixer.c new file mode 100644 index 0000000000..62e271e5ae --- /dev/null +++ b/sys/oss4/oss4-mixer.c @@ -0,0 +1,1774 @@ +/* GStreamer OSS4 mixer implementation + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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 mixer library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-oss4mixer + * @short_description: element to control sound input and output levels with OSS4 + * + * + * + * This element lets you adjust sound input and output levels with the + * Open Sound System (OSS) version 4. It supports the GstMixer interface, which + * can be used to obtain a list of available mixer tracks. Set the mixer + * element to READY state before using the GstMixer interface on it. + * + * Example pipelines + * + * oss4mixer can't be used in a sensible way in gst-launch. + * + * + * + * Since: 0.10.7 + */ + +/* Note: ioctl calls on the same open mixer device are serialised via + * the object lock to make sure we don't do concurrent ioctls from two + * different threads (e.g. app thread and mixer watch thread), since that + * will probably confuse OSS. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define NO_LEGACY_MIXER + +#include "oss4-audio.h" +#include "oss4-mixer.h" +#include "oss4-mixer-enum.h" +#include "oss4-mixer-slider.h" +#include "oss4-mixer-switch.h" +#include "oss4-property-probe.h" +#include "oss4-soundcard.h" + +#define GST_OSS4_MIXER_WATCH_INTERVAL 500 /* in millisecs, ie. 0.5s */ + +GST_DEBUG_CATEGORY_EXTERN (oss4mixer_debug); +#define GST_CAT_DEFAULT oss4mixer_debug + +#define DEFAULT_DEVICE NULL +#define DEFAULT_DEVICE_NAME NULL + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DEVICE_NAME +}; + +static void gst_oss4_mixer_init_interfaces (GType type); + +GST_BOILERPLATE_FULL (GstOss4Mixer, gst_oss4_mixer, GstElement, + GST_TYPE_ELEMENT, gst_oss4_mixer_init_interfaces); + +static GstStateChangeReturn gst_oss4_mixer_change_state (GstElement * + element, GstStateChange transition); + +static void gst_oss4_mixer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_oss4_mixer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_oss4_mixer_finalize (GObject * object); + +static gboolean gst_oss4_mixer_open (GstOss4Mixer * mixer, + gboolean silent_errors); +static void gst_oss4_mixer_close (GstOss4Mixer * mixer); + +static gboolean gst_oss4_mixer_enum_control_update_enum_list (GstOss4Mixer * m, + GstOss4MixerControl * mc); + +#ifndef GST_DISABLE_DEBUG +static const gchar *mixer_ext_type_get_name (gint type); +static const gchar *mixer_ext_flags_get_string (gint flags); +#endif + +static void +gst_oss4_mixer_base_init (gpointer klass) +{ + gst_element_class_set_details_simple (GST_ELEMENT_CLASS (klass), + "OSS v4 Audio Mixer", "Generic/Audio", + "Control sound input and output levels with OSS4", + "Tim-Philipp Müller "); +} + +static void +gst_oss4_mixer_class_init (GstOss4MixerClass * klass) +{ + GstElementClass *element_class; + GObjectClass *gobject_class; + + element_class = (GstElementClass *) klass; + gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_oss4_mixer_finalize; + gobject_class->set_property = gst_oss4_mixer_set_property; + gobject_class->get_property = gst_oss4_mixer_get_property; + + /** + * GstOss4Mixer:device + * + * OSS4 mixer device (e.g. /dev/oss/hdaudio0/mix0 or /dev/mixerN) + * + **/ + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Device", + "OSS mixer device (e.g. /dev/oss/hdaudio0/mix0 or /dev/mixerN) " + "(NULL = use first mixer device found)", DEFAULT_DEVICE, + G_PARAM_READWRITE)); + + /** + * GstOss4Mixer:device-name + * + * Human-readable name of the sound device. May be NULL if the device is + * not open (ie. when the mixer is in NULL state) + * + **/ + g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Device name", + "Human-readable name of the sound device", DEFAULT_DEVICE_NAME, + G_PARAM_READABLE)); + + element_class->change_state = GST_DEBUG_FUNCPTR (gst_oss4_mixer_change_state); +} + +static void +gst_oss4_mixer_finalize (GObject * obj) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (obj); + + g_free (mixer->device); + + G_OBJECT_CLASS (parent_class)->finalize (obj); +} + +static void +gst_oss4_mixer_reset (GstOss4Mixer * mixer) +{ + mixer->fd = -1; + mixer->need_update = TRUE; + memset (&mixer->last_mixext, 0, sizeof (oss_mixext)); +} + +static void +gst_oss4_mixer_init (GstOss4Mixer * mixer, GstOss4MixerClass * g_class) +{ + mixer->device = g_strdup (DEFAULT_DEVICE); + mixer->device_name = NULL; + + gst_oss4_mixer_reset (mixer); +} + +static void +gst_oss4_mixer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (mixer); + if (!GST_OSS4_MIXER_IS_OPEN (mixer)) { + g_free (mixer->device); + mixer->device = g_value_dup_string (value); + /* unset any cached device-name */ + g_free (mixer->device_name); + mixer->device_name = NULL; + } else { + g_warning ("%s: can't change \"device\" property while mixer is open", + GST_OBJECT_NAME (mixer)); + } + GST_OBJECT_UNLOCK (mixer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_oss4_mixer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (mixer); + g_value_set_string (value, mixer->device); + GST_OBJECT_UNLOCK (mixer); + break; + case PROP_DEVICE_NAME: + GST_OBJECT_LOCK (mixer); + /* If device is set, try to retrieve the name even if we're not open */ + if (mixer->fd == -1 && mixer->device != NULL) { + if (gst_oss4_mixer_open (mixer, TRUE)) { + g_value_set_string (value, mixer->device_name); + gst_oss4_mixer_close (mixer); + } else { + g_value_set_string (value, mixer->device_name); + } + } else { + g_value_set_string (value, mixer->device_name); + } + GST_OBJECT_UNLOCK (mixer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_oss4_mixer_open (GstOss4Mixer * mixer, gboolean silent_errors) +{ + struct oss_mixerinfo mi = { 0, }; + gchar *device; + + g_return_val_if_fail (!GST_OSS4_MIXER_IS_OPEN (mixer), FALSE); + + if (mixer->device) + device = g_strdup (mixer->device); + else + device = gst_oss4_audio_find_device (GST_OBJECT_CAST (mixer)); + + /* desperate times, desperate measures */ + if (device == NULL) + device = g_strdup ("/dev/mixer"); + + GST_INFO_OBJECT (mixer, "Trying to open OSS4 mixer device '%s'", device); + + mixer->fd = open (device, O_RDWR, 0); + if (mixer->fd < 0) + goto open_failed; + + /* Make sure it's OSS4. If it's old OSS, let the old ossmixer handle it */ + if (!gst_oss4_audio_check_version (GST_OBJECT (mixer), mixer->fd)) + goto legacy_oss; + + GST_INFO_OBJECT (mixer, "Opened mixer device '%s', which is mixer %d", + device, mi.dev); + + /* Get device name for currently open fd */ + mi.dev = -1; + if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == 0) { + mixer->modify_counter = mi.modify_counter; + if (mi.name[0] != '\0') { + mixer->device_name = g_strdup (mi.name); + } + } else { + mixer->modify_counter = 0; + } + + if (mixer->device_name == NULL) { + mixer->device_name = g_strdup ("Unknown"); + } + GST_INFO_OBJECT (mixer, "device name = '%s'", mixer->device_name); + + mixer->open_device = device; + + return TRUE; + + /* ERRORS */ +open_failed: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (mixer, RESOURCE, OPEN_READ_WRITE, + (_("Could not open audio device for mixer control handling.")), + GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +legacy_oss: + { + gst_oss4_mixer_close (mixer); + if (!silent_errors) { + GST_ELEMENT_ERROR (mixer, RESOURCE, OPEN_READ_WRITE, + (_("Could not open audio device for mixer control handling." + "This version of the Open Sound System is not supported by this " + "element.")), ("Try the 'ossmixer' element instead")); + } + g_free (device); + return FALSE; + } +} + +static void +gst_oss4_mixer_control_free (GstOss4MixerControl * mc) +{ + g_list_free (mc->children); + g_list_free (mc->mute_group); + g_free (mc->enum_vals); + memset (mc, 0, sizeof (GstOss4MixerControl)); + g_free (mc); +} + +static void +gst_oss4_mixer_free_tracks (GstOss4Mixer * mixer) +{ + g_list_foreach (mixer->tracks, (GFunc) g_object_unref, NULL); + g_list_free (mixer->tracks); + mixer->tracks = NULL; + + g_list_foreach (mixer->controls, (GFunc) gst_oss4_mixer_control_free, NULL); + g_list_free (mixer->controls); + mixer->controls = NULL; +} + +static void +gst_oss4_mixer_close (GstOss4Mixer * mixer) +{ + g_free (mixer->device_name); + mixer->device_name = NULL; + + g_free (mixer->open_device); + mixer->open_device = NULL; + + gst_oss4_mixer_free_tracks (mixer); + + if (mixer->fd != -1) { + close (mixer->fd); + mixer->fd = -1; + } + + gst_oss4_mixer_reset (mixer); +} + +static void +gst_oss4_mixer_watch_process_changes (GstOss4Mixer * mixer) +{ + GList *c, *t, *tracks = NULL; + + GST_INFO_OBJECT (mixer, "mixer interface or control changed"); + + /* this is all with the mixer object lock held */ + + /* we go through the list backwards so we can bail out faster when the entire + * interface needs to be rebuilt */ + for (c = g_list_last (mixer->controls); c != NULL; c = c->prev) { + GstOss4MixerControl *mc = c->data; + oss_mixer_value ossval = { 0, }; + + mc->changed = FALSE; + mc->list_changed = FALSE; + + /* not interested in controls we don't expose in the mixer interface */ + if (!mc->used) + continue; + + /* don't try to read a value from controls that don't have one */ + if (mc->mixext.type == MIXT_DEVROOT || mc->mixext.type == MIXT_GROUP) + continue; + + /* is this an enum control whose list may change? */ + if (mc->mixext.type == MIXT_ENUM && mc->enum_version != 0) { + if (gst_oss4_mixer_enum_control_update_enum_list (mixer, mc)) + mc->list_changed = TRUE; + } + + ossval.dev = mc->mixext.dev; + ossval.ctrl = mc->mixext.ctrl; + ossval.timestamp = mc->mixext.timestamp; + + if (ioctl (mixer->fd, SNDCTL_MIX_READ, &ossval) == -1) { + if (errno == EIDRM || errno == EFAULT) { + GST_DEBUG ("%s has disappeared", mc->mixext.extname); + goto mixer_changed; + } + GST_WARNING_OBJECT (mixer, "MIX_READ failed: %s", g_strerror (errno)); + /* just ignore, move on to next one */ + continue; + } + + if (ossval.value == mc->last_val) { /* no change */ + /* GST_LOG_OBJECT (mixer, "%s hasn't changed", mc->mixext.extname); */ + continue; + } + + mc->last_val = ossval.value; + GST_LOG_OBJECT (mixer, "%s changed value to %u 0x%08x", + mc->mixext.extname, ossval.value, ossval.value); + mc->changed = TRUE; + } + + /* copy list and take track refs, so we can safely drop the object lock, + * which we need to do to be able to post messages on the bus */ + tracks = g_list_copy (mixer->tracks); + g_list_foreach (tracks, (GFunc) g_object_ref, NULL); + + GST_OBJECT_UNLOCK (mixer); + + /* since we don't know (or want to know exactly) which controls belong to + * which track, we just go through the tracks one-by-one now and make them + * check themselves if any of their controls have changed and which messages + * to post on the bus as a result */ + for (t = tracks; t != NULL; t = t->next) { + GstMixerTrack *track = t->data; + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_process_change_unlocked (track); + } else if (GST_IS_OSS4_MIXER_SWITCH (track)) { + gst_oss4_mixer_switch_process_change_unlocked (track); + } else if (GST_IS_OSS4_MIXER_ENUM (track)) { + gst_oss4_mixer_enum_process_change_unlocked (track); + } + + g_object_unref (track); + } + g_list_free (tracks); + + GST_OBJECT_LOCK (mixer); + return; + +mixer_changed: + { + GST_OBJECT_UNLOCK (mixer); + gst_mixer_mixer_changed (GST_MIXER (mixer)); + GST_OBJECT_LOCK (mixer); + return; + } +} + +/* This thread watches the mixer for changes in a somewhat inefficient way + * (running an ioctl every half second or so). This is still better and + * cheaper than apps polling all tracks for changes a few times a second + * though. Needs more thought. There's probably (hopefully) a way to get + * notifications via the fd directly somehow. */ +static gpointer +gst_oss4_mixer_watch_thread (gpointer thread_data) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER_CAST (thread_data); + + GST_DEBUG_OBJECT (mixer, "watch thread running"); + + GST_OBJECT_LOCK (mixer); + while (!mixer->watch_shutdown) { + oss_mixerinfo mi = { 0, }; + GTimeVal tv; + + mi.dev = -1; + if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == 0) { + if (mixer->modify_counter != mi.modify_counter) { + /* GST_LOG ("processing changes"); */ + gst_oss4_mixer_watch_process_changes (mixer); + mixer->modify_counter = mi.modify_counter; + } else { + /* GST_LOG ("no changes"); */ + } + } else { + GST_WARNING_OBJECT (mixer, "MIXERINFO failed: %s", g_strerror (errno)); + } + + /* we could move the _get_current_time out of the loop and just do the + * add in ever iteration, which would be less exact, but who cares */ + g_get_current_time (&tv); + g_time_val_add (&tv, GST_OSS4_MIXER_WATCH_INTERVAL * 1000); + g_cond_timed_wait (mixer->watch_cond, GST_OBJECT_GET_LOCK (mixer), &tv); + } + GST_OBJECT_UNLOCK (mixer); + + GST_DEBUG_OBJECT (mixer, "watch thread done"); + gst_object_unref (mixer); + return NULL; +} + +/* call with object lock held */ +static void +gst_oss4_mixer_wake_up_watch_task (GstOss4Mixer * mixer) +{ + GST_LOG_OBJECT (mixer, "signalling watch thread to wake up"); + g_cond_signal (mixer->watch_cond); +} + +static void +gst_oss4_mixer_stop_watch_task (GstOss4Mixer * mixer) +{ + if (mixer->watch_thread) { + GST_OBJECT_LOCK (mixer); + mixer->watch_shutdown = TRUE; + GST_LOG_OBJECT (mixer, "signalling watch thread to stop"); + g_cond_signal (mixer->watch_cond); + GST_OBJECT_UNLOCK (mixer); + GST_LOG_OBJECT (mixer, "waiting for watch thread to join"); + g_thread_join (mixer->watch_thread); + GST_DEBUG_OBJECT (mixer, "watch thread stopped"); + mixer->watch_thread = NULL; + } + + if (mixer->watch_cond) { + g_cond_free (mixer->watch_cond); + mixer->watch_cond = NULL; + } +} + +static void +gst_oss4_mixer_start_watch_task (GstOss4Mixer * mixer) +{ + GError *err = NULL; + + mixer->watch_cond = g_cond_new (); + mixer->watch_shutdown = FALSE; + + mixer->watch_thread = g_thread_create (gst_oss4_mixer_watch_thread, + gst_object_ref (mixer), TRUE, &err); + + if (mixer->watch_thread == NULL) { + GST_ERROR_OBJECT (mixer, "Could not create watch thread: %s", err->message); + g_cond_free (mixer->watch_cond); + mixer->watch_cond = NULL; + g_error_free (err); + } +} + +static GstStateChangeReturn +gst_oss4_mixer_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstOss4Mixer *mixer = GST_OSS4_MIXER (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_oss4_mixer_open (mixer, FALSE)) + return GST_STATE_CHANGE_FAILURE; + gst_oss4_mixer_start_watch_task (mixer); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL: + gst_oss4_mixer_stop_watch_task (mixer); + gst_oss4_mixer_close (mixer); + break; + default: + break; + } + + return ret; +} + +/* === GstMixer interface === */ + +static inline gboolean +gst_oss4_mixer_contains_track (GstMixer * mixer, GstMixerTrack * track) +{ + return (g_list_find (GST_OSS4_MIXER (mixer)->tracks, track) != NULL); +} + +static inline gboolean +gst_oss4_mixer_contains_options (GstMixer * mixer, GstMixerOptions * options) +{ + return (g_list_find (GST_OSS4_MIXER (mixer)->tracks, options) != NULL); +} + +static void +gst_oss4_mixer_post_mixer_changed_msg (GstOss4Mixer * mixer) +{ + /* only post mixer-changed message once */ + if (!mixer->need_update) { + gst_mixer_mixer_changed (GST_MIXER (mixer)); + mixer->need_update = TRUE; + } +} + +/* call with mixer object lock held to serialise ioctl */ +gboolean +gst_oss4_mixer_get_control_val (GstOss4Mixer * mixer, GstOss4MixerControl * mc, + int *val) +{ + oss_mixer_value ossval = { 0, }; + + if (GST_OBJECT_TRYLOCK (mixer)) { + GST_ERROR ("must be called with mixer lock held"); + GST_OBJECT_UNLOCK (mixer); + } + + ossval.dev = mc->mixext.dev; + ossval.ctrl = mc->mixext.ctrl; + ossval.timestamp = mc->mixext.timestamp; + + if (ioctl (mixer->fd, SNDCTL_MIX_READ, &ossval) == -1) { + if (errno == EIDRM) { + GST_DEBUG_OBJECT (mixer, "MIX_READ failed: mixer interface has changed"); + gst_oss4_mixer_post_mixer_changed_msg (mixer); + } else { + GST_WARNING_OBJECT (mixer, "MIX_READ failed: %s", g_strerror (errno)); + } + *val = 0; + mc->last_val = 0; + return FALSE; + } + + *val = ossval.value; + mc->last_val = ossval.value; + GST_LOG_OBJECT (mixer, "got value 0x%08x from %s)", *val, mc->mixext.extname); + return TRUE; +} + +/* call with mixer object lock held to serialise ioctl */ +gboolean +gst_oss4_mixer_set_control_val (GstOss4Mixer * mixer, GstOss4MixerControl * mc, + int val) +{ + oss_mixer_value ossval = { 0, }; + + ossval.dev = mc->mixext.dev; + ossval.ctrl = mc->mixext.ctrl; + ossval.timestamp = mc->mixext.timestamp; + ossval.value = val; + + if (GST_OBJECT_TRYLOCK (mixer)) { + GST_ERROR ("must be called with mixer lock held"); + GST_OBJECT_UNLOCK (mixer); + } + + if (ioctl (mixer->fd, SNDCTL_MIX_WRITE, &ossval) == -1) { + if (errno == EIDRM) { + GST_LOG_OBJECT (mixer, "MIX_WRITE failed: mixer interface has changed"); + gst_oss4_mixer_post_mixer_changed_msg (mixer); + } else { + GST_WARNING_OBJECT (mixer, "MIX_WRITE failed: %s", g_strerror (errno)); + } + return FALSE; + } + + mc->last_val = val; + GST_LOG_OBJECT (mixer, "set value 0x%08x on %s", val, mc->mixext.extname); + return TRUE; +} + +static gchar * +gst_oss4_mixer_control_get_pretty_name (GstOss4MixerControl * mc) +{ + gchar *name; + +#if 0 + const gchar *name, *u; + + /* "The id field is the original name given by the driver when it called + * mixer_ext_create_control. This name can be used by fully featured GUI + * mixers. However this name should be downshifted and cut before the last + * underscore ("_") to get the proper name. For example mixer control name + * created as "MYDRV_MAINVOL" will become just "mainvol" after this + * transformation." */ + name = mc->mixext.extname; + u = MAX (strrchr (name, '_'), strrchr (name, '.')); + if (u != NULL) + name = u + 1; + + /* maybe capitalize the first letter? */ + return g_ascii_strdown (name, -1); +#endif + /* the .id thing doesn't really seem to work right, ie. for some sliders + * it's just '-' so you have to use the name of the parent control etc. + * let's not use it for now, much too painful. */ + if (g_str_has_prefix (mc->mixext.extname, "misc.")) + name = g_strdup (mc->mixext.extname + 5); + else + name = g_strdup (mc->mixext.extname); + /* chop off trailing digit (only one for now) */ + if (strlen (name) > 0 && g_ascii_isdigit (name[strlen (name) - 1])) + name[strlen (name) - 1] = '\0'; + g_strdelimit (name, ".", ' '); + return name; +} + +#if 0 +/* FIXME: translations for common option strings */ +static struct +{ + const gchar oss_name[32]; + const gchar *label; +} option_labels[] = { + { + "Fast", N_("Fast")}, { + "Low", N_("Low")}, { + "Medium", N_("Medium")}, { + "High", N_("High")}, { + "High+", N_("Very high")}, { + "Production", N_("Production")}, { + "OFF", N_("Off")}, { + "ON", N_("On")}, { + "Stereo", N_("Stereo")}, { + "Multich", N_("Surround sound")}, { + "input-mix", N_("Input mix")}, { + "front", N_("Front")}, { + "rear", N_("Rear")}, { + "side", N_("Side")}, { + "center/LFE", N_("Center / LFE")}, { + "mic", N_("Microphone")}, { + "fp-mic", N_("Front panel microphone")}, { + "input", N_("Input")}, { + "linein", N_("Line-in")}, { + "pcm1", N_("PCM 1")}, { + "pcm2", N_("PCM 2")}, { + "pcm3", N_("PCM 3")}, { +"pcm4", N_("PCM 4")},}; +#endif + +/* these translations are a bit ad-hoc and horribly incomplete; it's not + * really going to work this way with all the different chipsets and drivers */ +static struct +{ + const gchar oss_name[32]; + const gchar *label; +} labels[] = { + /* connectors (e.g. hdaudio) */ + { + "jack.green", N_("Green connector")}, { + "jack.fp-green", N_("Green front panel connector")}, { + "jack.pink", N_("Pink connector")}, { + "jack.fp-pink", N_("Pink front panel connector")}, { + "jack.blue", N_("Blue connector")}, { + "jack.fp-blue", N_("Blue front panel connector")}, { + "jack.orange", N_("Orange connector")}, { + "jack.fp-orange", N_("Orange front panel connector")}, { + "jack.black", N_("Black connector")}, { + "jack.fp-black", N_("Black front panel connector")}, { + "jack.gray", N_("Gray connector")}, { + "jack.fp-gray", N_("Gray front panel connector")}, { + "jack.white", N_("White connector")}, { + "jack.fp-white", N_("White front panel connector")}, { + "jack.red", N_("Red connector")}, { + "jack.fp-red", N_("Red front panel connector")}, { + "jack.yellow", N_("Yellow connector")}, { + "jack.fp-yellow", N_("Yellow front panel connector")}, + /* connector functions (e.g. hdaudio) */ + { + "jack.green.mode", N_("Green connector function")}, { + "jack.fp-green.mode", N_("Green front panel connector function")}, { + "jack.pink.mode", N_("Pink connector function")}, { + "jack.fp-pink.mode", N_("Pink front panel connector function")}, { + "jack.blue.mode", N_("Blue connector function")}, { + "jack.fp-blue.mode", N_("Blue front panel connector function")}, { + "jack.orange.mode", N_("Orange connector function")}, { + "jack.fp-orange.mode", N_("Orange front panel connector function")}, { + "jack.black.mode", N_("Black connector function")}, { + "jack.fp-black.mode", N_("Black front panel connector function")}, { + "jack.gray.mode", N_("Gray connector function")}, { + "jack.fp-gray.mode", N_("Gray front panel connector function")}, { + "jack.white.mode", N_("White connector function")}, { + "jack.fp-white.mode", N_("White front panel connector function")}, { + "jack.red.mode", N_("Red connector function")}, { + "jack.fp-red.mode", N_("Red front panel connector function")}, { + "jack.yellow.mode", N_("Yellow connector function")}, { + "jack.fp-yellow.mode", N_("Yellow front panel connector function")}, + /* other */ + { + "misc.mic", N_("Microphone")}, { + "misc.fp-mic", N_("Front panel microphone")}, { + "misc.linein", N_("Line-in")}, { + "misc.fp-linein", N_("Front panel line-in")}, { + "misc.headphone", N_("Headphones")}, { + "misc.fp-headphone", N_("Front panel headphones")}, { + "misc.front", N_("Front")}, { + "misc.rear", N_("Rear")}, { + "misc.side", N_("Side")}, { + "misc.center/lfe", N_("Center / LFE")}, { + "misc.pcm", N_("PCM")}, { + "misc.input-mix", N_("Input mix")} + /* FIXME translate Audigy NX USB labels) */ +/* + { "rec.src", N_("Record Source") }, + { "output.mute", N_("Mute output") } + headph (Controller group) + headph.src (Enumeration control) + headph.mute (On/Off switch) + digital2 (Controller group) + digital2.src (Enumeration control) + digital2.mute (On/Off switch) + digital (Controller group) + digital.mute1 (On/Off switch) + digital.vol (Controller group) + digital.vol.front (Stereo slider (0-255)) + digital.vol.surr (Stereo slider (0-255)) + digital.vol.c/l (Stereo slider (0-255)) + digital.vol.center (Stereo slider (0-255)) + digital.mute2 (On/Off switch) + digital.vol (Stereo slider (0-255)) + line (Controller group) + line.mute (On/Off switch) + line.vol (Stereo slider (0-255)) + play-altset (Enumeration control) + rec-altset (Enumeration control) +*/ +}; + +/* Decent i18n is pretty much impossible with OSS's way of providing us with + * mixer labels (and the fact that they are pretty much random), but that + * doesn't mean we shouldn't at least try. */ +const gchar * +gst_oss4_mixer_control_get_translated_name (GstOss4MixerControl * mc) +{ + gchar name[33] = { 0, }; + char vmix_str[32] = { '\0', }; + int dummy, i; + + /* main virtual mixer controls (we hide the stream volumes) */ + if (sscanf (mc->mixext.extname, "vmix%d-%32c", &dummy, vmix_str) == 2) { + if (strcmp (vmix_str, "src") == 0) + return _("Virtual mixer input"); + else if (strcmp (vmix_str, "vol") == 0) + return _("Virtual mixer output"); + else if (strcmp (vmix_str, "channels") == 0) + return _("Virtual mixer channel configuration"); + } + + /* munge connector.foo => jack.foo (change from 4.0 -> 4.1) */ + if (g_str_has_prefix (mc->mixext.extname, "connector.")) { + g_snprintf (name, sizeof (name), "jack.%s", mc->mixext.extname + 10); + } else { + g_strlcpy (name, mc->mixext.extname, sizeof (name)); + } + + /* munge foo.function => foo.mode (change from 4.0 -> 4.1) */ + if (g_str_has_suffix (name, ".function")) + memcpy (name + strlen (name) - strlen (".function"), ".mode", 5 + 1); + + /* chop off trailing numbers */ + while (strlen (name) > 0 && g_ascii_isdigit (name[strlen (name) - 1])) + name[strlen (name) - 1] = '\0'; + + for (i = 0; i < G_N_ELEMENTS (labels); ++i) { + if (strcmp (name, labels[i].oss_name) == 0) + return _(labels[i].label); + } + + g_strdelimit (name, ".", ' '); + return g_quark_to_string (g_quark_from_string (name)); /* eek */ +} + +#ifndef GST_DISABLE_DEBUG +static const gchar * +mixer_ext_type_get_name (gint type) +{ + switch (type) { + case MIXT_DEVROOT: + return "Device root entry"; + case MIXT_GROUP: + return "Controller group"; + case MIXT_ONOFF: + return "On/Off switch"; + case MIXT_ENUM: + return "Enumeration control"; + case MIXT_MONOSLIDER: + return "Mono slider (0-255)"; + case MIXT_STEREOSLIDER: + return "Stereo slider (0-255)"; + case MIXT_MESSAGE: + return "Textual message"; /* whatever this is */ + case MIXT_MONOVU: + return "Mono VU meter value"; + case MIXT_STEREOVU: + return "Stereo VU meter value"; + case MIXT_MONOPEAK: + return "Mono VU meter peak value"; + case MIXT_STEREOPEAK: + return "Stereo VU meter peak value"; + case MIXT_RADIOGROUP: + return "Radio button group"; + case MIXT_MARKER: /* Separator between normal and extension entries */ + return "Separator"; + case MIXT_VALUE: + return "Decimal value entry"; + case MIXT_HEXVALUE: + return "Hex value entry"; + case MIXT_SLIDER: + return "Mono slider (31-bit value range)"; + case MIXT_3D: + return "3D"; /* what's this? */ + case MIXT_MONOSLIDER16: + return "Mono slider (0-32767)"; + case MIXT_STEREOSLIDER16: + return "Stereo slider (0-32767)"; + case MIXT_MUTE: + return "Mute switch"; + default: + break; + } + return "unknown"; +} +#endif /* GST_DISABLE_DEBUG */ + +#ifndef GST_DISABLE_DEBUG +static const gchar * +mixer_ext_flags_get_string (gint flags) +{ + struct + { + gint flag; + gchar nick[16]; + } all_flags[] = { + /* first the important ones */ + { + MIXF_MAINVOL, "MAINVOL"}, { + MIXF_PCMVOL, "PCMVOL"}, { + MIXF_RECVOL, "RECVOL"}, { + MIXF_MONVOL, "MONVOL"}, { + MIXF_DESCR, "DESCR"}, + /* now the rest in the right order */ + { + MIXF_READABLE, "READABLE"}, { + MIXF_WRITEABLE, "WRITABLE"}, { + MIXF_POLL, "POLL"}, { + MIXF_HZ, "HZ"}, { + MIXF_STRING, "STRING"}, { + MIXF_DYNAMIC, "DYNAMIC"}, { + MIXF_OKFAIL, "OKFAIL"}, { + MIXF_FLAT, "FLAT"}, { + MIXF_LEGACY, "LEGACY"}, { + MIXF_CENTIBEL, "CENTIBEL"}, { + MIXF_DECIBEL, "DECIBEL"}, { + MIXF_WIDE, "WIDE"} + }; + GString *s; + GQuark q; + gint i; + + if (flags == 0) + return "None"; + + s = g_string_new (""); + for (i = 0; i < G_N_ELEMENTS (all_flags); ++i) { + if ((flags & all_flags[i].flag)) { + if (s->len > 0) + g_string_append (s, " | "); + g_string_append (s, all_flags[i].nick); + flags &= ~all_flags[i].flag; /* unset */ + } + } + + /* unknown flags? */ + if (flags != 0) { + if (s->len > 0) + g_string_append (s, " | "); + g_string_append (s, "???"); + } + + /* we'll just put it into the global quark table (cheeky, eh?) */ + q = g_quark_from_string (s->str); + g_string_free (s, TRUE); + + return g_quark_to_string (q); +} +#endif /* GST_DISABLE_DEBUG */ + +#ifndef GST_DISABLE_DEBUG +static void +gst_oss4_mixer_control_dump_tree (GstOss4MixerControl * mc, gint depth) +{ + GList *c; + gchar spaces[64]; + gint i; + + depth = MIN (sizeof (spaces) - 1, depth); + for (i = 0; i < depth; ++i) + spaces[i] = ' '; + spaces[i] = '\0'; + + GST_LOG ("%s%s (%s)", spaces, mc->mixext.extname, + mixer_ext_type_get_name (mc->mixext.type)); + for (c = mc->children; c != NULL; c = c->next) { + GstOss4MixerControl *child_mc = (GstOss4MixerControl *) c->data; + + gst_oss4_mixer_control_dump_tree (child_mc, depth + 2); + } +} +#endif /* GST_DISABLE_DEBUG */ + +static GList * +gst_oss4_mixer_get_controls (GstOss4Mixer * mixer) +{ + GstOss4MixerControl *root_mc = NULL; + oss_mixerinfo mi = { 0, }; + GList *controls = NULL; + GList *l; + guint i; + + /* Get info for currently open fd */ + mi.dev = -1; + if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == -1) + goto no_mixerinfo; + + if (mi.nrext <= 0) { + GST_DEBUG ("mixer %s has no controls", mi.id); + return NULL; + } + + GST_INFO ("Reading controls for mixer %s", mi.id); + + for (i = 0; i < mi.nrext; ++i) { + GstOss4MixerControl *mc; + oss_mixext mix_ext = { 0, }; + + mix_ext.dev = mi.dev; + mix_ext.ctrl = i; + + if (ioctl (mixer->fd, SNDCTL_MIX_EXTINFO, &mix_ext) == -1) { + GST_DEBUG ("SNDCTL_MIX_EXTINFO failed on mixer %s, control %d: %s", + mi.id, i, g_strerror (errno)); + continue; + } + + /* if this is the last one, save it for is-interface-up-to-date checking */ + if (i == mi.nrext) + mixer->last_mixext = mix_ext; + + mc = g_new0 (GstOss4MixerControl, 1); + mc->mixext = mix_ext; + + /* both control_no and desc fields are pretty useless, ie. not always set, + * if ever, so not listed here */ + GST_INFO ("Control %d", mix_ext.ctrl); + GST_INFO (" name : %s", mix_ext.extname); + GST_INFO (" type : %s (%d)", mixer_ext_type_get_name (mix_ext.type), + mix_ext.type); + GST_INFO (" flags : %s (0x%04x)", + mixer_ext_flags_get_string (mix_ext.flags), mix_ext.flags); + GST_INFO (" parent : %d", mix_ext.parent); + + /* get tooltip (just for informational purposes for now) */ + if (MIXEXT_HAS_DESCRIPTION (mix_ext)) { + oss_mixer_enuminfo desc = { 0, }; + + desc.dev = mix_ext.dev; + desc.ctrl = mix_ext.ctrl; + if (ioctl (mixer->fd, SNDCTL_MIX_DESCRIPTION, &desc) >= 0) { + /* "The string may contain multiple lines. The first line is the + * 'tooltip'. Optional subsequent lines may contain more detailed + * help text. Lines are separated by a linefeed character." */ + g_strdelimit (&desc.strings[desc.strindex[0]], "\n\r", '\0'); + GST_INFO (" tooltip: %s", &desc.strings[desc.strindex[0]]); + } + } + + if (!MIXEXT_IS_ROOT (mix_ext)) { + /* find parent (we assume it comes in the list before the child) */ + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *parent_mc = (GstOss4MixerControl *) l->data; + + if (parent_mc->mixext.ctrl == mix_ext.parent) { + mc->parent = parent_mc; + parent_mc->children = g_list_append (parent_mc->children, mc); + break; + } + } + if (mc->parent == NULL) { + GST_ERROR_OBJECT (mixer, "couldn't find parent for control %d", i); + g_free (mc); + continue; + } + } else if (root_mc == NULL) { + root_mc = mc; + } else { + GST_WARNING_OBJECT (mixer, "two root controls?!"); + } + + controls = g_list_prepend (controls, mc); + } + +#ifndef GST_DISABLE_DEBUG + gst_oss4_mixer_control_dump_tree (root_mc, 0); +#endif + + return g_list_reverse (controls); + +/* ERRORS */ +no_mixerinfo: + { + GST_WARNING ("SNDCTL_MIXERINFO failed on mixer device %s: %s", mi.id, + g_strerror (errno)); + return NULL; + } +} + +static void +gst_oss4_mixer_controls_guess_master (GstOss4Mixer * mixer, + const GList * controls) +{ + GstOss4MixerControl *firstpcm_mc = NULL; + GstOss4MixerControl *master_mc = NULL; + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (((mc->mixext.flags & MIXF_MAINVOL)) && master_mc == NULL) { + GST_INFO_OBJECT (mixer, "Master control: %s", mc->mixext.extname); + master_mc = mc; + break; + } + /* do we need to check if it's a slider type here? */ + if (((mc->mixext.flags & MIXF_PCMVOL)) && firstpcm_mc == NULL) { + GST_INFO_OBJECT (mixer, "First PCM control: %s", mc->mixext.extname); + firstpcm_mc = mc; + } + } + + /* if no control with MIXF_MAINVOL found, use first one with PCMVOL */ + if (master_mc == NULL && firstpcm_mc != NULL) { + GST_INFO_OBJECT (mixer, "Marking first PCM control as master: %s", + firstpcm_mc->mixext.extname); + master_mc = firstpcm_mc; + } + + if (master_mc != NULL) + master_mc->is_master = TRUE; +} + +/* type: -1 for all types, otherwise just return siblings with requested type */ +static GList * +gst_oss4_mixer_control_get_siblings (GstOss4MixerControl * mc, gint type) +{ + GList *s, *siblings = NULL; + + if (mc->parent == NULL) + return NULL; + + for (s = mc->parent->children; s != NULL; s = s->next) { + GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data; + + if (mc != sibling && (type < 0 || sibling->mixext.type == type)) + siblings = g_list_append (siblings, sibling); + } + + return siblings; +} + +static void +gst_oss4_mixer_controls_find_sliders (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + GList *s, *siblings; + + if (!MIXEXT_IS_SLIDER (mc->mixext) || mc->parent == NULL || mc->used) + continue; + + mc->is_slider = TRUE; + mc->used = TRUE; + + siblings = gst_oss4_mixer_control_get_siblings (mc, -1); + + /* Note: the names can be misleading and may not reflect the actual + * hierarchy of the controls, e.g. it's possible that a slider is called + * connector.green and the mute control then connector.green.mute, but + * both controls are in fact siblings and both children of the group + * 'green' instead of mute being a child of connector.green as the naming + * would seem to suggest */ + GST_LOG ("Slider: %s, parent=%s, %d siblings", mc->mixext.extname, + mc->parent->mixext.extname, g_list_length (siblings)); + + for (s = siblings; s != NULL; s = s->next) { + GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data; + + GST_LOG (" %s (%s)", sibling->mixext.extname, + mixer_ext_type_get_name (sibling->mixext.type)); + + if (sibling->mixext.type == MIXT_MUTE || + g_str_has_suffix (sibling->mixext.extname, ".mute")) { + /* simple case: slider with single mute sibling. We assume the .mute + * suffix in the name won't change - can't really do much else anyway */ + if (sibling->mixext.type == MIXT_ONOFF || + sibling->mixext.type == MIXT_MUTE) { + GST_LOG (" mute control for %s is %s", mc->mixext.extname, + sibling->mixext.extname); + mc->mute = sibling; + sibling->used = TRUE; + } + /* a group of .mute controls. We assume they are all switches here */ + if (sibling->mixext.type == MIXT_GROUP) { + GList *m; + + for (m = sibling->children; m != NULL; m = m->next) { + GstOss4MixerControl *grouped_sibling = m->data; + + if (grouped_sibling->mixext.type == MIXT_ONOFF || + grouped_sibling->mixext.type == MIXT_MUTE) { + GST_LOG (" %s is grouped mute control for %s", + grouped_sibling->mixext.extname, mc->mixext.extname); + mc->mute_group = g_list_append (mc->mute_group, grouped_sibling); + } + } + + GST_LOG (" %s has a group of %d mute controls", + mc->mixext.extname, g_list_length (mc->mute_group)); + + /* we don't mark the individual mute controls as used, only the + * group control, as we still want individual switches for the + * individual controls */ + sibling->used = TRUE; + } + } + } + g_list_free (siblings); + } +} + +/* should be called with the mixer object lock held because of the ioctl; + * returns TRUE if the list was read the first time or modified */ +static gboolean +gst_oss4_mixer_enum_control_update_enum_list (GstOss4Mixer * mixer, + GstOss4MixerControl * mc) +{ + oss_mixer_enuminfo ei = { 0, }; + guint num_existing = 0; + int i; + + /* count and existing values */ + while (mc->enum_vals != NULL && mc->enum_vals[num_existing] != 0) + ++num_existing; + + ei.dev = mc->mixext.dev; + ei.ctrl = mc->mixext.ctrl; + + /* if we have create a generic list with numeric IDs already and the + * number of values hasn't changed, then there's not much to do here */ + if (mc->no_list && mc->enum_vals != NULL && + num_existing == mc->mixext.maxvalue) { + return FALSE; + } + + /* if we have a list and it doesn't change, there's nothing to do either */ + if (mc->enum_vals != NULL && mc->enum_version == 0) + return FALSE; + + if (ioctl (mixer->fd, SNDCTL_MIX_ENUMINFO, &ei) == -1) { + g_free (mc->enum_vals); + mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1); + + GST_DEBUG ("no enum info available, creating numeric values from 0-%d", + mc->mixext.maxvalue - 1); + + /* "It is possible that some enum controls don't have any name list + * available. In this case the application should automatically generate + * list of numbers (0 to N-1)" */ + for (i = 0; i < mc->mixext.maxvalue; ++i) { + gchar num_str[8]; + + g_snprintf (num_str, sizeof (num_str), "%d", i); + mc->enum_vals[i] = g_quark_from_string (num_str); + } + mc->enum_version = 0; /* the only way to change is via maxvalue */ + } else { + /* old list same as current list? */ + if (mc->enum_vals != NULL && mc->enum_version == ei.version) + return FALSE; + + /* no list yet, or the list has changed */ + GST_LOG ("%s", (mc->enum_vals) ? "enum list has changed" : "reading list"); + if (ei.nvalues != mc->mixext.maxvalue) { + GST_WARNING_OBJECT (mixer, "Enum: %s, nvalues %d != maxvalue %d", + mc->mixext.extname, ei.nvalues, mc->mixext.maxvalue); + mc->mixext.maxvalue = MIN (ei.nvalues, mc->mixext.maxvalue); + } + + mc->mixext.maxvalue = MIN (mc->mixext.maxvalue, OSS_ENUM_MAXVALUE); + + g_free (mc->enum_vals); + mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1); + for (i = 0; i < mc->mixext.maxvalue; ++i) { + GST_LOG (" %s", ei.strings + ei.strindex[i]); + mc->enum_vals[i] = g_quark_from_string (ei.strings + ei.strindex[i]); + } + } + + return TRUE; +} + +static void +gst_oss4_mixer_controls_find_enums (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (mc->mixext.type != MIXT_ENUM || mc->used) + continue; + + mc->is_enum = TRUE; + mc->used = TRUE; + + /* Note: enums are special: for most controls, the maxvalue is inclusive, + * but for enum controls it's actually exclusive (boggle), so that + * mixext.maxvalue = num_values */ + + GST_LOG ("Enum: %s, parent=%s, num_enums=%d", mc->mixext.extname, + mc->parent->mixext.extname, mc->mixext.maxvalue); + + gst_oss4_mixer_enum_control_update_enum_list (mixer, mc); + } +} + +static void +gst_oss4_mixer_controls_find_switches (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (mc->used) + continue; + + if (mc->mixext.type != MIXT_ONOFF && mc->mixext.type != MIXT_MUTE) + continue; + + mc->is_switch = TRUE; + mc->used = TRUE; + + GST_LOG ("Switch: %s, parent=%s", mc->mixext.extname, + mc->parent->mixext.extname); + } +} + +static void +gst_oss4_mixer_controls_find_virtual (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + /* or sscanf (mc->mixext.extname, "vmix%d-out.", &n) == 1 ? */ + /* for now we just flag all virtual controls with managed labels, those + * are really more appropriate for a pavucontrol-type control thing than + * the (more hardware-oriented) mixer interface */ + if (mc->mixext.id[0] == '@') { + mc->is_virtual = TRUE; + GST_LOG ("%s is virtual control with managed label", mc->mixext.extname); + } + } +} + +static void +gst_oss4_mixer_controls_dump_unused (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (mc->used) + continue; + + switch (mc->mixext.type) { + case MIXT_DEVROOT: + case MIXT_GROUP: + case MIXT_MESSAGE: + case MIXT_MONOVU: + case MIXT_STEREOVU: + case MIXT_MONOPEAK: + case MIXT_STEREOPEAK: + case MIXT_MARKER: + continue; /* not interested in these types of controls */ + case MIXT_MONODB: + case MIXT_STEREODB: + GST_DEBUG ("obsolete control type %d", mc->mixext.type); + continue; + case MIXT_MONOSLIDER: + case MIXT_STEREOSLIDER: + case MIXT_SLIDER: + case MIXT_MONOSLIDER16: + case MIXT_STEREOSLIDER16: + /* this shouldn't happen */ + GST_ERROR ("unused slider control?!"); + continue; + case MIXT_VALUE: + case MIXT_HEXVALUE: + /* value entry, not sure what to do with that, skip for now */ + continue; + case MIXT_ONOFF: + case MIXT_MUTE: + case MIXT_ENUM: + case MIXT_3D: + case MIXT_RADIOGROUP: + GST_DEBUG ("FIXME: handle %s %s", + mixer_ext_type_get_name (mc->mixext.type), mc->mixext.extname); + break; + default: + GST_WARNING ("unknown control type %d", mc->mixext.type); + continue; + } + } +} + +static GList * +gst_oss4_mixer_create_tracks (GstOss4Mixer * mixer, const GList * controls) +{ + const GList *c; + GList *tracks = NULL; + + for (c = controls; c != NULL; c = c->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) c->data; + GstMixerTrack *track = NULL; + + if (mc->is_virtual) + continue; + + if (mc->is_slider) { + track = gst_oss4_mixer_slider_new (mixer, mc); + } else if (mc->is_enum) { + track = gst_oss4_mixer_enum_new (mixer, mc); + } else if (mc->is_switch) { + track = gst_oss4_mixer_switch_new (mixer, mc); + } + + if (track == NULL) + continue; + + track->label = gst_oss4_mixer_control_get_pretty_name (mc); + track->flags = 0; + + GST_LOG ("translated label: %s [%s] = %s", track->label, mc->mixext.id, + gst_oss4_mixer_control_get_translated_name (mc)); + + /* This whole 'a track is either INPUT or OUTPUT' model is just flawed, + * esp. if a slider's role can be changed on the fly, like when you change + * function of a connector. What should we do in that case? Change the flag + * and make the app rebuild the interface? Ignore it? */ + if (g_str_has_prefix (mc->mixext.extname, "record.")) { + mc->is_output = FALSE; + mc->is_input = TRUE; + } + + /* FIXME: determine is_input and is_output */ + /* must be either INPUT or OUTPUT (but not both and not neither) for now, + * or gnome-volume-control aborts */ + if (mc->is_input) + track->flags |= GST_MIXER_TRACK_INPUT; + else if (mc->is_output) + track->flags |= GST_MIXER_TRACK_OUTPUT; + else + track->flags |= GST_MIXER_TRACK_OUTPUT; + + if (mc->is_master) + track->flags |= GST_MIXER_TRACK_MASTER; + + tracks = g_list_append (tracks, track); + } + + return tracks; +} + +static void +gst_oss4_mixer_update_tracks (GstOss4Mixer * mixer) +{ + GList *controls, *tracks; + + /* read and process controls */ + controls = gst_oss4_mixer_get_controls (mixer); + + gst_oss4_mixer_controls_guess_master (mixer, controls); + + gst_oss4_mixer_controls_find_sliders (mixer, controls); + + gst_oss4_mixer_controls_find_enums (mixer, controls); + + gst_oss4_mixer_controls_find_switches (mixer, controls); + + gst_oss4_mixer_controls_find_virtual (mixer, controls); + + gst_oss4_mixer_controls_dump_unused (mixer, controls); + + tracks = gst_oss4_mixer_create_tracks (mixer, controls); + + /* free old tracks and controls */ + gst_oss4_mixer_free_tracks (mixer); + + /* replace old with new */ + mixer->tracks = tracks; + mixer->controls = controls; +} + +static const GList * +gst_oss4_mixer_list_tracks (GstMixer * mixer_iface) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (mixer_iface); + + g_return_val_if_fail (mixer != NULL, NULL); + g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL); + + GST_OBJECT_LOCK (mixer); + + /* Do a read on the last control to check if the interface has changed */ + if (!mixer->need_update && mixer->last_mixext.ctrl > 0) { + GstOss4MixerControl mc = { {0,} + , + }; + int val; + + mc.mixext = mixer->last_mixext; + gst_oss4_mixer_get_control_val (mixer, &mc, &val); + } + + if (mixer->need_update || mixer->tracks == NULL) { + gst_oss4_mixer_update_tracks (mixer); + mixer->need_update = FALSE; + } + + GST_OBJECT_UNLOCK (mixer); + + return (const GList *) mixer->tracks; +} + +static void +gst_oss4_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track, + gint * volumes) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + g_return_if_fail (volumes != NULL); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_set_volume (GST_OSS4_MIXER_SLIDER (track), volumes); + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track, + gint * volumes) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + g_return_if_fail (volumes != NULL); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + memset (volumes, 0, track->num_channels * sizeof (gint)); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_get_volume (GST_OSS4_MIXER_SLIDER (track), volumes); + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_set_record (GstMixer * mixer, GstMixerTrack * track, + gboolean record) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_set_record (GST_OSS4_MIXER_SLIDER (track), record); + } else if (GST_IS_OSS4_MIXER_SWITCH (track)) { + if ((track->flags & GST_MIXER_TRACK_INPUT)) { + gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), record); + } else { + GST_WARNING_OBJECT (track, "set_record called on non-INPUT track"); + } + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_set_mute (GstMixer * mixer, GstMixerTrack * track, gboolean mute) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_set_mute (GST_OSS4_MIXER_SLIDER (track), mute); + } else if (GST_IS_OSS4_MIXER_SWITCH (track)) { + if ((track->flags & GST_MIXER_TRACK_OUTPUT)) { + gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), mute); + } else { + GST_WARNING_OBJECT (track, "set_mute called on non-OUTPUT track"); + } + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_set_option (GstMixer * mixer, GstMixerOptions * options, + gchar * value) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (value != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (GST_IS_OSS4_MIXER_ENUM (options)); + g_return_if_fail (gst_oss4_mixer_contains_options (mixer, options)); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (!gst_oss4_mixer_enum_set_option (GST_OSS4_MIXER_ENUM (options), value)) { + /* not much we can do here but wake up the watch thread early, so it + * can do its thing and post messages if anything has changed */ + gst_oss4_mixer_wake_up_watch_task (oss); + } + + GST_OBJECT_UNLOCK (oss); +} + +static const gchar * +gst_oss4_mixer_get_option (GstMixer * mixer, GstMixerOptions * options) +{ + GstOss4Mixer *oss; + const gchar *current_val; + + g_return_val_if_fail (mixer != NULL, NULL); + g_return_val_if_fail (GST_IS_OSS4_MIXER (mixer), NULL); + g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL); + g_return_val_if_fail (GST_IS_OSS4_MIXER_ENUM (options), NULL); + g_return_val_if_fail (gst_oss4_mixer_contains_options (mixer, options), NULL); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + current_val = gst_oss4_mixer_enum_get_option (GST_OSS4_MIXER_ENUM (options)); + + if (current_val == NULL) { + /* not much we can do here but wake up the watch thread early, so it + * can do its thing and post messages if anything has changed */ + gst_oss4_mixer_wake_up_watch_task (oss); + } + + GST_OBJECT_UNLOCK (oss); + + return current_val; +} + +static GstMixerFlags +gst_oss4_mixer_get_mixer_flags (GstMixer * mixer) +{ + return GST_MIXER_FLAG_AUTO_NOTIFICATIONS; +} + +static void +gst_oss4_mixer_interface_init (GstMixerClass * klass) +{ + GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE; + + klass->list_tracks = gst_oss4_mixer_list_tracks; + klass->set_volume = gst_oss4_mixer_set_volume; + klass->get_volume = gst_oss4_mixer_get_volume; + klass->set_mute = gst_oss4_mixer_set_mute; + klass->set_record = gst_oss4_mixer_set_record; + klass->set_option = gst_oss4_mixer_set_option; + klass->get_option = gst_oss4_mixer_get_option; + klass->get_mixer_flags = gst_oss4_mixer_get_mixer_flags; +} + +/* Implement the horror that is GstImplementsInterface */ + +static gboolean +gst_oss4_mixer_supported (GstImplementsInterface * iface, GType iface_type) +{ + GstOss4Mixer *mixer; + + g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE); + + mixer = GST_OSS4_MIXER (iface); + + return GST_OSS4_MIXER_IS_OPEN (mixer); +} + +static void +gst_oss4_mixer_implements_interface_init (GstImplementsInterfaceClass * klass) +{ + klass->supported = gst_oss4_mixer_supported; +} + +static void +gst_oss4_mixer_init_interfaces (GType type) +{ + static const GInterfaceInfo implements_iface_info = { + (GInterfaceInitFunc) gst_oss4_mixer_implements_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo mixer_iface_info = { + (GInterfaceInitFunc) gst_oss4_mixer_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, + &implements_iface_info); + g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_iface_info); + + gst_oss4_add_property_probe_interface (type); +} diff --git a/sys/oss4/oss4-mixer.h b/sys/oss4/oss4-mixer.h new file mode 100644 index 0000000000..4eaff50c5c --- /dev/null +++ b/sys/oss4/oss4-mixer.h @@ -0,0 +1,128 @@ +/* GStreamer OSS4 mixer implementation + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef OSS4_MIXER_H +#define OSS4_MIXER_H + +#include + +#include "oss4-soundcard.h" + +#define GST_OSS4_MIXER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSS4_MIXER,GstOss4Mixer)) +#define GST_OSS4_MIXER_CAST(obj) ((GstOss4Mixer *)(obj)) +#define GST_OSS4_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSS4_MIXER,GstOss4MixerClass)) +#define GST_IS_OSS4_MIXER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSS4_MIXER)) +#define GST_IS_OSS4_MIXER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSS4_MIXER)) +#define GST_TYPE_OSS4_MIXER (gst_oss4_mixer_get_type()) + +#define GST_OSS4_MIXER_IS_OPEN(obj) (GST_OSS4_MIXER(obj)->fd != -1) + +typedef struct _GstOss4Mixer GstOss4Mixer; +typedef struct _GstOss4MixerClass GstOss4MixerClass; + +struct _GstOss4Mixer { + GstElement element; + + /*< private >*/ + + /* element bits'n'bops */ + gchar * device; + + /* mixer details */ + gint fd; /* file descriptor if open, or -1 */ + gchar * device_name; /* device description, or NULL */ + gchar * open_device; /* the device we opened */ + + GList * tracks; /* list of available tracks */ + GList * controls; /* list of available controls */ + gboolean need_update; /* re-read list of available tracks? */ + + oss_mixext last_mixext; /* we keep this around so we can + * easily check if the mixer + * interface has changed */ + + GThread * watch_thread; /* thread watching for value changes */ + GCond * watch_cond; + gint watch_shutdown; + gint modify_counter; /* from MIXERINFO */ + + /* for property probe interface */ + GList * property_probe_list; +}; + +struct _GstOss4MixerClass { + GstElementClass element_class; +}; + +/* helper struct holding info about one control */ +typedef struct _GstOss4MixerControl GstOss4MixerControl; + +struct _GstOss4MixerControl { + oss_mixext mixext; + GstOss4MixerControl *parent; /* NULL if root */ + GstOss4MixerControl *mute; /* sibling with mute function, or NULL */ + GList *mute_group; /* group of mute controls, or NULL */ + GList *children; /* GstOss4MixerControls (no ownership) */ + + GQuark *enum_vals; /* 0-terminated array of values or NULL */ + int enum_version; /* 0 = list won't change */ + + int last_val; /* last value seen */ + + gboolean is_virtual : 1; /* is a vmix control with dynamic label */ + gboolean is_master : 1; + gboolean is_slider : 1; /* represent as slider */ + gboolean is_switch : 1; /* represent as switch */ + gboolean is_enum : 1; /* represent as combo/enumeration */ + gboolean no_list : 1; /* enumeration with no list available */ + gboolean is_input : 1; /* is an input-related control */ + gboolean is_output : 1; /* is an output-related control */ + gboolean used : 1; /* whether we know what to do with this */ + + gboolean changed : 1; /* transient flag used by watch thread */ + gboolean list_changed : 1; /* transient flag used by watch thread */ +}; + +/* header says parent=-1 means root, but it can also be parent=ctrl */ +#define MIXEXT_IS_ROOT(me) ((me).parent == -1 || (me).parent == (me).ctrl) + +#define MIXEXT_IS_SLIDER(me) ((me).type == MIXT_MONOSLIDER || \ + (me).type == MIXT_STEREOSLIDER || (me).type == MIXT_MONOSLIDER16 || \ + (me).type == MIXT_STEREOSLIDER16 || (me).type == MIXT_SLIDER) + +#define MIXEXT_HAS_DESCRIPTION(me) (((me).flags & MIXF_DESCR) != 0) + +#define MIXEXT_ENUM_IS_AVAILABLE(me,num) \ + (((me).enum_present[num/8]) & (1 << (num % 8))) + + +GType gst_oss4_mixer_get_type (void); + +gboolean gst_oss4_mixer_get_control_val (GstOss4Mixer * mixer, + GstOss4MixerControl * mc, + int * val); + +gboolean gst_oss4_mixer_set_control_val (GstOss4Mixer * mixer, + GstOss4MixerControl * mc, + int val); + +G_END_DECLS + +#endif /* OSS4_MIXER_H */ + diff --git a/sys/oss4/oss4-property-probe.c b/sys/oss4/oss4-property-probe.c new file mode 100644 index 0000000000..e4b5667995 --- /dev/null +++ b/sys/oss4/oss4-property-probe.c @@ -0,0 +1,396 @@ +/* GStreamer OSS4 audio property probe interface implementation + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#define NO_LEGACY_MIXER +#include "oss4-audio.h" +#include "oss4-mixer.h" +#include "oss4-sink.h" +#include "oss4-source.h" +#include "oss4-soundcard.h" +#include "oss4-property-probe.h" + +#include +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_EXTERN (oss4_debug); +#define GST_CAT_DEFAULT oss4_debug + +static const GList * +gst_oss4_property_probe_get_properties (GstPropertyProbe * probe) +{ + GObjectClass *klass = G_OBJECT_GET_CLASS (probe); + GList *list; + + GST_OBJECT_LOCK (GST_OBJECT (probe)); + + /* we create a new list and store it in the instance struct, since apparently + * we forgot to update the API for 0.10 (and why don't we mark probable + * properties with a flag instead anyway?). A bit hackish, but what can you + * do (can't really use a static variable since the pspec will be different + * for src and sink class); this isn't particularly pretty, but the best + * we can do given that we can't create a common base class (we could do + * fancy things with the interface, or use g_object_set_data instead, but + * it's not really going to make it much better) */ + if (GST_IS_AUDIO_SINK_CLASS (klass)) { + list = GST_OSS4_SINK (probe)->property_probe_list; + } else if (GST_IS_AUDIO_SRC_CLASS (klass)) { + list = GST_OSS4_SOURCE (probe)->property_probe_list; + } else if (GST_IS_OSS4_MIXER_CLASS (klass)) { + list = GST_OSS4_MIXER (probe)->property_probe_list; + } else { + GST_OBJECT_UNLOCK (GST_OBJECT (probe)); + g_return_val_if_reached (NULL); + } + + if (list == NULL) { + GParamSpec *pspec; + + pspec = g_object_class_find_property (klass, "device"); + list = g_list_prepend (NULL, pspec); + + if (GST_IS_AUDIO_SINK_CLASS (klass)) { + GST_OSS4_SINK (probe)->property_probe_list = list; + } else if (GST_IS_AUDIO_SRC_CLASS (klass)) { + GST_OSS4_SOURCE (probe)->property_probe_list = list; + } else if (GST_IS_OSS4_MIXER_CLASS (klass)) { + GST_OSS4_MIXER (probe)->property_probe_list = list; + } + } + + GST_OBJECT_UNLOCK (GST_OBJECT (probe)); + + return list; +} + +static void +gst_oss4_property_probe_probe_property (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + if (!g_str_equal (pspec->name, "device")) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + } +} + +static gboolean +gst_oss4_property_probe_needs_probe (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + /* don't cache probed data */ + return TRUE; +} + +static gint +oss4_mixerinfo_priority_cmp (struct oss_mixerinfo *mi1, + struct oss_mixerinfo *mi2) +{ + /* return negative vaue if mi1 comes before mi2 */ + if (mi1->priority != mi2->priority) + return mi2->priority - mi1->priority; + + return strcmp (mi1->devnode, mi2->devnode); +} + +/* caller must ensure LOCK is taken (e.g. if ioctls need to be serialised) */ +gboolean +gst_oss4_property_probe_find_device_name (GstObject * obj, int fd, + const gchar * device_handle, gchar ** device_name) +{ + struct oss_sysinfo si = { {0,}, }; + gchar *name = NULL; + + if (ioctl (fd, SNDCTL_SYSINFO, &si) == 0) { + int i; + + for (i = 0; i < si.numaudios; ++i) { + struct oss_audioinfo ai = { 0, }; + + ai.dev = i; + if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) { + GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i); + continue; + } + if (strcmp (ai.devnode, device_handle) == 0) { + name = g_strdup (ai.name); + break; + } + } + } else { + GST_WARNING_OBJECT (obj, "SYSINFO ioctl failed: %s", g_strerror (errno)); + } + + /* try ENGINEINFO as fallback (which is better than nothing) */ + if (name == NULL) { + struct oss_audioinfo ai = { 0, }; + + GST_LOG_OBJECT (obj, "device %s not listed in AUDIOINFO", device_handle); + ai.dev = -1; + if (ioctl (fd, SNDCTL_ENGINEINFO, &ai) == 0) + name = g_strdup (ai.name); + } + + GST_DEBUG_OBJECT (obj, "Device name: %s", GST_STR_NULL (name)); + + if (name != NULL) + *device_name = name; + + return (name != NULL); +} + +static GList * +gst_oss4_property_probe_get_mixer_devices (GstObject * obj, int fd, + struct oss_sysinfo *si) +{ + GList *m, *mixers = NULL; + GList *devices = NULL; + + int i; + + GST_LOG_OBJECT (obj, "%d mixer devices", si->nummixers); + + /* first, find suitable mixer devices and sort by priority */ + for (i = 0; i < si->nummixers; ++i) { + struct oss_mixerinfo mi = { 0, }; + + mi.dev = i; + if (ioctl (fd, SNDCTL_MIXERINFO, &mi) == -1) { + GST_DEBUG_OBJECT (obj, "MIXERINFO ioctl for device %d failed", i); + continue; + } + + GST_INFO_OBJECT (obj, "mixer device %d:", i); + GST_INFO_OBJECT (obj, " enabled : %s", (mi.enabled) ? "yes" : + "no (powered off or unplugged)"); + GST_INFO_OBJECT (obj, " priority : %d", mi.priority); + GST_INFO_OBJECT (obj, " devnode : %s", mi.devnode); + GST_INFO_OBJECT (obj, " handle : %s", mi.handle); + GST_INFO_OBJECT (obj, " caps : 0x%02x", mi.caps); + GST_INFO_OBJECT (obj, " name : %s", mi.name); + + if (!mi.enabled) { + GST_DEBUG_OBJECT (obj, "mixer device is not usable/enabled, skipping"); + continue; + } + + /* only want mixers that control hardware directly */ + if ((mi.caps & MIXER_CAP_VIRTUAL)) { + GST_DEBUG_OBJECT (obj, "mixer device is a virtual device, skipping"); + continue; + } + + mixers = g_list_insert_sorted (mixers, g_memdup (&mi, sizeof (mi)), + (GCompareFunc) oss4_mixerinfo_priority_cmp); + } + + /* then create device list according to priority */ + for (m = mixers; m != NULL; m = m->next) { + struct oss_mixerinfo *mi = (struct oss_mixerinfo *) m->data; + + GST_LOG_OBJECT (obj, "mixer device: '%s'", mi->devnode); + devices = g_list_prepend (devices, g_strdup (mi->devnode)); + g_free (m->data); + } + g_list_free (mixers); + mixers = NULL; + + return g_list_reverse (devices); +} + +static GList * +gst_oss4_property_probe_get_audio_devices (GstObject * obj, int fd, + struct oss_sysinfo *si, int cap_mask) +{ + GList *devices = NULL; + int i; + + GST_LOG_OBJECT (obj, "%d audio/dsp devices", si->numaudios); + + for (i = 0; i < si->numaudios; ++i) { + struct oss_audioinfo ai = { 0, }; + + ai.dev = i; + if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) { + GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i); + continue; + } + + if ((ai.caps & cap_mask) == 0) { + GST_DEBUG_OBJECT (obj, "audio device %d is not an %s device", i, + (cap_mask == PCM_CAP_OUTPUT) ? "output" : "input"); + continue; + } + + if (!ai.enabled) { + GST_DEBUG_OBJECT (obj, "audio device %d is not usable/enabled", i); + continue; + } + + GST_DEBUG_OBJECT (obj, "audio device %d looks ok: %s (\"%s\")", i, + ai.devnode, ai.name); + + devices = g_list_prepend (devices, g_strdup (ai.devnode)); + } + + return g_list_reverse (devices); +} + +static GValueArray * +gst_oss4_property_probe_get_values (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + struct oss_sysinfo si = { {0,}, }; + GstElementClass *klass; + GValueArray *array = NULL; + GstObject *obj; + GList *devices, *l; + int cap_mask, fd = -1; + + if (!g_str_equal (pspec->name, "device")) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + return NULL; + } + + obj = GST_OBJECT (probe); + + GST_OBJECT_LOCK (obj); + + /* figure out whether the element is a source or sink */ + klass = GST_ELEMENT_GET_CLASS (GST_ELEMENT (probe)); + if (GST_IS_OSS4_SINK (probe)) { + GST_DEBUG_OBJECT (probe, "probing available output devices"); + cap_mask = PCM_CAP_OUTPUT; + fd = GST_OSS4_SINK (probe)->fd; + } else if (GST_IS_OSS4_SOURCE (probe)) { + GST_DEBUG_OBJECT (probe, "probing available input devices"); + cap_mask = PCM_CAP_INPUT; + fd = GST_OSS4_SOURCE (probe)->fd; + } else if (GST_IS_OSS4_MIXER (probe)) { + fd = GST_OSS4_MIXER (probe)->fd; + cap_mask = 0; + } else { + GST_OBJECT_UNLOCK (obj); + g_return_val_if_reached (NULL); + } + + /* copy fd if it's open, so we can just unconditionally close() later */ + if (fd != -1) + fd = dup (fd); + + /* this will also catch the unlikely case where the above dup() failed */ + if (fd == -1) { + fd = open ("/dev/mixer", O_RDONLY | O_NONBLOCK, 0); + if (fd < 0) + goto open_failed; + else if (!gst_oss4_audio_check_version (GST_OBJECT (probe), fd)) + goto legacy_oss; + } + + if (ioctl (fd, SNDCTL_SYSINFO, &si) == -1) + goto no_sysinfo; + + if (cap_mask != 0) { + devices = + gst_oss4_property_probe_get_audio_devices (obj, fd, &si, cap_mask); + } else { + devices = gst_oss4_property_probe_get_mixer_devices (obj, fd, &si); + } + + if (devices == NULL) { + GST_DEBUG_OBJECT (obj, "No devices found"); + goto done; + } + + array = g_value_array_new (1); + + for (l = devices; l != NULL; l = l->next) { + GValue val = { 0, }; + + g_value_init (&val, G_TYPE_STRING); + g_value_take_string (&val, (gchar *) l->data); + l->data = NULL; + g_value_array_append (array, &val); + g_value_unset (&val); + } + + GST_OBJECT_UNLOCK (obj); + + g_list_free (devices); + +done: + + close (fd); + + return array; + +/* ERRORS */ +open_failed: + { + GST_OBJECT_UNLOCK (GST_OBJECT (probe)); + GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe " + "available devices: %s", g_strerror (errno)); + return NULL; + } +legacy_oss: + { + close (fd); + GST_OBJECT_UNLOCK (GST_OBJECT (probe)); + GST_DEBUG_OBJECT (probe, "Legacy OSS (ie. not OSSv4), not supported"); + return NULL; + } +no_sysinfo: + { + close (fd); + GST_OBJECT_UNLOCK (GST_OBJECT (probe)); + GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe " + "available devices: %s", g_strerror (errno)); + return NULL; + } +} + +static void +gst_oss4_property_probe_interface_init (GstPropertyProbeInterface * iface) +{ + iface->get_properties = gst_oss4_property_probe_get_properties; + iface->probe_property = gst_oss4_property_probe_probe_property; + iface->needs_probe = gst_oss4_property_probe_needs_probe; + iface->get_values = gst_oss4_property_probe_get_values; +} + +void +gst_oss4_add_property_probe_interface (GType type) +{ + static const GInterfaceInfo probe_iface_info = { + (GInterfaceInitFunc) gst_oss4_property_probe_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE, + &probe_iface_info); +} diff --git a/sys/oss4/oss4-property-probe.h b/sys/oss4/oss4-property-probe.h new file mode 100644 index 0000000000..26ee8b7992 --- /dev/null +++ b/sys/oss4/oss4-property-probe.h @@ -0,0 +1,34 @@ +/* GStreamer OSS4 audio property probe interface implementation + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_OSS4_PROPERTY_PROBE_H +#define GST_OSS4_PROPERTY_PROBE_H + +#include + +void gst_oss4_add_property_probe_interface (GType type); + +gboolean gst_oss4_property_probe_find_device_name (GstObject * obj, + int fd, + const gchar * device_handle, + gchar ** device_name); + +#endif /* GST_OSS4_PROPERTY_PROBE_H */ + + diff --git a/sys/oss4/oss4-sink.c b/sys/oss4/oss4-sink.c new file mode 100644 index 0000000000..1cff22a512 --- /dev/null +++ b/sys/oss4/oss4-sink.c @@ -0,0 +1,571 @@ +/* GStreamer OSS4 audio sink + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:element-oss4sink + * @short_description: output sound using OSS4 + * + * + * + * This element lets you output sound using the Open Sound System (OSS) + * version 4. + * + * + * Note that you should almost always use generic audio conversion elements + * like audioconvert and audioresample in front of an audiosink to make sure + * your pipeline works under all circumstances (those conversion elements will + * act in passthrough-mode if no conversion is necessary). + * + * Example pipelines + * + * + * gst-launch -v audiotestsrc ! audioconvert ! volume volume=0.1 ! oss4sink + * + * will output a sine wave (continuous beep sound) to your sound card (with + * a very low volume as precaution). + * + * + * + * gst-launch -v filesrc location=music.ogg ! decodebin ! audioconvert ! audioresample ! oss4sink + * + * will play an Ogg/Vorbis audio file and output it using the Open Sound System + * version 4. + * + * + * + * Since: 0.10.7 + */ + +/* TODO: - add "volume" property for stream volume control and intercept tags + * to set stream title + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NO_LEGACY_MIXER +#include "oss4-audio.h" +#include "oss4-sink.h" +#include "oss4-property-probe.h" +#include "oss4-soundcard.h" + +GST_DEBUG_CATEGORY_EXTERN (oss4sink_debug); +#define GST_CAT_DEFAULT oss4sink_debug + +static void gst_oss4_sink_init_interfaces (GType type); +static void gst_oss4_sink_dispose (GObject * object); +static void gst_oss4_sink_finalise (GObject * object); + +static void gst_oss4_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_oss4_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +static GstCaps *gst_oss4_sink_getcaps (GstBaseSink * bsink); +static gboolean gst_oss4_sink_open (GstAudioSink * asink, + gboolean silent_errors); +static gboolean gst_oss4_sink_open_func (GstAudioSink * asink); +static gboolean gst_oss4_sink_close (GstAudioSink * asink); +static gboolean gst_oss4_sink_prepare (GstAudioSink * asink, + GstRingBufferSpec * spec); +static gboolean gst_oss4_sink_unprepare (GstAudioSink * asink); +static guint gst_oss4_sink_write (GstAudioSink * asink, gpointer data, + guint length); +static guint gst_oss4_sink_delay (GstAudioSink * asink); +static void gst_oss4_sink_reset (GstAudioSink * asink); + +#define DEFAULT_DEVICE NULL +#define DEFAULT_DEVICE_NAME NULL + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DEVICE_NAME +}; + +GST_BOILERPLATE_FULL (GstOss4Sink, gst_oss4_sink, GstAudioSink, + GST_TYPE_AUDIO_SINK, gst_oss4_sink_init_interfaces); + +static void +gst_oss4_sink_dispose (GObject * object) +{ + GstOss4Sink *osssink = GST_OSS4_SINK (object); + + if (osssink->probed_caps) { + gst_caps_unref (osssink->probed_caps); + osssink->probed_caps = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_oss4_sink_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GstPadTemplate *templ; + + gst_element_class_set_details_simple (element_class, + "OSS v4 Audio Sink", "Sink/Audio", + "Output to a sound card via OSS version 4", + "Tim-Philipp Müller "); + + templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + gst_oss4_audio_get_template_caps ()); + gst_element_class_add_pad_template (element_class, templ); +} + +static void +gst_oss4_sink_class_init (GstOss4SinkClass * klass) +{ + GstAudioSinkClass *audiosink_class = (GstAudioSinkClass *) klass; + GstBaseSinkClass *basesink_class = (GstBaseSinkClass *) klass; + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_oss4_sink_dispose); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_oss4_sink_finalise); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_oss4_sink_get_property); + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_oss4_sink_set_property); + + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Device", + "OSS4 device (e.g. /dev/oss/hdaudio0/pcm0 or /dev/dspN) " + "(NULL = use first available playback device)", + DEFAULT_DEVICE, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Device name", + "Human-readable name of the sound device", DEFAULT_DEVICE_NAME, + G_PARAM_READABLE)); + + basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_oss4_sink_getcaps); + + audiosink_class->open = GST_DEBUG_FUNCPTR (gst_oss4_sink_open_func); + audiosink_class->close = GST_DEBUG_FUNCPTR (gst_oss4_sink_close); + audiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_oss4_sink_prepare); + audiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_oss4_sink_unprepare); + audiosink_class->write = GST_DEBUG_FUNCPTR (gst_oss4_sink_write); + audiosink_class->delay = GST_DEBUG_FUNCPTR (gst_oss4_sink_delay); + audiosink_class->reset = GST_DEBUG_FUNCPTR (gst_oss4_sink_reset); +} + +static void +gst_oss4_sink_init (GstOss4Sink * osssink, GstOss4SinkClass * klass) +{ + const gchar *device; + + device = g_getenv ("AUDIODEV"); + if (device == NULL) + device = DEFAULT_DEVICE; + osssink->device = g_strdup (device); + osssink->fd = -1; + osssink->bytes_per_sample = 0; + osssink->probed_caps = NULL; + osssink->device_name = NULL; +} + +static void +gst_oss4_sink_finalise (GObject * object) +{ + GstOss4Sink *osssink = GST_OSS4_SINK (object); + + g_free (osssink->device); + osssink->device = NULL; + + g_list_free (osssink->property_probe_list); + osssink->property_probe_list = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_oss4_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstOss4Sink *oss = GST_OSS4_SINK (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (oss); + if (oss->fd == -1) { + g_free (oss->device); + oss->device = g_value_dup_string (value); + if (oss->probed_caps) { + gst_caps_unref (oss->probed_caps); + oss->probed_caps = NULL; + } + g_free (oss->device_name); + oss->device_name = NULL; + } else { + g_warning ("%s: can't change \"device\" property while audio sink " + "is open", GST_OBJECT_NAME (oss)); + } + GST_OBJECT_UNLOCK (oss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_oss4_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstOss4Sink *oss = GST_OSS4_SINK (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (oss); + g_value_set_string (value, oss->device); + GST_OBJECT_UNLOCK (oss); + break; + case PROP_DEVICE_NAME: + GST_OBJECT_LOCK (oss); + if (oss->fd == -1 && oss->device != NULL) { + /* If device is set, try to retrieve the name even if we're not open */ + if (gst_oss4_sink_open (GST_AUDIO_SINK (oss), TRUE)) { + g_value_set_string (value, oss->device_name); + gst_oss4_sink_close (GST_AUDIO_SINK (oss)); + } else { + g_value_set_string (value, oss->device_name); + } + } else { + g_value_set_string (value, oss->device_name); + } + GST_OBJECT_UNLOCK (oss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstCaps * +gst_oss4_sink_getcaps (GstBaseSink * bsink) +{ + GstOss4Sink *oss; + GstCaps *caps; + + oss = GST_OSS4_SINK (bsink); + + if (oss->fd == -1) { + caps = gst_caps_copy (gst_oss4_audio_get_template_caps ()); + } else if (oss->probed_caps) { + caps = gst_caps_copy (oss->probed_caps); + } else { + caps = gst_oss4_audio_probe_caps (GST_OBJECT (oss), oss->fd); + if (caps != NULL && !gst_caps_is_empty (caps)) { + oss->probed_caps = gst_caps_copy (caps); + } + } + + return caps; +} + +/* note: we must not take the object lock here unless we fix up get_property */ +static gboolean +gst_oss4_sink_open (GstAudioSink * asink, gboolean silent_errors) +{ + GstOss4Sink *oss; + gchar *device; + int mode; + + oss = GST_OSS4_SINK (asink); + + if (oss->device) + device = g_strdup (oss->device); + else + device = gst_oss4_audio_find_device (GST_OBJECT_CAST (oss)); + + /* desperate times, desperate measures */ + if (device == NULL) + device = g_strdup ("/dev/dsp0"); + + GST_INFO_OBJECT (oss, "Trying to open OSS4 device '%s'", device); + + /* we open in non-blocking mode even if we don't really want to do writes + * non-blocking because we can't be sure that this is really a genuine + * OSS4 device with well-behaved drivers etc. We really don't want to + * hang forever under any circumstances. */ + oss->fd = open (device, O_WRONLY | O_NONBLOCK, 0); + if (oss->fd == -1) { + switch (errno) { + case EBUSY: + goto busy; + case EACCES: + goto no_permission; + default: + goto open_failed; + } + } + + GST_INFO_OBJECT (oss, "Opened device '%s'", device); + + /* Make sure it's OSS4. If it's old OSS, let osssink handle it */ + if (!gst_oss4_audio_check_version (GST_OBJECT_CAST (oss), oss->fd)) + goto legacy_oss; + + /* now remove the non-blocking flag. */ + mode = fcntl (oss->fd, F_GETFL); + mode &= ~O_NONBLOCK; + if (fcntl (oss->fd, F_SETFL, mode) < 0) { + /* some drivers do no support unsetting the non-blocking flag, try to + * close/open the device then. This is racy but we error out properly. */ + GST_WARNING_OBJECT (oss, "failed to unset O_NONBLOCK (buggy driver?), " + "will try to re-open device now"); + gst_oss4_sink_close (asink); + if ((oss->fd = open (device, O_WRONLY, 0)) == -1) + goto non_block; + } + + oss->open_device = device; + + /* not using ENGINEINFO here because it sometimes returns a different and + * less useful name than AUDIOINFO for the same device */ + if (!gst_oss4_property_probe_find_device_name (GST_OBJECT (oss), oss->fd, + oss->open_device, &oss->device_name)) { + oss->device_name = NULL; + } + + /* list output routings, for informational purposes only so far */ + { + oss_mixer_enuminfo routings = { 0, }; + guint i; + + if (ioctl (oss->fd, SNDCTL_DSP_GET_PLAYTGT_NAMES, &routings) != -1) { + GST_LOG_OBJECT (oss, "%u output routings (static list: %d)", + routings.nvalues, !!(routings.version == 0)); + for (i = 0; i < routings.nvalues; ++i) { + GST_LOG_OBJECT (oss, " output routing %d: %s", i, + &routings.strings[routings.strindex[i]]); + } + } + } + + return TRUE; + + /* ERRORS */ +busy: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, BUSY, + (_("Could not open audio device for playback. " + "Device is being used by another application.")), (NULL)); + } + g_free (device); + return FALSE; + } +no_permission: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_WRITE, + (_("Could not open audio device for playback." + "You don't have permission to open the device.")), + GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +open_failed: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_WRITE, + (_("Could not open audio device for playback.")), GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +legacy_oss: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_WRITE, + (_("Could not open audio device for playback." + "This version of the Open Sound System is not supported by this " + "element.")), ("Try the 'osssink' element instead")); + } + gst_oss4_sink_close (asink); + g_free (device); + return FALSE; + } +non_block: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, SETTINGS, (NULL), + ("Unable to set device %s into non-blocking mode: %s", + oss->device, g_strerror (errno))); + } + g_free (device); + return FALSE; + } +} + +static gboolean +gst_oss4_sink_open_func (GstAudioSink * asink) +{ + return gst_oss4_sink_open (asink, FALSE); +} + +static gboolean +gst_oss4_sink_close (GstAudioSink * asink) +{ + GstOss4Sink *oss = GST_OSS4_SINK (asink); + + if (oss->fd != -1) { + GST_DEBUG_OBJECT (oss, "closing device"); + close (oss->fd); + oss->fd = -1; + } + + oss->bytes_per_sample = 0; + /* we keep the probed caps cached, at least until the device changes */ + + g_free (oss->open_device); + oss->open_device = NULL; + + g_free (oss->device_name); + oss->device_name = NULL; + + if (oss->probed_caps) { + gst_caps_unref (oss->probed_caps); + oss->probed_caps = NULL; + } + + return TRUE; +} + +static gboolean +gst_oss4_sink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) +{ + GstOss4Sink *oss; + + oss = GST_OSS4_SINK (asink); + + if (!gst_oss4_audio_set_format (GST_OBJECT_CAST (oss), oss->fd, spec)) { + GST_WARNING_OBJECT (oss, "Couldn't set requested format %" GST_PTR_FORMAT, + spec->caps); + return FALSE; + } + + oss->bytes_per_sample = spec->bytes_per_sample; + return TRUE; +} + +static gboolean +gst_oss4_sink_unprepare (GstAudioSink * asink) +{ + /* could do a SNDCTL_DSP_HALT, but the OSS manual recommends a close/open, + * since HALT won't properly reset some devices, apparently */ + + if (!gst_oss4_sink_close (asink)) + goto couldnt_close; + + if (!gst_oss4_sink_open_func (asink)) + goto couldnt_reopen; + + return TRUE; + + /* ERRORS */ +couldnt_close: + { + GST_DEBUG_OBJECT (asink, "Couldn't close the audio device"); + return FALSE; + } +couldnt_reopen: + { + GST_DEBUG_OBJECT (asink, "Couldn't reopen the audio device"); + return FALSE; + } +} + +static guint +gst_oss4_sink_write (GstAudioSink * asink, gpointer data, guint length) +{ + GstOss4Sink *oss; + int n; + + oss = GST_OSS4_SINK_CAST (asink); + + n = write (oss->fd, data, length); + GST_LOG_OBJECT (asink, "wrote %d/%d samples, %d bytes", + n / oss->bytes_per_sample, length / oss->bytes_per_sample, n); + + if (G_UNLIKELY (n < 0)) { + switch (errno) { + case ENOTSUP: + case EACCES:{ + /* This is the most likely cause, I think */ + GST_ELEMENT_ERROR (asink, RESOURCE, WRITE, + (_("Playback is not supported by this audio device.")), + ("write: %s (device: %s) (maybe this is an input-only device?)", + g_strerror (errno), oss->open_device)); + break; + } + default:{ + GST_ELEMENT_ERROR (asink, RESOURCE, WRITE, + (_("Audio playback error.")), + ("write: %s (device: %s)", g_strerror (errno), oss->open_device)); + break; + } + } + } + + return n; +} + +static guint +gst_oss4_sink_delay (GstAudioSink * asink) +{ + GstOss4Sink *oss; + gint delay = -1; + + oss = GST_OSS4_SINK_CAST (asink); + + if (ioctl (oss->fd, SNDCTL_DSP_GETODELAY, &delay) < 0 || delay < 0) { + GST_LOG_OBJECT (oss, "GETODELAY failed"); + return 0; + } + + return delay / oss->bytes_per_sample; +} + +static void +gst_oss4_sink_reset (GstAudioSink * asink) +{ + /* There's nothing we can do here really: OSS can't handle access to the + * same device/fd from multiple threads and might deadlock or blow up in + * other ways if we try an ioctl SNDCTL_DSP_HALT or similar */ +} + +static void +gst_oss4_sink_init_interfaces (GType type) +{ + gst_oss4_add_property_probe_interface (type); +} diff --git a/sys/oss4/oss4-sink.h b/sys/oss4/oss4-sink.h new file mode 100644 index 0000000000..0120598990 --- /dev/null +++ b/sys/oss4/oss4-sink.h @@ -0,0 +1,63 @@ +/* GStreamer OSS4 audio sink + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_OSS4_SINK_H +#define GST_OSS4_SINK_H + + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_OSS4_SINK (gst_oss4_sink_get_type()) +#define GST_OSS4_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSS4_SINK,GstOss4Sink)) +#define GST_OSS4_SINK_CAST(obj) ((GstOss4Sink *)(obj)) +#define GST_OSS4_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSS4_SINK,GstOss4SinkClass)) +#define GST_IS_OSS4_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSS4_SINK)) +#define GST_IS_OSS4_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSS4_SINK)) + +typedef struct _GstOss4Sink GstOss4Sink; +typedef struct _GstOss4SinkClass GstOss4SinkClass; + +struct _GstOss4Sink { + GstAudioSink audio_sink; + + gchar * device; /* NULL if none was set */ + gchar * open_device; /* the device we opened */ + gchar * device_name; /* set if the device is open */ + gint fd; /* -1 if not open */ + gint bytes_per_sample; + + GstCaps * probed_caps; + + GList * property_probe_list; +}; + +struct _GstOss4SinkClass { + GstAudioSinkClass audio_sink_class; +}; + +GType gst_oss4_sink_get_type (void); + +G_END_DECLS + +#endif /* GST_OSS4_SINK_H */ + + diff --git a/sys/oss4/oss4-soundcard.h b/sys/oss4/oss4-soundcard.h new file mode 100644 index 0000000000..8b124892b7 --- /dev/null +++ b/sys/oss4/oss4-soundcard.h @@ -0,0 +1,2067 @@ +/* + * Purpose: The C/C++ header file that defines the OSS API. + * Description: + * This header file contains all the declarations required to compile OSS + * programs. The latest version is always installed together with OSS + * use of the latest version is strongly recommended. + * + * {!notice This header file contains many obsolete definitions + * (for compatibility with older applications that still ned them). + * Do not use this file as a reference manual of OSS. + * Please check the OSS Programmer's guide for descriptions + * of the supported API details (http://manuals.opensound.com/developer).} + */ + +#ifndef SOUNDCARD_H +#define SOUNDCARD_H + +#define COPYING40 Copyright (C) 4Front Technologies 2000-2006. Released under the BSD license. + +#if defined(__cplusplus) +#define EXTERNC extern "C" +#else +#define EXTERNC extern +#endif /* EXTERN_C_WRAPPERS */ + +#define OSS_VERSION 0x040090 // Pre 4.1 + +#define SOUND_VERSION OSS_VERSION +#define OPEN_SOUND_SYSTEM + +#if defined(__hpux) && !defined(_HPUX_SOURCE) +# error "-D_HPUX_SOURCE must be used when compiling OSS applications" +#endif + +#ifdef __hpux +#include +#endif + +#ifdef linux +/* In Linux we need to be prepared for cross compiling */ +#include +#else +# ifdef __FreeBSD__ +# include +# else +# include +# endif +#endif + +#ifndef __SIOWR +#if defined(__hpux) || (defined(_IOWR) && (defined(_AIX) || (!defined(sun) && !defined(sparc) && !defined(__INCioctlh) && !defined(__Lynx__)))) + +/* + * Make sure the ioctl macros are compatible with the ones already used + * by this operating system. + */ +#define SIOCPARM_MASK IOCPARM_MASK +#define SIOC_VOID IOC_VOID +#define SIOC_OUT IOC_OUT +#define SIOC_IN IOC_IN +#define SIOC_INOUT IOC_INOUT +#define __SIOC_SIZE _IOC_SIZE +#define __SIOC_DIR _IOC_DIR +#define __SIOC_NONE _IOC_NONE +#define __SIOC_READ _IOC_READ +#define __SIOC_WRITE _IOC_WRITE +#define __SIO _IO +#define __SIOR _IOR +#define __SIOW _IOW +#define __SIOWR _IOWR +#else + +/* #define SIOCTYPE (0xff<<8) */ +#define SIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */ +#define SIOC_VOID 0x00000000 /* no parameters */ +#define SIOC_OUT 0x20000000 /* copy out parameters */ +#define SIOC_IN 0x40000000 /* copy in parameters */ +#define SIOC_INOUT (SIOC_IN|SIOC_OUT) + +#define __SIO(x,y) ((int)(SIOC_VOID|(x<<8)|y)) +#define __SIOR(x,y,t) ((int)(SIOC_OUT|((sizeof(t)&SIOCPARM_MASK)<<16)|(x<<8)|y)) +#define __SIOW(x,y,t) ((int)(SIOC_IN|((sizeof(t)&SIOCPARM_MASK)<<16)|(x<<8)|y)) +#define __SIOWR(x,y,t) ((int)(SIOC_INOUT|((sizeof(t)&SIOCPARM_MASK)<<16)|(x<<8)|y)) +#define __SIOC_SIZE(x) ((x>>16)&SIOCPARM_MASK) +#define __SIOC_DIR(x) (x & 0xf0000000) +#define __SIOC_NONE SIOC_VOID +#define __SIOC_READ SIOC_OUT +#define __SIOC_WRITE SIOC_IN +# endif /* _IOWR */ +#endif /* !__SIOWR */ + +#define OSS_LONGNAME_SIZE 64 +#define OSS_LABEL_SIZE 16 +#define OSS_DEVNODE_SIZE 32 +typedef char oss_longname_t[OSS_LONGNAME_SIZE]; +typedef char oss_label_t[OSS_LABEL_SIZE]; +typedef char oss_devnode_t[OSS_DEVNODE_SIZE]; + +#ifndef DISABLE_SEQUENCER +/* + **************************************************************************** + * IOCTL Commands for /dev/sequencer and /dev/music (AKA /dev/sequencer2) + * + * Note that this interface is obsolete and no longer developed. New + * applications should use /dev/midi instead. + ****************************************************************************/ +#define SNDCTL_SEQ_RESET __SIO ('Q', 0) +#define SNDCTL_SEQ_SYNC __SIO ('Q', 1) +#define SNDCTL_SYNTH_INFO __SIOWR('Q', 2, struct synth_info) +#define SNDCTL_SEQ_CTRLRATE __SIOWR('Q', 3, int) /* Set/get timer resolution (HZ) */ +#define SNDCTL_SEQ_GETOUTCOUNT __SIOR ('Q', 4, int) +#define SNDCTL_SEQ_GETINCOUNT __SIOR ('Q', 5, int) +#define SNDCTL_SEQ_PERCMODE __SIOW ('Q', 6, int) +#define SNDCTL_FM_LOAD_INSTR __SIOW ('Q', 7, struct sbi_instrument) /* Obsolete. Don't use!!!!!! */ +#define SNDCTL_SEQ_TESTMIDI __SIOW ('Q', 8, int) +#define SNDCTL_SEQ_RESETSAMPLES __SIOW ('Q', 9, int) +#define SNDCTL_SEQ_NRSYNTHS __SIOR ('Q',10, int) +#define SNDCTL_SEQ_NRMIDIS __SIOR ('Q',11, int) +#define SNDCTL_MIDI_INFO __SIOWR('Q',12, struct midi_info) /* OBSOLETE - use SNDCTL_MIDIINFO instead */ +#define SNDCTL_SEQ_THRESHOLD __SIOW ('Q',13, int) +#define SNDCTL_SYNTH_MEMAVL __SIOWR('Q',14, int) /* in=dev#, out=memsize */ +#define SNDCTL_FM_4OP_ENABLE __SIOW ('Q',15, int) /* in=dev# */ +#define SNDCTL_SEQ_PANIC __SIO ('Q',17) +#define SNDCTL_SEQ_OUTOFBAND __SIOW ('Q',18, struct seq_event_rec) +#define SNDCTL_SEQ_GETTIME __SIOR ('Q',19, int) +#define SNDCTL_SYNTH_ID __SIOWR('Q',20, struct synth_info) +#define SNDCTL_SYNTH_CONTROL __SIOWR('Q',21, struct synth_control) +#define SNDCTL_SYNTH_REMOVESAMPLE __SIOWR('Q',22, struct remove_sample) /* Reserved for future use */ +#define SNDCTL_SEQ_TIMING_ENABLE __SIO ('Q', 23) /* Enable incoming MIDI timing messages */ +#define SNDCTL_SEQ_ACTSENSE_ENABLE __SIO ('Q', 24) /* Enable incoming active sensing messages */ +#define SNDCTL_SEQ_RT_ENABLE __SIO ('Q', 25) /* Enable other incoming realtime messages */ + +typedef struct synth_control +{ + int devno; /* Synthesizer # */ + char data[4000]; /* Device spesific command/data record */ +} synth_control; + +typedef struct remove_sample +{ + int devno; /* Synthesizer # */ + int bankno; /* MIDI bank # (0=General MIDI) */ + int instrno; /* MIDI instrument number */ +} remove_sample; + +typedef struct seq_event_rec +{ + unsigned char arr[8]; +} seq_event_rec; + +#define SNDCTL_TMR_TIMEBASE __SIOWR('T', 1, int) +#define SNDCTL_TMR_START __SIO ('T', 2) +#define SNDCTL_TMR_STOP __SIO ('T', 3) +#define SNDCTL_TMR_CONTINUE __SIO ('T', 4) +#define SNDCTL_TMR_TEMPO __SIOWR('T', 5, int) +#define SNDCTL_TMR_SOURCE __SIOWR('T', 6, int) +# define TMR_INTERNAL 0x00000001 +# define TMR_EXTERNAL 0x00000002 +# define TMR_MODE_MIDI 0x00000010 +# define TMR_MODE_FSK 0x00000020 +# define TMR_MODE_CLS 0x00000040 +# define TMR_MODE_SMPTE 0x00000080 +#define SNDCTL_TMR_METRONOME __SIOW ('T', 7, int) +#define SNDCTL_TMR_SELECT __SIOW ('T', 8, int) + +/* + * Sample loading mechanism for internal synthesizers (/dev/sequencer) + * (for the .PAT format). + */ + +struct patch_info +{ + unsigned short key; /* Use WAVE_PATCH here */ +#define WAVE_PATCH _PATCHKEY(0x04) +#define GUS_PATCH WAVE_PATCH +#define WAVEFRONT_PATCH _PATCHKEY(0x06) + + short device_no; /* Synthesizer number */ + short instr_no; /* Midi pgm# */ + + unsigned int mode; +/* + * The least significant byte has the same format than the GUS .PAT + * files + */ +#define WAVE_16_BITS 0x01 /* bit 0 = 8 or 16 bit wave data. */ +#define WAVE_UNSIGNED 0x02 /* bit 1 = Signed - Unsigned data. */ +#define WAVE_LOOPING 0x04 /* bit 2 = looping enabled-1. */ +#define WAVE_BIDIR_LOOP 0x08 /* bit 3 = Set is bidirectional looping. */ +#define WAVE_LOOP_BACK 0x10 /* bit 4 = Set is looping backward. */ +#define WAVE_SUSTAIN_ON 0x20 /* bit 5 = Turn sustaining on. (Env. pts. 3) */ +#define WAVE_ENVELOPES 0x40 /* bit 6 = Enable envelopes - 1 */ +#define WAVE_FAST_RELEASE 0x80 /* bit 7 = Shut off immediately after note off */ + /* (use the env_rate/env_offs fields). */ +/* Linux specific bits */ +#define WAVE_VIBRATO 0x00010000 /* The vibrato info is valid */ +#define WAVE_TREMOLO 0x00020000 /* The tremolo info is valid */ +#define WAVE_SCALE 0x00040000 /* The scaling info is valid */ +#define WAVE_FRACTIONS 0x00080000 /* Fraction information is valid */ +/* Reserved bits */ +#define WAVE_ROM 0x40000000 /* For future use */ +#define WAVE_MULAW 0x20000000 /* For future use */ +/* Other bits must be zeroed */ + + int len; /* Size of the wave data in bytes */ + int loop_start, loop_end; /* Byte offsets from the beginning */ + +/* + * The base_freq and base_note fields are used when computing the + * playback speed for a note. The base_note defines the tone frequency + * which is heard if the sample is played using the base_freq as the + * playback speed. + * + * The low_note and high_note fields define the minimum and maximum note + * frequencies for which this sample is valid. It is possible to define + * more than one samples for an instrument number at the same time. The + * low_note and high_note fields are used to select the most suitable one. + * + * The fields base_note, high_note and low_note should contain + * the note frequency multiplied by 1000. For example value for the + * middle A is 440*1000. + */ + + unsigned int base_freq; + unsigned int base_note; + unsigned int high_note; + unsigned int low_note; + int panning; /* -128=left, 127=right */ + int detuning; + + /* Envelope. Enabled by mode bit WAVE_ENVELOPES */ + unsigned char env_rate[6]; /* GUS HW ramping rate */ + unsigned char env_offset[6]; /* 255 == 100% */ + + /* + * The tremolo, vibrato and scale info are not supported yet. + * Enable by setting the mode bits WAVE_TREMOLO, WAVE_VIBRATO or + * WAVE_SCALE + */ + + unsigned char tremolo_sweep; + unsigned char tremolo_rate; + unsigned char tremolo_depth; + + unsigned char vibrato_sweep; + unsigned char vibrato_rate; + unsigned char vibrato_depth; + + int scale_frequency; + unsigned int scale_factor; /* from 0 to 2048 or 0 to 2 */ + + int volume; + int fractions; + int reserved1; + int spare[2]; + char data[1]; /* The waveform data starts here */ +}; + +struct sysex_info +{ + short key; /* Use SYSEX_PATCH or MAUI_PATCH here */ +#define SYSEX_PATCH _PATCHKEY(0x05) +#define MAUI_PATCH _PATCHKEY(0x06) + short device_no; /* Synthesizer number */ + int len; /* Size of the sysex data in bytes */ + unsigned char data[1]; /* Sysex data starts here */ +}; + +/* + * /dev/sequencer input events. + * + * The data written to the /dev/sequencer is a stream of events. Events + * are records of 4 or 8 bytes. The first byte defines the size. + * Any number of events can be written with a write call. There + * is a set of macros for sending these events. Use these macros if you + * want to maximize portability of your program. + * + * Events SEQ_WAIT, SEQ_MIDIPUTC and SEQ_ECHO. Are also input events. + * (All input events are currently 4 bytes long. Be prepared to support + * 8 byte events also. If you receive any event having first byte >= 128, + * it's a 8 byte event. + * + * The events are documented at the end of this file. + * + * Normal events (4 bytes) + * There is also a 8 byte version of most of the 4 byte events. The + * 8 byte one is recommended. + * + * NOTE! All 4 byte events are now obsolete. Applications should not write + * them. However 4 byte events are still used as inputs from + * /dev/sequencer (/dev/music uses only 8 byte ones). + */ +#define SEQ_NOTEOFF 0 +#define SEQ_FMNOTEOFF SEQ_NOTEOFF /* Just old name */ +#define SEQ_NOTEON 1 +#define SEQ_FMNOTEON SEQ_NOTEON +#define SEQ_WAIT TMR_WAIT_ABS +#define SEQ_PGMCHANGE 3 +#define SEQ_FMPGMCHANGE SEQ_PGMCHANGE +#define SEQ_SYNCTIMER TMR_START +#define SEQ_MIDIPUTC 5 +#define SEQ_DRUMON 6 /*** OBSOLETE ***/ +#define SEQ_DRUMOFF 7 /*** OBSOLETE ***/ +#define SEQ_ECHO TMR_ECHO /* For synching programs with output */ +#define SEQ_AFTERTOUCH 9 +#define SEQ_CONTROLLER 10 +#define SEQ_BALANCE 11 +#define SEQ_VOLMODE 12 + +/************************************ + * Midi controller numbers * + ************************************/ +/* + * Controllers 0 to 31 (0x00 to 0x1f) and + * 32 to 63 (0x20 to 0x3f) are continuous + * controllers. + * In the MIDI 1.0 these controllers are sent using + * two messages. Controller numbers 0 to 31 are used + * to send the MSB and the controller numbers 32 to 63 + * are for the LSB. Note that just 7 bits are used in MIDI bytes. + */ + +#define CTL_BANK_SELECT 0x00 +#define CTL_MODWHEEL 0x01 +#define CTL_BREATH 0x02 +/* undefined 0x03 */ +#define CTL_FOOT 0x04 +#define CTL_PORTAMENTO_TIME 0x05 +#define CTL_DATA_ENTRY 0x06 +#define CTL_MAIN_VOLUME 0x07 +#define CTL_BALANCE 0x08 +/* undefined 0x09 */ +#define CTL_PAN 0x0a +#define CTL_EXPRESSION 0x0b +/* undefined 0x0c */ +/* undefined 0x0d */ +/* undefined 0x0e */ +/* undefined 0x0f */ +#define CTL_GENERAL_PURPOSE1 0x10 +#define CTL_GENERAL_PURPOSE2 0x11 +#define CTL_GENERAL_PURPOSE3 0x12 +#define CTL_GENERAL_PURPOSE4 0x13 +/* undefined 0x14 - 0x1f */ + +/* undefined 0x20 */ +/* The controller numbers 0x21 to 0x3f are reserved for the */ +/* least significant bytes of the controllers 0x00 to 0x1f. */ +/* These controllers are not recognised by the driver. */ + +/* Controllers 64 to 69 (0x40 to 0x45) are on/off switches. */ +/* 0=OFF and 127=ON (intermediate values are possible) */ +#define CTL_DAMPER_PEDAL 0x40 +#define CTL_SUSTAIN 0x40 /* Alias */ +#define CTL_HOLD 0x40 /* Alias */ +#define CTL_PORTAMENTO 0x41 +#define CTL_SOSTENUTO 0x42 +#define CTL_SOFT_PEDAL 0x43 +/* undefined 0x44 */ +#define CTL_HOLD2 0x45 +/* undefined 0x46 - 0x4f */ + +#define CTL_GENERAL_PURPOSE5 0x50 +#define CTL_GENERAL_PURPOSE6 0x51 +#define CTL_GENERAL_PURPOSE7 0x52 +#define CTL_GENERAL_PURPOSE8 0x53 +/* undefined 0x54 - 0x5a */ +#define CTL_EXT_EFF_DEPTH 0x5b +#define CTL_TREMOLO_DEPTH 0x5c +#define CTL_CHORUS_DEPTH 0x5d +#define CTL_DETUNE_DEPTH 0x5e +#define CTL_CELESTE_DEPTH 0x5e /* Alias for the above one */ +#define CTL_PHASER_DEPTH 0x5f +#define CTL_DATA_INCREMENT 0x60 +#define CTL_DATA_DECREMENT 0x61 +#define CTL_NONREG_PARM_NUM_LSB 0x62 +#define CTL_NONREG_PARM_NUM_MSB 0x63 +#define CTL_REGIST_PARM_NUM_LSB 0x64 +#define CTL_REGIST_PARM_NUM_MSB 0x65 +/* undefined 0x66 - 0x78 */ +/* reserved 0x79 - 0x7f */ + +/* Pseudo controllers (not midi compatible) */ +#define CTRL_PITCH_BENDER 255 +#define CTRL_PITCH_BENDER_RANGE 254 +#define CTRL_EXPRESSION 253 /* Obsolete */ +#define CTRL_MAIN_VOLUME 252 /* Obsolete */ + +/* + * Volume mode defines how volumes are used + */ + +#define VOL_METHOD_ADAGIO 1 +#define VOL_METHOD_LINEAR 2 + +/* + * Note! SEQ_WAIT, SEQ_MIDIPUTC and SEQ_ECHO are used also as + * input events. + */ + +/* + * Event codes 0xf0 to 0xfc are reserved for future extensions. + */ + +#define SEQ_FULLSIZE 0xfd /* Long events */ +/* + * SEQ_FULLSIZE events are used for loading patches/samples to the + * synthesizer devices. These events are passed directly to the driver + * of the associated synthesizer device. There is no limit to the size + * of the extended events. These events are not queued but executed + * immediately when the write() is called (execution can take several + * seconds of time). + * + * When a SEQ_FULLSIZE message is written to the device, it must + * be written using exactly one write() call. Other events cannot + * be mixed to the same write. + * + * For FM synths (YM3812/OPL3) use struct sbi_instrument and write it to the + * /dev/sequencer. Don't write other data together with the instrument structure + * Set the key field of the structure to FM_PATCH. The device field is used to + * route the patch to the corresponding device. + * + * For wave table use struct patch_info. Initialize the key field + * to WAVE_PATCH. + */ +#define SEQ_PRIVATE 0xfe /* Low level HW dependent events (8 bytes) */ +#define SEQ_EXTENDED 0xff /* Extended events (8 bytes) OBSOLETE */ + +/* + * Record for FM patches + */ + +typedef unsigned char sbi_instr_data[32]; + +struct sbi_instrument +{ + unsigned short key; /* FM_PATCH or OPL3_PATCH */ +#define FM_PATCH _PATCHKEY(0x01) +#define OPL3_PATCH _PATCHKEY(0x03) + short device; /* Synth# (0-4) */ + int channel; /* Program# to be initialized */ + sbi_instr_data operators; /* Register settings for operator cells (.SBI format) */ +}; + +struct synth_info +{ /* Read only */ + char name[30]; + int device; /* 0-N. INITIALIZE BEFORE CALLING */ + int synth_type; +#define SYNTH_TYPE_FM 0 +#define SYNTH_TYPE_SAMPLE 1 +#define SYNTH_TYPE_MIDI 2 /* Midi interface */ + + int synth_subtype; +#define FM_TYPE_ADLIB 0x00 +#define FM_TYPE_OPL3 0x01 +#define MIDI_TYPE_MPU401 0x401 + +#define SAMPLE_TYPE_BASIC 0x10 +#define SAMPLE_TYPE_GUS SAMPLE_TYPE_BASIC +#define SAMPLE_TYPE_WAVEFRONT 0x11 + + int perc_mode; /* No longer supported */ + int nr_voices; + int nr_drums; /* Obsolete field */ + int instr_bank_size; + unsigned int capabilities; +#define SYNTH_CAP_PERCMODE 0x00000001 /* No longer used */ +#define SYNTH_CAP_OPL3 0x00000002 /* Set if OPL3 supported */ +#define SYNTH_CAP_INPUT 0x00000004 /* Input (MIDI) device */ + int dummies[19]; /* Reserve space */ +}; + +struct sound_timer_info +{ + char name[32]; + int caps; +}; + +struct midi_info /* OBSOLETE */ +{ + char name[30]; + int device; /* 0-N. INITIALIZE BEFORE CALLING */ + unsigned int capabilities; /* To be defined later */ + int dev_type; + int dummies[18]; /* Reserve space */ +}; + +/* + * Level 2 event types for /dev/sequencer + */ + +/* + * The 4 most significant bits of byte 0 specify the class of + * the event: + * + * 0x8X = system level events, + * 0x9X = device/port specific events, event[1] = device/port, + * The last 4 bits give the subtype: + * 0x02 = Channel event (event[3] = chn). + * 0x01 = note event (event[4] = note). + * (0x01 is not used alone but always with bit 0x02). + * event[2] = MIDI message code (0x80=note off etc.) + * + */ + +#define EV_SEQ_LOCAL 0x80 +#define EV_TIMING 0x81 +#define EV_CHN_COMMON 0x92 +#define EV_CHN_VOICE 0x93 +#define EV_SYSEX 0x94 +#define EV_SYSTEM 0x95 /* MIDI system and real time messages (input only) */ +/* + * Event types 200 to 220 are reserved for application use. + * These numbers will not be used by the driver. + */ + +/* + * Events for event type EV_CHN_VOICE + */ + +#define MIDI_NOTEOFF 0x80 +#define MIDI_NOTEON 0x90 +#define MIDI_KEY_PRESSURE 0xA0 + +/* + * Events for event type EV_CHN_COMMON + */ + +#define MIDI_CTL_CHANGE 0xB0 +#define MIDI_PGM_CHANGE 0xC0 +#define MIDI_CHN_PRESSURE 0xD0 +#define MIDI_PITCH_BEND 0xE0 + +#define MIDI_SYSTEM_PREFIX 0xF0 + +/* + * Timer event types + */ +#define TMR_WAIT_REL 1 /* Time relative to the prev time */ +#define TMR_WAIT_ABS 2 /* Absolute time since TMR_START */ +#define TMR_STOP 3 +#define TMR_START 4 +#define TMR_CONTINUE 5 +#define TMR_TEMPO 6 +#define TMR_ECHO 8 +#define TMR_CLOCK 9 /* MIDI clock */ +#define TMR_SPP 10 /* Song position pointer */ +#define TMR_TIMESIG 11 /* Time signature */ + +/* + * Local event types + */ +#define LOCL_STARTAUDIO 1 +#define LOCL_STARTAUDIO2 2 +#define LOCL_STARTAUDIO3 3 +#define LOCL_STARTAUDIO4 4 + +#if (!defined(__KERNEL__) && !defined(KERNEL) && !defined(INKERNEL) && !defined(_KERNEL)) || defined(USE_SEQ_MACROS) +/* + * Some convenience macros to simplify programming of the + * /dev/sequencer interface + * + * These macros define the API which should be used when possible. + */ +#define SEQ_DECLAREBUF() SEQ_USE_EXTBUF() + +void seqbuf_dump (void); /* This function must be provided by programs */ + +EXTERNC int OSS_init (int seqfd, int buflen); +EXTERNC void OSS_seqbuf_dump (int fd, unsigned char *buf, int buflen); +EXTERNC void OSS_seq_advbuf (int len, int fd, unsigned char *buf, int buflen); +EXTERNC void OSS_seq_needbuf (int len, int fd, unsigned char *buf, + int buflen); +EXTERNC void OSS_patch_caching (int dev, int chn, int patch, int fd, + unsigned char *buf, int buflen); +EXTERNC void OSS_drum_caching (int dev, int chn, int patch, int fd, + unsigned char *buf, int buflen); +EXTERNC void OSS_write_patch (int fd, unsigned char *buf, int len); +EXTERNC int OSS_write_patch2 (int fd, unsigned char *buf, int len); + +#define SEQ_PM_DEFINES int __foo_bar___ +#ifdef OSSLIB +# define SEQ_USE_EXTBUF() \ + EXTERNC unsigned char *_seqbuf; \ + EXTERNC int _seqbuflen;EXTERNC int _seqbufptr +# define SEQ_DEFINEBUF(len) SEQ_USE_EXTBUF();static int _requested_seqbuflen=len +# define _SEQ_ADVBUF(len) OSS_seq_advbuf(len, seqfd, _seqbuf, _seqbuflen) +# define _SEQ_NEEDBUF(len) OSS_seq_needbuf(len, seqfd, _seqbuf, _seqbuflen) +# define SEQ_DUMPBUF() OSS_seqbuf_dump(seqfd, _seqbuf, _seqbuflen) + +# define SEQ_LOAD_GMINSTR(dev, instr) \ + OSS_patch_caching(dev, -1, instr, seqfd, _seqbuf, _seqbuflen) +# define SEQ_LOAD_GMDRUM(dev, drum) \ + OSS_drum_caching(dev, -1, drum, seqfd, _seqbuf, _seqbuflen) +#else /* !OSSLIB */ + +# define SEQ_LOAD_GMINSTR(dev, instr) +# define SEQ_LOAD_GMDRUM(dev, drum) + +# define SEQ_USE_EXTBUF() \ + EXTERNC unsigned char _seqbuf[]; \ + EXTERNC int _seqbuflen;EXTERNC int _seqbufptr + +#ifndef USE_SIMPLE_MACROS +/* Sample seqbuf_dump() implementation: + * + * SEQ_DEFINEBUF (2048); -- Defines a buffer for 2048 bytes + * + * int seqfd; -- The file descriptor for /dev/sequencer. + * + * void + * seqbuf_dump () + * { + * if (_seqbufptr) + * if (write (seqfd, _seqbuf, _seqbufptr) == -1) + * { + * perror ("write /dev/sequencer"); + * exit (-1); + * } + * _seqbufptr = 0; + * } + */ + +#define SEQ_DEFINEBUF(len) \ + unsigned char _seqbuf[len]; int _seqbuflen = len;int _seqbufptr = 0 +#define _SEQ_NEEDBUF(len) \ + if ((_seqbufptr+(len)) > _seqbuflen) seqbuf_dump() +#define _SEQ_ADVBUF(len) _seqbufptr += len +#define SEQ_DUMPBUF seqbuf_dump +#else +/* + * This variation of the sequencer macros is used just to format one event + * using fixed buffer. + * + * The program using the macro library must define the following macros before + * using this library. + * + * #define _seqbuf name of the buffer (unsigned char[]) + * #define _SEQ_ADVBUF(len) If the applic needs to know the exact + * size of the event, this macro can be used. + * Otherwise this must be defined as empty. + * #define _seqbufptr Define the name of index variable or 0 if + * not required. + */ +#define _SEQ_NEEDBUF(len) /* empty */ +#endif +#endif /* !OSSLIB */ + +#define SEQ_VOLUME_MODE(dev, mode) \ + {_SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = SEQ_EXTENDED;\ + _seqbuf[_seqbufptr+1] = SEQ_VOLMODE;\ + _seqbuf[_seqbufptr+2] = (dev);\ + _seqbuf[_seqbufptr+3] = (mode);\ + _seqbuf[_seqbufptr+4] = 0;\ + _seqbuf[_seqbufptr+5] = 0;\ + _seqbuf[_seqbufptr+6] = 0;\ + _seqbuf[_seqbufptr+7] = 0;\ + _SEQ_ADVBUF(8);} + +/* + * Midi voice messages + */ + +#define _CHN_VOICE(dev, event, chn, note, parm) \ + {_SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = EV_CHN_VOICE;\ + _seqbuf[_seqbufptr+1] = (dev);\ + _seqbuf[_seqbufptr+2] = (event);\ + _seqbuf[_seqbufptr+3] = (chn);\ + _seqbuf[_seqbufptr+4] = (note);\ + _seqbuf[_seqbufptr+5] = (parm);\ + _seqbuf[_seqbufptr+6] = (0);\ + _seqbuf[_seqbufptr+7] = 0;\ + _SEQ_ADVBUF(8);} + +#define SEQ_START_NOTE(dev, chn, note, vol) \ + _CHN_VOICE(dev, MIDI_NOTEON, chn, note, vol) + +#define SEQ_STOP_NOTE(dev, chn, note, vol) \ + _CHN_VOICE(dev, MIDI_NOTEOFF, chn, note, vol) + +#define SEQ_KEY_PRESSURE(dev, chn, note, pressure) \ + _CHN_VOICE(dev, MIDI_KEY_PRESSURE, chn, note, pressure) + +/* + * Midi channel messages + */ + +#define _CHN_COMMON(dev, event, chn, p1, p2, w14) \ + {_SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = EV_CHN_COMMON;\ + _seqbuf[_seqbufptr+1] = (dev);\ + _seqbuf[_seqbufptr+2] = (event);\ + _seqbuf[_seqbufptr+3] = (chn);\ + _seqbuf[_seqbufptr+4] = (p1);\ + _seqbuf[_seqbufptr+5] = (p2);\ + *(short *)&_seqbuf[_seqbufptr+6] = (w14);\ + _SEQ_ADVBUF(8);} +/* + * SEQ_SYSEX permits sending of sysex messages. (It may look that it permits + * sending any MIDI bytes but it's absolutely not possible. Trying to do + * so _will_ cause problems with MPU401 intelligent mode). + * + * Sysex messages are sent in blocks of 1 to 6 bytes. Longer messages must be + * sent by calling SEQ_SYSEX() several times (there must be no other events + * between them). First sysex fragment must have 0xf0 in the first byte + * and the last byte (buf[len-1] of the last fragment must be 0xf7. No byte + * between these sysex start and end markers cannot be larger than 0x7f. Also + * lengths of each fragments (except the last one) must be 6. + * + * Breaking the above rules may work with some MIDI ports but is likely to + * cause fatal problems with some other devices (such as MPU401). + */ +#define SEQ_SYSEX(dev, buf, len) \ + {int ii, ll=(len); \ + unsigned char *bufp=buf;\ + if (ll>6)ll=6;\ + _SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr] = EV_SYSEX;\ + _seqbuf[_seqbufptr+1] = (dev);\ + for(ii=0;ii>8)&0xff);\ + _seqbuf[_seqbufptr+7] = 0;\ + _SEQ_ADVBUF(8);} +/* + * The following 5 macros are incorrectly implemented and obsolete. + * Use SEQ_BENDER and SEQ_CONTROL (with proper controller) instead. + */ +#define SEQ_PITCHBEND(dev, voice, value) \ + SEQ_V2_X_CONTROL(dev, voice, CTRL_PITCH_BENDER, value) +#define SEQ_BENDER_RANGE(dev, voice, value) \ + SEQ_V2_X_CONTROL(dev, voice, CTRL_PITCH_BENDER_RANGE, value) +#define SEQ_EXPRESSION(dev, voice, value) \ + SEQ_CONTROL(dev, voice, CTL_EXPRESSION, value*128) +#define SEQ_MAIN_VOLUME(dev, voice, value) \ + SEQ_CONTROL(dev, voice, CTL_MAIN_VOLUME, (value*16383)/100) +#define SEQ_PANNING(dev, voice, pos) \ + SEQ_CONTROL(dev, voice, CTL_PAN, (pos+128) / 2) + +/* + * Timing and syncronization macros + */ + +#define _TIMER_EVENT(ev, parm) {_SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr+0] = EV_TIMING; \ + _seqbuf[_seqbufptr+1] = (ev); \ + _seqbuf[_seqbufptr+2] = 0;\ + _seqbuf[_seqbufptr+3] = 0;\ + *(unsigned int *)&_seqbuf[_seqbufptr+4] = (parm); \ + _SEQ_ADVBUF(8);} + +#define SEQ_START_TIMER() _TIMER_EVENT(TMR_START, 0) +#define SEQ_STOP_TIMER() _TIMER_EVENT(TMR_STOP, 0) +#define SEQ_CONTINUE_TIMER() _TIMER_EVENT(TMR_CONTINUE, 0) +#define SEQ_WAIT_TIME(ticks) _TIMER_EVENT(TMR_WAIT_ABS, ticks) +#define SEQ_DELTA_TIME(ticks) _TIMER_EVENT(TMR_WAIT_REL, ticks) +#define SEQ_ECHO_BACK(key) _TIMER_EVENT(TMR_ECHO, key) +#define SEQ_SET_TEMPO(value) _TIMER_EVENT(TMR_TEMPO, value) +#define SEQ_SONGPOS(pos) _TIMER_EVENT(TMR_SPP, pos) +#define SEQ_TIME_SIGNATURE(sig) _TIMER_EVENT(TMR_TIMESIG, sig) + +/* + * Local control events + */ + +#define _LOCAL_EVENT(ev, parm) {_SEQ_NEEDBUF(8);\ + _seqbuf[_seqbufptr+0] = EV_SEQ_LOCAL; \ + _seqbuf[_seqbufptr+1] = (ev); \ + _seqbuf[_seqbufptr+2] = 0;\ + _seqbuf[_seqbufptr+3] = 0;\ + *(unsigned int *)&_seqbuf[_seqbufptr+4] = (parm); \ + _SEQ_ADVBUF(8);} + +#define SEQ_PLAYAUDIO(devmask) _LOCAL_EVENT(LOCL_STARTAUDIO, devmask) +#define SEQ_PLAYAUDIO2(devmask) _LOCAL_EVENT(LOCL_STARTAUDIO2, devmask) +#define SEQ_PLAYAUDIO3(devmask) _LOCAL_EVENT(LOCL_STARTAUDIO3, devmask) +#define SEQ_PLAYAUDIO4(devmask) _LOCAL_EVENT(LOCL_STARTAUDIO4, devmask) +/* + * Events for the level 1 interface only + */ + +#define SEQ_MIDIOUT(device, byte) {_SEQ_NEEDBUF(4);\ + _seqbuf[_seqbufptr] = SEQ_MIDIPUTC;\ + _seqbuf[_seqbufptr+1] = (byte);\ + _seqbuf[_seqbufptr+2] = (device);\ + _seqbuf[_seqbufptr+3] = 0;\ + _SEQ_ADVBUF(4);} + +/* + * Patch loading. + */ +#ifdef OSSLIB +# define SEQ_WRPATCH(patchx, len) \ + OSS_write_patch(seqfd, (char*)(patchx), len) +# define SEQ_WRPATCH2(patchx, len) \ + OSS_write_patch2(seqfd, (char*)(patchx), len) +#else +# define SEQ_WRPATCH(patchx, len) \ + {if (_seqbufptr) SEQ_DUMPBUF();\ + if (write(seqfd, (char*)(patchx), len)==-1) \ + perror("Write patch: /dev/sequencer");} +# define SEQ_WRPATCH2(patchx, len) \ + (SEQ_DUMPBUF(), write(seqfd, (char*)(patchx), len)) +#endif + +#endif +#endif /* ifndef DISABLE_SEQUENCER */ + +/* + **************************************************************************** + * ioctl commands for the /dev/midi## + ****************************************************************************/ +#define SNDCTL_MIDI_PRETIME __SIOWR('m', 0, int) + +#if 0 +/* + * The SNDCTL_MIDI_MPUMODE and SNDCTL_MIDI_MPUCMD calls + * are completely obsolete. The hardware device (MPU-401 "intelligent mode" + * and compatibles) has disappeared from the market 10 years ago so there + * is no need for this stuff. The MPU-401 "UART" mode devices don't support + * this stuff. + */ +typedef struct +{ + unsigned char cmd; + char nr_args, nr_returns; + unsigned char data[30]; +} mpu_command_rec; + +#define SNDCTL_MIDI_MPUMODE __SIOWR('m', 1, int) +#define SNDCTL_MIDI_MPUCMD __SIOWR('m', 2, mpu_command_rec) +#endif + +/* + * SNDCTL_MIDI_MTCINPUT turns on a mode where OSS automatically inserts + * MTC quarter frame messages (F1 xx) to the input. + * The argument is the MTC mode: + * + * -1 = Turn MTC messages OFF (default) + * 24 = 24 FPS + * 25 = 25 FPS + * 29 = 30 FPS drop frame + * 30 = 30 FPS + * + * Note that 25 FPS mode is probably the only mode that is supported. Other + * modes may be supported in the future versions of OSS, 25 FPS is handy + * because it generates 25*4=100 quarter frame messages per second which + * matches the usual 100 HZ system timer rate). + * + * The quarter frame timer will be reset to 0:00:00:00.0 at the moment this + * ioctl is made. + */ +#define SNDCTL_MIDI_MTCINPUT __SIOWR('m', 3, int) + +/* + * MTC/SMPTE time code record (for future use) + */ +typedef struct +{ + unsigned char hours, minutes, seconds, frames, qframes; + char direction; +#define MTC_DIR_STOPPED 0 +#define MTC_DIR_FORWARD 1 +#define MTC_DIR_BACKWARD -1 + unsigned char time_code_type; + unsigned int flags; +} oss_mtc_data_t; + +#define SNDCTL_MIDI_SETMODE __SIOWR('m', 6, int) +# define MIDI_MODE_TRADITIONAL 0 +# define MIDI_MODE_TIMED 1 /* Input times are in MIDI ticks */ +# define MIDI_MODE_TIMED_ABS 2 /* Input times are absolute (usecs) */ + +/* + * Packet header for MIDI_MODE_TIMED and MIDI_MODE_TIMED_ABS + */ +typedef unsigned long long oss_midi_time_t; /* Variable type for MIDI time (clock ticks) */ + +typedef struct +{ + int magic; /* Initialize to MIDI_HDR_MAGIC */ +#define MIDI_HDR_MAGIC -1 + unsigned short event_type; +#define MIDI_EV_WRITE 0 /* Write or read (with payload) */ +#define MIDI_EV_TEMPO 1 +#define MIDI_EV_ECHO 2 +#define MIDI_EV_START 3 +#define MIDI_EV_STOP 4 +#define MIDI_EV_CONTINUE 5 +#define MIDI_EV_XPRESSWRITE 6 +#define MIDI_EV_TIMEBASE 7 +#define MIDI_EV_DEVCTL 8 /* Device control read/write */ + unsigned short options; +#define MIDI_OPT_NONE 0x0000 +#define MIDI_OPT_TIMED 0x0001 +#define MIDI_OPT_CONTINUATION 0x0002 +#define MIDI_OPT_USECTIME 0x0004 /* Time is absolute (in usecs) */ +#define MIDI_OPT_BUSY 0x0008 /* Reserved for internal use */ + oss_midi_time_t time; + int parm; + int filler[3]; /* Fur future expansion - init to zeros */ +} midi_packet_header_t; +/* + * MIDI_PAYLOAD_SIZE is the maximum size of one MIDI input chunk. It must be + * less (or equal) than 1024 which is the read size recommended in the + * documentation. TODO: Explain this better. + */ +#define MIDI_PAYLOAD_SIZE 1000 + +typedef struct +{ + midi_packet_header_t hdr; + unsigned char payload[MIDI_PAYLOAD_SIZE]; +} midi_packet_t; + +#define SNDCTL_MIDI_TIMEBASE __SIOWR('m', 7, int) +#define SNDCTL_MIDI_TEMPO __SIOWR('m', 8, int) +/* + * User land MIDI servers (synths) can use SNDCTL_MIDI_SET_LATENCY + * to request MIDI events to be sent to them in advance. The parameter + * (in microseconds) tells how much before the events are submitted. + * + * This feature is only valid for loopback devices and possibly some other + * types of virtual devices. + */ +#define SNDCTL_MIDI_SET_LATENCY __SIOW ('m', 9, int) +/* + **************************************************************************** + * IOCTL commands for /dev/dsp + ****************************************************************************/ + +#define SNDCTL_DSP_HALT __SIO ('P', 0) +#define SNDCTL_DSP_RESET SNDCTL_DSP_HALT /* Old name */ +#define SNDCTL_DSP_SYNC __SIO ('P', 1) +#define SNDCTL_DSP_SPEED __SIOWR('P', 2, int) + +/* SNDCTL_DSP_STEREO is obsolete - use SNDCTL_DSP_CHANNELS instead */ +#define SNDCTL_DSP_STEREO __SIOWR('P', 3, int) +/* SNDCTL_DSP_STEREO is obsolete - use SNDCTL_DSP_CHANNELS instead */ + +#define SNDCTL_DSP_GETBLKSIZE __SIOWR('P', 4, int) +#define SNDCTL_DSP_SAMPLESIZE SNDCTL_DSP_SETFMT +#define SNDCTL_DSP_CHANNELS __SIOWR('P', 6, int) +#define SNDCTL_DSP_POST __SIO ('P', 8) +#define SNDCTL_DSP_SUBDIVIDE __SIOWR('P', 9, int) +#define SNDCTL_DSP_SETFRAGMENT __SIOWR('P',10, int) + +/* Audio data formats (Note! U8=8 and S16_LE=16 for compatibility) */ +#define SNDCTL_DSP_GETFMTS __SIOR ('P',11, int) /* Returns a mask */ +#define SNDCTL_DSP_SETFMT __SIOWR('P',5, int) /* Selects ONE fmt */ +# define AFMT_QUERY 0x00000000 /* Return current fmt */ +# define AFMT_MU_LAW 0x00000001 +# define AFMT_A_LAW 0x00000002 +# define AFMT_IMA_ADPCM 0x00000004 +# define AFMT_U8 0x00000008 +# define AFMT_S16_LE 0x00000010 /* Little endian signed 16 */ +# define AFMT_S16_BE 0x00000020 /* Big endian signed 16 */ +# define AFMT_S8 0x00000040 +# define AFMT_U16_LE 0x00000080 /* Little endian U16 */ +# define AFMT_U16_BE 0x00000100 /* Big endian U16 */ +# define AFMT_MPEG 0x00000200 /* MPEG (2) audio */ + +/* AC3 _compressed_ bitstreams (See Programmer's Guide for details). */ +# define AFMT_AC3 0x00000400 +/* Ogg Vorbis _compressed_ bit streams */ +# define AFMT_VORBIS 0x00000800 + +/* 32 bit formats (MSB aligned) formats */ +# define AFMT_S32_LE 0x00001000 +# define AFMT_S32_BE 0x00002000 + +/* Reserved for _native_ endian double precision IEEE floating point */ +# define AFMT_FLOAT 0x00004000 + +/* 24 bit formats (LSB aligned in 32 bit word) formats */ +# define AFMT_S24_LE 0x00008000 +# define AFMT_S24_BE 0x00010000 + +/* + * S/PDIF raw format. In this format the S/PDIF frames (including all + * control and user bits) are included in the data stream. Each sample + * is stored in a 32 bit frame (see IEC-958 for more info). This format + * is supported by very few devices and it's only usable for purposes + * where full access to the control/user bits is required (real time control). + */ +# define AFMT_SPDIF_RAW 0x00020000 + +/* 24 bit packed (3 byte) little endian format (USB compatibility) */ +# define AFMT_S24_PACKED 0x00040000 + + +/* + * Some big endian/little endian handling macros (native endian and opposite + * endian formats). The usage of these macros is described in the OSS + * Programmer's Manual. + */ + +#if defined(_AIX) || defined(AIX) || defined(sparc) || defined(__hppa) || defined(PPC) || defined(__powerpc__) && !defined(i386) && !defined(__i386) && !defined(__i386__) + +/* Big endian machines */ +# define _PATCHKEY(id) (0xfd00|id) +# define AFMT_S16_NE AFMT_S16_BE +# define AFMT_U16_NE AFMT_U16_BE +# define AFMT_S32_NE AFMT_S32_BE +# define AFMT_S24_NE AFMT_S24_BE +# define AFMT_S16_OE AFMT_S16_LE +# define AFMT_S32_OE AFMT_S32_LE +# define AFMT_S24_OE AFMT_S24_LE +#else +# define _PATCHKEY(id) ((id<<8)|0xfd) +# define AFMT_S16_NE AFMT_S16_LE +# define AFMT_U16_NE AFMT_U16_LE +# define AFMT_S32_NE AFMT_S32_LE +# define AFMT_S24_NE AFMT_S24_LE +# define AFMT_S16_OE AFMT_S16_BE +# define AFMT_S32_OE AFMT_S32_BE +# define AFMT_S24_OE AFMT_S24_BE +#endif +/* + * Buffer status queries. + */ +typedef struct audio_buf_info +{ + int fragments; /* # of available fragments (partially usend ones not counted) */ + int fragstotal; /* Total # of fragments allocated */ + int fragsize; /* Size of a fragment in bytes */ + int bytes; /* Available space in bytes (includes partially used fragments) */ + /* Note! 'bytes' could be more than fragments*fragsize */ +} audio_buf_info; + +#define SNDCTL_DSP_GETOSPACE __SIOR ('P',12, audio_buf_info) +#define SNDCTL_DSP_GETISPACE __SIOR ('P',13, audio_buf_info) +#define SNDCTL_DSP_GETCAPS __SIOR ('P',15, int) +# define PCM_CAP_REVISION 0x000000ff /* Bits for revision level (0 to 255) */ +# define PCM_CAP_DUPLEX 0x00000100 /* Full duplex record/playback */ +# define PCM_CAP_REALTIME 0x00000200 /* Not in use */ +# define PCM_CAP_BATCH 0x00000400 /* Device has some kind of */ + /* internal buffers which may */ + /* cause some delays and */ + /* decrease precision of timing */ +# define PCM_CAP_COPROC 0x00000800 /* Has a coprocessor */ + /* Sometimes it's a DSP */ + /* but usually not */ +# define PCM_CAP_TRIGGER 0x00001000 /* Supports SETTRIGGER */ +# define PCM_CAP_MMAP 0x00002000 /* Supports mmap() */ +# define PCM_CAP_MULTI 0x00004000 /* Supports multiple open */ +# define PCM_CAP_BIND 0x00008000 /* Supports binding to front/rear/center/lfe */ +# define PCM_CAP_INPUT 0x00010000 /* Supports recording */ +# define PCM_CAP_OUTPUT 0x00020000 /* Supports playback */ +# define PCM_CAP_VIRTUAL 0x00040000 /* Virtual device */ +/* 0x00040000 and 0x00080000 reserved for future use */ + +/* Analog/digital control capabilities */ +# define PCM_CAP_ANALOGOUT 0x00100000 +# define PCM_CAP_ANALOGIN 0x00200000 +# define PCM_CAP_DIGITALOUT 0x00400000 +# define PCM_CAP_DIGITALIN 0x00800000 +# define PCM_CAP_ADMASK 0x00f00000 +/* + * NOTE! (capabilities & PCM_CAP_ADMASK)==0 means just that the + * digital/analog interface control features are not supported by the + * device/driver. However the device still supports analog, digital or + * both inputs/outputs (depending on the device). See the OSS Programmer's + * Guide for full details. + */ +# define PCM_CAP_SHADOW 0x01000000 /* "Shadow" device */ + +/* + * Preferred channel usage. These bits can be used to + * give recommendations to the application. Used by few drivers. + * For example if ((caps & DSP_CH_MASK) == DSP_CH_MONO) means that + * the device works best in mono mode. However it doesn't necessarily mean + * that the device cannot be used in stereo. These bits should only be used + * by special applications such as multi track hard disk recorders to find + * out the initial setup. However the user should be able to override this + * selection. + * + * To find out which modes are actually supported the application should + * try to select them using SNDCTL_DSP_CHANNELS. + */ +# define DSP_CH_MASK 0x06000000 /* Mask */ +# define DSP_CH_ANY 0x00000000 /* No preferred mode */ +# define DSP_CH_MONO 0x02000000 +# define DSP_CH_STEREO 0x04000000 +# define DSP_CH_MULTI 0x06000000 /* More than two channels */ + +# define PCM_CAP_HIDDEN 0x08000000 /* Hidden device */ +# define PCM_CAP_FREERATE 0x10000000 +# define PCM_CAP_MODEM 0x20000000 /* Modem device */ +# define PCM_CAP_DEFAULT 0x40000000 /* "Default" device */ + +/* + * The PCM_CAP_* capability names were known as DSP_CAP_* prior OSS 4.0 + * so it's necessary to define the older names too. + */ +#define DSP_CAP_ADMASK PCM_CAP_ADMASK +#define DSP_CAP_ANALOGIN PCM_CAP_ANALOGIN +#define DSP_CAP_ANALOGOUT PCM_CAP_ANALOGOUT +#define DSP_CAP_BATCH PCM_CAP_BATCH +#define DSP_CAP_BIND PCM_CAP_BIND +#define DSP_CAP_COPROC PCM_CAP_COPROC +#define DSP_CAP_DEFAULT PCM_CAP_DEFAULT +#define DSP_CAP_DIGITALIN PCM_CAP_DIGITALIN +#define DSP_CAP_DIGITALOUT PCM_CAP_DIGITALOUT +#define DSP_CAP_DUPLEX PCM_CAP_DUPLEX +#define DSP_CAP_FREERATE PCM_CAP_FREERATE +#define DSP_CAP_HIDDEN PCM_CAP_HIDDEN +#define DSP_CAP_INPUT PCM_CAP_INPUT +#define DSP_CAP_MMAP PCM_CAP_MMAP +#define DSP_CAP_MODEM PCM_CAP_MODEM +#define DSP_CAP_MULTI PCM_CAP_MULTI +#define DSP_CAP_OUTPUT PCM_CAP_OUTPUT +#define DSP_CAP_REALTIME PCM_CAP_REALTIME +#define DSP_CAP_REVISION PCM_CAP_REVISION +#define DSP_CAP_SHADOW PCM_CAP_SHADOW +#define DSP_CAP_TRIGGER PCM_CAP_TRIGGER +#define DSP_CAP_VIRTUAL PCM_CAP_VIRTUAL + +#define SNDCTL_DSP_GETTRIGGER __SIOR ('P',16, int) +#define SNDCTL_DSP_SETTRIGGER __SIOW ('P',16, int) +# define PCM_ENABLE_INPUT 0x00000001 +# define PCM_ENABLE_OUTPUT 0x00000002 + +typedef struct count_info +{ + unsigned int bytes; /* Total # of bytes processed */ + int blocks; /* # of fragment transitions since last time */ + int ptr; /* Current DMA pointer value */ +} count_info; + +#define SNDCTL_DSP_GETIPTR __SIOR ('P',17, count_info) +#define SNDCTL_DSP_GETOPTR __SIOR ('P',18, count_info) + +typedef struct buffmem_desc +{ + unsigned *buffer; + int size; +} buffmem_desc; +#define SNDCTL_DSP_SETSYNCRO __SIO ('P', 21) +#define SNDCTL_DSP_SETDUPLEX __SIO ('P', 22) + +#define SNDCTL_DSP_PROFILE __SIOW ('P', 23, int) /* OBSOLETE */ +#define APF_NORMAL 0 /* Normal applications */ +#define APF_NETWORK 1 /* Underruns probably caused by an "external" delay */ +#define APF_CPUINTENS 2 /* Underruns probably caused by "overheating" the CPU */ + +#define SNDCTL_DSP_GETODELAY __SIOR ('P', 23, int) + +typedef struct audio_errinfo +{ + int play_underruns; + int rec_overruns; + unsigned int play_ptradjust; + unsigned int rec_ptradjust; + int play_errorcount; + int rec_errorcount; + int play_lasterror; + int rec_lasterror; + int play_errorparm; + int rec_errorparm; + int filler[16]; +} audio_errinfo; + +#define SNDCTL_DSP_GETPLAYVOL __SIOR ('P', 24, int) +#define SNDCTL_DSP_SETPLAYVOL __SIOWR('P', 24, int) +#define SNDCTL_DSP_GETERROR __SIOR ('P', 25, audio_errinfo) +/* + **************************************************************************** + * Digital interface (S/PDIF) control interface + */ + +typedef struct oss_digital_control +{ + unsigned int caps; +#define DIG_CBITIN_NONE 0x00000000 +#define DIG_CBITIN_LIMITED 0x00000001 +#define DIG_CBITIN_DATA 0x00000002 +#define DIG_CBITIN_BYTE0 0x00000004 +#define DIG_CBITIN_FULL 0x00000008 +#define DIG_CBITIN_MASK 0x0000000f +#define DIG_CBITOUT_NONE 0x00000000 +#define DIG_CBITOUT_LIMITED 0x00000010 +#define DIG_CBITOUT_BYTE0 0x00000020 +#define DIG_CBITOUT_FULL 0x00000040 +#define DIG_CBITOUT_DATA 0x00000080 +#define DIG_CBITOUT_MASK 0x000000f0 +#define DIG_UBITIN 0x00000100 +#define DIG_UBITOUT 0x00000200 +#define DIG_VBITOUT 0x00000400 +#define DIG_OUTRATE 0x00000800 +#define DIG_INRATE 0x00001000 +#define DIG_INBITS 0x00002000 +#define DIG_OUTBITS 0x00004000 +#define DIG_EXACT 0x00010000 +#define DIG_PRO 0x00020000 +#define DIG_CONSUMER 0x00040000 +#define DIG_PASSTHROUGH 0x00080000 +#define DIG_OUTSEL 0x00100000 + + unsigned int valid; +#define VAL_CBITIN 0x00000001 +#define VAL_UBITIN 0x00000002 +#define VAL_CBITOUT 0x00000004 +#define VAL_UBITOUT 0x00000008 +#define VAL_ISTATUS 0x00000010 +#define VAL_IRATE 0x00000020 +#define VAL_ORATE 0x00000040 +#define VAL_INBITS 0x00000080 +#define VAL_OUTBITS 0x00000100 +#define VAL_REQUEST 0x00000200 +#define VAL_OUTSEL 0x00000400 + +#define VAL_OUTMASK (VAL_CBITOUT|VAL_UBITOUT|VAL_ORATE|VAL_OUTBITS|VAL_OUTSEL) + + unsigned int request, param; +#define SPD_RQ_PASSTHROUGH 1 + + unsigned char cbitin[24]; + unsigned char ubitin[24]; + unsigned char cbitout[24]; + unsigned char ubitout[24]; + + unsigned int outsel; +#define OUTSEL_DIGITAL 1 +#define OUTSEL_ANALOG 2 +#define OUTSEL_BOTH (OUTSEL_DIGITAL|OUTSEL_ANALOG) + + int in_data; /* Audio/data if autodetectable by the receiver */ +#define IND_UNKNOWN 0 +#define IND_AUDIO 1 +#define IND_DATA 2 + + int in_locked; /* Receiver locked */ +#define LOCK_NOT_INDICATED 0 +#define LOCK_UNLOCKED 1 +#define LOCK_LOCKED 2 + + int in_quality; /* Input signal quality */ +#define IN_QUAL_NOT_INDICATED 0 +#define IN_QUAL_POOR 1 +#define IN_QUAL_GOOD 2 + + int in_vbit, out_vbit; /* V bits */ +#define VBIT_NOT_INDICATED 0 +#define VBIT_OFF 1 +#define VBIT_ON 2 + + unsigned int in_errors; /* Various input error conditions */ +#define INERR_CRC 0x0001 +#define INERR_QCODE_CRC 0x0002 +#define INERR_PARITY 0x0004 +#define INERR_BIPHASE 0x0008 + + int srate_in, srate_out; + int bits_in, bits_out; + + int filler[32]; +} oss_digital_control; + +#define SNDCTL_DSP_READCTL __SIOWR('P', 26, oss_digital_control) +#define SNDCTL_DSP_WRITECTL __SIOWR('P', 27, oss_digital_control) + +/* + **************************************************************************** + * Sync groups for audio devices + */ +typedef struct oss_syncgroup +{ + int id; + int mode; + int filler[16]; +} oss_syncgroup; + +#define SNDCTL_DSP_SYNCGROUP __SIOWR('P', 28, oss_syncgroup) +#define SNDCTL_DSP_SYNCSTART __SIOW ('P', 29, int) + +/* + ************************************************************************** + * "cooked" mode enables software based conversions for sample rate, sample + * format (bits) and number of channels (mono/stereo). These conversions are + * required with some devices that support only one sample rate or just stereo + * to let the applications to use other formats. The cooked mode is enabled by + * default. However it's necessary to disable this mode when mmap() is used or + * when very deterministic timing is required. SNDCTL_DSP_COOKEDMODE is an + * optional call introduced in OSS 3.9.6f. It's _error return must be ignored_ + * since normally this call will return erno=EINVAL. + * + * SNDCTL_DSP_COOKEDMODE must be called immediately after open before doing + * anything else. Otherwise the call will not have any effect. + */ +#define SNDCTL_DSP_COOKEDMODE __SIOW ('P', 30, int) + +/* + ************************************************************************** + * SNDCTL_DSP_SILENCE and SNDCTL_DSP_SKIP are new calls in OSS 3.99.0 + * that can be used to implement pause/continue during playback (no effect + * on recording). + */ +#define SNDCTL_DSP_SILENCE __SIO ('P', 31) +#define SNDCTL_DSP_SKIP __SIO ('P', 32) +/* + **************************************************************************** + * Abort transfer (reset) functions for input and output + */ +#define SNDCTL_DSP_HALT_INPUT __SIO ('P', 33) +#define SNDCTL_DSP_RESET_INPUT SNDCTL_DSP_HALT_INPUT /* Old name */ +#define SNDCTL_DSP_HALT_OUTPUT __SIO ('P', 34) +#define SNDCTL_DSP_RESET_OUTPUT SNDCTL_DSP_HALT_OUTPUT /* Old name */ +/* + **************************************************************************** + * Low water level control + */ +#define SNDCTL_DSP_LOW_WATER __SIOW ('P', 34, int) + +/* + **************************************************************************** + * 64 bit pointer support. Only available in environments that support + * the 64 bit (long long) integer type. + */ +#ifndef OSS_NO_LONG_LONG +typedef struct +{ + long long samples; + int fifo_samples; + int filler[32]; /* For future use */ +} oss_count_t; + +#define SNDCTL_DSP_CURRENT_IPTR __SIOR ('P', 35, oss_count_t) +#define SNDCTL_DSP_CURRENT_OPTR __SIOR ('P', 36, oss_count_t) +#endif + +/* + **************************************************************************** + * Interface for selecting recording sources and playback output routings. + */ +#define SNDCTL_DSP_GET_RECSRC_NAMES __SIOR ('P', 37, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_RECSRC __SIOR ('P', 38, int) +#define SNDCTL_DSP_SET_RECSRC __SIOWR('P', 38, int) + +#define SNDCTL_DSP_GET_PLAYTGT_NAMES __SIOR ('P', 39, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_PLAYTGT __SIOR ('P', 40, int) +#define SNDCTL_DSP_SET_PLAYTGT __SIOWR('P', 40, int) +#define SNDCTL_DSP_GETRECVOL __SIOR ('P', 41, int) +#define SNDCTL_DSP_SETRECVOL __SIOWR('P', 41, int) + +/* + *************************************************************************** + * Some calls for setting the channel assignment with multi channel devices + * (see the manual for details). + */ +#ifndef OSS_NO_LONG_LONG +#define SNDCTL_DSP_GET_CHNORDER __SIOR ('P', 42, unsigned long long) +#define SNDCTL_DSP_SET_CHNORDER __SIOWR('P', 42, unsigned long long) +# define CHID_UNDEF 0 +# define CHID_L 1 +# define CHID_R 2 +# define CHID_C 3 +# define CHID_LFE 4 +# define CHID_LS 5 +# define CHID_RS 6 +# define CHID_LR 7 +# define CHID_RR 8 +#define CHNORDER_UNDEF 0x0000000000000000ULL +#define CHNORDER_NORMAL 0x0000000087654321ULL +#endif + +#define MAX_PEAK_CHANNELS 128 +typedef unsigned short oss_peaks_t[MAX_PEAK_CHANNELS]; +#define SNDCTL_DSP_GETIPEAKS __SIOR('P', 43, oss_peaks_t) +#define SNDCTL_DSP_GETOPEAKS __SIOR('P', 44, oss_peaks_t) + +#define SNDCTL_DSP_POLICY __SIOW('P', 45, int) /* See the manual */ + +/* + **************************************************************************** + * Few ioctl calls that are not official parts of OSS. They have been used + * by few freeware implementations of OSS. + */ +#define SNDCTL_DSP_GETCHANNELMASK __SIOWR('P', 64, int) +#define SNDCTL_DSP_BIND_CHANNEL __SIOWR('P', 65, int) +# define DSP_BIND_QUERY 0x00000000 +# define DSP_BIND_FRONT 0x00000001 +# define DSP_BIND_SURR 0x00000002 +# define DSP_BIND_CENTER_LFE 0x00000004 +# define DSP_BIND_HANDSET 0x00000008 +# define DSP_BIND_MIC 0x00000010 +# define DSP_BIND_MODEM1 0x00000020 +# define DSP_BIND_MODEM2 0x00000040 +# define DSP_BIND_I2S 0x00000080 +# define DSP_BIND_SPDIF 0x00000100 +# define DSP_BIND_REAR 0x00000200 + +#ifdef sun +/* Not part of OSS. Reserved for internal use by Solaris */ +#define X_SADA_GET_PLAYTGT_MASK __SIOR ('P', 66, int) +#define X_SADA_GET_PLAYTGT __SIOR ('P', 67, int) +#define X_SADA_SET_PLAYTGT __SIOWR('P', 68, int) +#endif + +#ifndef NO_LEGACY_MIXER +/* + **************************************************************************** + * IOCTL commands for the "legacy " /dev/mixer API (obsolete) + * + * Mixer controls + * + * There can be up to 20 different analog mixer channels. The + * SOUND_MIXER_NRDEVICES gives the currently supported maximum. + * The SOUND_MIXER_READ_DEVMASK returns a bitmask which tells + * the devices supported by the particular mixer. + * + * {!notice This "legacy" mixer API is obsolete. It has been superceded + * by a new one (see below). + */ + +#define SOUND_MIXER_NRDEVICES 28 +#define SOUND_MIXER_VOLUME 0 +#define SOUND_MIXER_BASS 1 +#define SOUND_MIXER_TREBLE 2 +#define SOUND_MIXER_SYNTH 3 +#define SOUND_MIXER_PCM 4 +#define SOUND_MIXER_SPEAKER 5 +#define SOUND_MIXER_LINE 6 +#define SOUND_MIXER_MIC 7 +#define SOUND_MIXER_CD 8 +#define SOUND_MIXER_IMIX 9 /* Recording monitor */ +#define SOUND_MIXER_ALTPCM 10 +#define SOUND_MIXER_RECLEV 11 /* Recording level */ +#define SOUND_MIXER_IGAIN 12 /* Input gain */ +#define SOUND_MIXER_OGAIN 13 /* Output gain */ +/* + * Some soundcards have three line level inputs (line, aux1 and aux2). + * Since each card manufacturer has assigned different meanings to + * these inputs, it's impractical to assign specific meanings + * (eg line, cd, synth etc.) to them. + */ +#define SOUND_MIXER_LINE1 14 /* Input source 1 (aux1) */ +#define SOUND_MIXER_LINE2 15 /* Input source 2 (aux2) */ +#define SOUND_MIXER_LINE3 16 /* Input source 3 (line) */ +#define SOUND_MIXER_DIGITAL1 17 /* Digital I/O 1 */ +#define SOUND_MIXER_DIGITAL2 18 /* Digital I/O 2 */ +#define SOUND_MIXER_DIGITAL3 19 /* Digital I/O 3 */ +#define SOUND_MIXER_PHONE 20 /* Phone */ +#define SOUND_MIXER_MONO 21 /* Mono Output */ +#define SOUND_MIXER_VIDEO 22 /* Video/TV (audio) in */ +#define SOUND_MIXER_RADIO 23 /* Radio in */ +#define SOUND_MIXER_DEPTH 24 /* Surround depth */ +#define SOUND_MIXER_REARVOL 25 /* Rear/Surround speaker vol */ +#define SOUND_MIXER_CENTERVOL 26 /* Center/LFE speaker vol */ +#define SOUND_MIXER_SIDEVOL 27 /* Side-Surround (8speaker) vol */ + +/* + * Warning: SOUND_MIXER_SURRVOL is an old name of SOUND_MIXER_SIDEVOL. + * They are both assigned to the same mixer control. Don't + * use both control names in the same program/driver. + */ +#define SOUND_MIXER_SURRVOL SOUND_MIXER_SIDEVOL + +/* Some on/off settings (SOUND_SPECIAL_MIN - SOUND_SPECIAL_MAX) */ +/* Not counted to SOUND_MIXER_NRDEVICES, but use the same number space */ +#define SOUND_ONOFF_MIN 28 +#define SOUND_ONOFF_MAX 30 + +/* Note! Number 31 cannot be used since the sign bit is reserved */ +#define SOUND_MIXER_NONE 31 + +/* + * The following unsupported macros are no longer functional. + * Use SOUND_MIXER_PRIVATE# macros in future. + */ +#define SOUND_MIXER_ENHANCE SOUND_MIXER_NONE +#define SOUND_MIXER_MUTE SOUND_MIXER_NONE +#define SOUND_MIXER_LOUD SOUND_MIXER_NONE + +#define SOUND_DEVICE_LABELS \ + {"Vol ", "Bass ", "Treble", "Synth", "Pcm ", "Speaker ", "Line ", \ + "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", \ + "Aux1", "Aux2", "Aux3", "Digital1", "Digital2", "Digital3", \ + "Phone", "Mono", "Video", "Radio", "Depth", \ + "Rear", "Center", "Side"} + +#define SOUND_DEVICE_NAMES \ + {"vol", "bass", "treble", "synth", "pcm", "speaker", "line", \ + "mic", "cd", "mix", "pcm2", "rec", "igain", "ogain", \ + "aux1", "aux2", "aux3", "dig1", "dig2", "dig3", \ + "phone", "mono", "video", "radio", "depth", \ + "rear", "center", "side"} + +/* Device bitmask identifiers */ + +#define SOUND_MIXER_RECSRC 0xff /* Arg contains a bit for each recording source */ +#define SOUND_MIXER_DEVMASK 0xfe /* Arg contains a bit for each supported device */ +#define SOUND_MIXER_RECMASK 0xfd /* Arg contains a bit for each supported recording source */ +#define SOUND_MIXER_CAPS 0xfc +# define SOUND_CAP_EXCL_INPUT 0x00000001 /* Only one recording source at a time */ +# define SOUND_CAP_NOLEGACY 0x00000004 /* For internal use only */ +# define SOUND_CAP_NORECSRC 0x00000008 +#define SOUND_MIXER_STEREODEVS 0xfb /* Mixer channels supporting stereo */ + +/* OSS/Free ONLY */ +#define SOUND_MIXER_OUTSRC 0xfa /* Arg contains a bit for each input source to output */ +#define SOUND_MIXER_OUTMASK 0xf9 /* Arg contains a bit for each supported input source to output */ +/* OSS/Free ONLY */ + +/* Device mask bits */ + +#define SOUND_MASK_VOLUME (1 << SOUND_MIXER_VOLUME) +#define SOUND_MASK_BASS (1 << SOUND_MIXER_BASS) +#define SOUND_MASK_TREBLE (1 << SOUND_MIXER_TREBLE) +#define SOUND_MASK_SYNTH (1 << SOUND_MIXER_SYNTH) +#define SOUND_MASK_PCM (1 << SOUND_MIXER_PCM) +#define SOUND_MASK_SPEAKER (1 << SOUND_MIXER_SPEAKER) +#define SOUND_MASK_LINE (1 << SOUND_MIXER_LINE) +#define SOUND_MASK_MIC (1 << SOUND_MIXER_MIC) +#define SOUND_MASK_CD (1 << SOUND_MIXER_CD) +#define SOUND_MASK_IMIX (1 << SOUND_MIXER_IMIX) +#define SOUND_MASK_ALTPCM (1 << SOUND_MIXER_ALTPCM) +#define SOUND_MASK_RECLEV (1 << SOUND_MIXER_RECLEV) +#define SOUND_MASK_IGAIN (1 << SOUND_MIXER_IGAIN) +#define SOUND_MASK_OGAIN (1 << SOUND_MIXER_OGAIN) +#define SOUND_MASK_LINE1 (1 << SOUND_MIXER_LINE1) +#define SOUND_MASK_LINE2 (1 << SOUND_MIXER_LINE2) +#define SOUND_MASK_LINE3 (1 << SOUND_MIXER_LINE3) +#define SOUND_MASK_DIGITAL1 (1 << SOUND_MIXER_DIGITAL1) +#define SOUND_MASK_DIGITAL2 (1 << SOUND_MIXER_DIGITAL2) +#define SOUND_MASK_DIGITAL3 (1 << SOUND_MIXER_DIGITAL3) +#define SOUND_MASK_MONO (1 << SOUND_MIXER_MONO) +#define SOUND_MASK_PHONE (1 << SOUND_MIXER_PHONE) +#define SOUND_MASK_RADIO (1 << SOUND_MIXER_RADIO) +#define SOUND_MASK_VIDEO (1 << SOUND_MIXER_VIDEO) +#define SOUND_MASK_DEPTH (1 << SOUND_MIXER_DEPTH) +#define SOUND_MASK_REARVOL (1 << SOUND_MIXER_REARVOL) +#define SOUND_MASK_CENTERVOL (1 << SOUND_MIXER_CENTERVOL) +#define SOUND_MASK_SIDEVOL (1 << SOUND_MIXER_SIDEVOL) + +/* Note! SOUND_MASK_SURRVOL is alias of SOUND_MASK_SIDEVOL */ +#define SOUND_MASK_SURRVOL (1 << SOUND_MIXER_SIDEVOL) + +/* Obsolete macros */ +#define SOUND_MASK_MUTE (1 << SOUND_MIXER_MUTE) +#define SOUND_MASK_ENHANCE (1 << SOUND_MIXER_ENHANCE) +#define SOUND_MASK_LOUD (1 << SOUND_MIXER_LOUD) + +#define MIXER_READ(dev) __SIOR('M', dev, int) +#define SOUND_MIXER_READ_VOLUME MIXER_READ(SOUND_MIXER_VOLUME) +#define SOUND_MIXER_READ_BASS MIXER_READ(SOUND_MIXER_BASS) +#define SOUND_MIXER_READ_TREBLE MIXER_READ(SOUND_MIXER_TREBLE) +#define SOUND_MIXER_READ_SYNTH MIXER_READ(SOUND_MIXER_SYNTH) +#define SOUND_MIXER_READ_PCM MIXER_READ(SOUND_MIXER_PCM) +#define SOUND_MIXER_READ_SPEAKER MIXER_READ(SOUND_MIXER_SPEAKER) +#define SOUND_MIXER_READ_LINE MIXER_READ(SOUND_MIXER_LINE) +#define SOUND_MIXER_READ_MIC MIXER_READ(SOUND_MIXER_MIC) +#define SOUND_MIXER_READ_CD MIXER_READ(SOUND_MIXER_CD) +#define SOUND_MIXER_READ_IMIX MIXER_READ(SOUND_MIXER_IMIX) +#define SOUND_MIXER_READ_ALTPCM MIXER_READ(SOUND_MIXER_ALTPCM) +#define SOUND_MIXER_READ_RECLEV MIXER_READ(SOUND_MIXER_RECLEV) +#define SOUND_MIXER_READ_IGAIN MIXER_READ(SOUND_MIXER_IGAIN) +#define SOUND_MIXER_READ_OGAIN MIXER_READ(SOUND_MIXER_OGAIN) +#define SOUND_MIXER_READ_LINE1 MIXER_READ(SOUND_MIXER_LINE1) +#define SOUND_MIXER_READ_LINE2 MIXER_READ(SOUND_MIXER_LINE2) +#define SOUND_MIXER_READ_LINE3 MIXER_READ(SOUND_MIXER_LINE3) + +/* Obsolete macros */ +#define SOUND_MIXER_READ_MUTE MIXER_READ(SOUND_MIXER_MUTE) +#define SOUND_MIXER_READ_ENHANCE MIXER_READ(SOUND_MIXER_ENHANCE) +#define SOUND_MIXER_READ_LOUD MIXER_READ(SOUND_MIXER_LOUD) + +#define SOUND_MIXER_READ_RECSRC MIXER_READ(SOUND_MIXER_RECSRC) +#define SOUND_MIXER_READ_DEVMASK MIXER_READ(SOUND_MIXER_DEVMASK) +#define SOUND_MIXER_READ_RECMASK MIXER_READ(SOUND_MIXER_RECMASK) +#define SOUND_MIXER_READ_STEREODEVS MIXER_READ(SOUND_MIXER_STEREODEVS) +#define SOUND_MIXER_READ_CAPS MIXER_READ(SOUND_MIXER_CAPS) + +#define MIXER_WRITE(dev) __SIOWR('M', dev, int) +#define SOUND_MIXER_WRITE_VOLUME MIXER_WRITE(SOUND_MIXER_VOLUME) +#define SOUND_MIXER_WRITE_BASS MIXER_WRITE(SOUND_MIXER_BASS) +#define SOUND_MIXER_WRITE_TREBLE MIXER_WRITE(SOUND_MIXER_TREBLE) +#define SOUND_MIXER_WRITE_SYNTH MIXER_WRITE(SOUND_MIXER_SYNTH) +#define SOUND_MIXER_WRITE_PCM MIXER_WRITE(SOUND_MIXER_PCM) +#define SOUND_MIXER_WRITE_SPEAKER MIXER_WRITE(SOUND_MIXER_SPEAKER) +#define SOUND_MIXER_WRITE_LINE MIXER_WRITE(SOUND_MIXER_LINE) +#define SOUND_MIXER_WRITE_MIC MIXER_WRITE(SOUND_MIXER_MIC) +#define SOUND_MIXER_WRITE_CD MIXER_WRITE(SOUND_MIXER_CD) +#define SOUND_MIXER_WRITE_IMIX MIXER_WRITE(SOUND_MIXER_IMIX) +#define SOUND_MIXER_WRITE_ALTPCM MIXER_WRITE(SOUND_MIXER_ALTPCM) +#define SOUND_MIXER_WRITE_RECLEV MIXER_WRITE(SOUND_MIXER_RECLEV) +#define SOUND_MIXER_WRITE_IGAIN MIXER_WRITE(SOUND_MIXER_IGAIN) +#define SOUND_MIXER_WRITE_OGAIN MIXER_WRITE(SOUND_MIXER_OGAIN) +#define SOUND_MIXER_WRITE_LINE1 MIXER_WRITE(SOUND_MIXER_LINE1) +#define SOUND_MIXER_WRITE_LINE2 MIXER_WRITE(SOUND_MIXER_LINE2) +#define SOUND_MIXER_WRITE_LINE3 MIXER_WRITE(SOUND_MIXER_LINE3) + +/* Obsolete macros */ +#define SOUND_MIXER_WRITE_MUTE MIXER_WRITE(SOUND_MIXER_MUTE) +#define SOUND_MIXER_WRITE_ENHANCE MIXER_WRITE(SOUND_MIXER_ENHANCE) +#define SOUND_MIXER_WRITE_LOUD MIXER_WRITE(SOUND_MIXER_LOUD) + +#define SOUND_MIXER_WRITE_RECSRC MIXER_WRITE(SOUND_MIXER_RECSRC) + +typedef struct mixer_info /* OBSOLETE */ +{ + char id[16]; + char name[32]; + int modify_counter; + int card_number; + int port_number; + char handle[32]; +} mixer_info; + +/* SOUND_MIXER_INFO is obsolete - use SNDCTL_MIXERINFO instead */ +#define SOUND_MIXER_INFO __SIOR ('M', 101, mixer_info) + +/* + * Two ioctls for special souncard function (OSS/Free only) + */ +#define SOUND_MIXER_AGC _SIOWR('M', 103, int) +#define SOUND_MIXER_3DSE _SIOWR('M', 104, int) +/* + * The SOUND_MIXER_PRIVATE# commands can be redefined by low level drivers. + * These features can be used when accessing device specific features. + */ +#define SOUND_MIXER_PRIVATE1 __SIOWR('M', 111, int) +#define SOUND_MIXER_PRIVATE2 __SIOWR('M', 112, int) +#define SOUND_MIXER_PRIVATE3 __SIOWR('M', 113, int) +#define SOUND_MIXER_PRIVATE4 __SIOWR('M', 114, int) +#define SOUND_MIXER_PRIVATE5 __SIOWR('M', 115, int) + +/* The following two controls were never implemented and they should not be used. */ +#define SOUND_MIXER_READ_MAINVOL __SIOR ('M', 116, int) +#define SOUND_MIXER_WRITE_MAINVOL __SIOWR('M', 116, int) + +/* + * SOUND_MIXER_GETLEVELS and SOUND_MIXER_SETLEVELS calls can be used + * for querying current mixer settings from the driver and for loading + * default volume settings _prior_ activating the mixer (loading + * doesn't affect current state of the mixer hardware). These calls + * are for internal use by the driver software only. + */ + +typedef struct mixer_vol_table +{ + int num; /* Index to volume table */ + char name[32]; + int levels[32]; +} mixer_vol_table; + +#define SOUND_MIXER_GETLEVELS __SIOWR('M', 116, mixer_vol_table) +#define SOUND_MIXER_SETLEVELS __SIOWR('M', 117, mixer_vol_table) + +#define OSS_GETVERSION __SIOR ('M', 118, int) + +/* + * Calls to set/get the recording gain for the currently active + * recording source. These calls automatically map to the right control. + * Note that these calls are not supported by all drivers. In this case + * the call will return -1 with errno set to EINVAL + * + * The _MONGAIN work in similar way but set/get the monitoring gain for + * the currently selected recording source. + */ +#define SOUND_MIXER_READ_RECGAIN __SIOR ('M', 119, int) +#define SOUND_MIXER_WRITE_RECGAIN __SIOWR('M', 119, int) +#define SOUND_MIXER_READ_MONGAIN __SIOR ('M', 120, int) +#define SOUND_MIXER_WRITE_MONGAIN __SIOWR('M', 120, int) + +/* The following call is for driver development time purposes. It's not + * present in any released drivers. + */ +typedef unsigned char oss_reserved_t[512]; +#define SOUND_MIXER_RESERVED __SIOWR('M', 121, oss_reserved_t) +#endif /* ifndef NO_LEGACY_MIXER */ + +/* + ************************************************************************* + * The "new" mixer API of OSS 4.0 and later. + * + * This improved mixer API makes it possible to access every possible feature + * of every possible device. However you should read the mixer programming + * section of the OSS API Developer's Manual. There is no chance that you + * could use this interface correctly just by examining this header. + */ + +typedef struct oss_sysinfo +{ + char product[32]; /* For example OSS/Free, OSS/Linux or OSS/Solaris */ + char version[32]; /* For example 4.0a */ + int versionnum; /* See OSS_GETVERSION */ + char options[128]; /* Reserved */ + + int numaudios; /* # of audio/dsp devices */ + int openedaudio[8]; /* Bit mask telling which audio devices are busy */ + + int numsynths; /* # of availavle synth devices */ + int nummidis; /* # of available MIDI ports */ + int numtimers; /* # of available timer devices */ + int nummixers; /* # of mixer devices */ + + int openedmidi[8]; /* Bit mask telling which midi devices are busy */ + int numcards; /* Number of sound cards in the system */ + int numaudioengines; /* Number of audio engines in the system */ + char license[16]; /* For example "GPL" or "CDDL" */ + int filler[236]; /* For future expansion (set to -1) */ +} oss_sysinfo; + +typedef struct oss_mixext +{ + int dev; /* Mixer device number */ + int ctrl; /* Controller number */ + int type; /* Entry type */ +# define MIXT_DEVROOT 0 /* Device root entry */ +# define MIXT_GROUP 1 /* Controller group */ +# define MIXT_ONOFF 2 /* OFF (0) or ON (1) */ +# define MIXT_ENUM 3 /* Enumerated (0 to maxvalue) */ +# define MIXT_MONOSLIDER 4 /* Mono slider (0 to 255) */ +# define MIXT_STEREOSLIDER 5 /* Stereo slider (dual 0 to 255) */ +# define MIXT_MESSAGE 6 /* (Readable) textual message */ +# define MIXT_MONOVU 7 /* VU meter value (mono) */ +# define MIXT_STEREOVU 8 /* VU meter value (stereo) */ +# define MIXT_MONOPEAK 9 /* VU meter peak value (mono) */ +# define MIXT_STEREOPEAK 10 /* VU meter peak value (stereo) */ +# define MIXT_RADIOGROUP 11 /* Radio button group */ +# define MIXT_MARKER 12 /* Separator between normal and extension entries */ +# define MIXT_VALUE 13 /* Decimal value entry */ +# define MIXT_HEXVALUE 14 /* Hexadecimal value entry */ +# define MIXT_MONODB 15 /* OBSOLETE */ +# define MIXT_STEREODB 16 /* OBSOLETE */ +# define MIXT_SLIDER 17 /* Slider (mono) with full (31 bit) postitive integer range */ +# define MIXT_3D 18 + +/* + * Sliders with range expanded to 15 bits per channel (0-32767) + */ +# define MIXT_MONOSLIDER16 19 +# define MIXT_STEREOSLIDER16 20 +# define MIXT_MUTE 21 /* Mute=1, unmute=0 */ + + /**************************************************************/ + + /* Possible value range (minvalue to maxvalue) */ + /* Note that maxvalue may also be smaller than minvalue */ + int maxvalue; + int minvalue; + + int flags; +# define MIXF_READABLE 0x00000001 /* Has readable value */ +# define MIXF_WRITEABLE 0x00000002 /* Has writeable value */ +# define MIXF_POLL 0x00000004 /* May change itself */ +# define MIXF_HZ 0x00000008 /* Herz scale */ +# define MIXF_STRING 0x00000010 /* Use dynamic extensions for value */ +# define MIXF_DYNAMIC 0x00000010 /* Supports dynamic extensions */ +# define MIXF_OKFAIL 0x00000020 /* Interpret value as 1=OK, 0=FAIL */ +# define MIXF_FLAT 0x00000040 /* Flat vertical space requirements */ +# define MIXF_LEGACY 0x00000080 /* Legacy mixer control group */ +# define MIXF_CENTIBEL 0x00000100 /* Centibel (0.1 dB) step size */ +# define MIXF_DECIBEL 0x00000200 /* Step size of 1 dB */ +# define MIXF_MAINVOL 0x00000400 /* Main volume control */ +# define MIXF_PCMVOL 0x00000800 /* PCM output volume control */ +# define MIXF_RECVOL 0x00001000 /* PCM recording volume control */ +# define MIXF_MONVOL 0x00002000 /* Input->output monitor volume */ +# define MIXF_WIDE 0x00004000 /* Enum control has wide labels */ +# define MIXF_DESCR 0x00008000 /* Description (tooltip) available */ + char id[16]; /* Mnemonic ID (mainly for internal use) */ + int parent; /* Entry# of parent (group) node (-1 if root) */ + + int dummy; /* Internal use */ + + int timestamp; + + char data[64]; /* Misc data (entry type dependent) */ + unsigned char enum_present[32]; /* Mask of allowed enum values */ + int control_no; /* SOUND_MIXER_VOLUME..SOUND_MIXER_MIDI */ + /* (-1 means not indicated) */ + +/* + * The desc field is reserved for internal purposes of OSS. It should not be + * used by applications. + */ + unsigned int desc; +#define MIXEXT_SCOPE_MASK 0x0000003f +#define MIXEXT_SCOPE_OTHER 0x00000000 +#define MIXEXT_SCOPE_INPUT 0x00000001 +#define MIXEXT_SCOPE_OUTPUT 0x00000002 +#define MIXEXT_SCOPE_MONITOR 0x00000003 +#define MIXEXT_SCOPE_RECSWITCH 0x00000004 + + char extname[32]; + int update_counter; + int rgbcolor; /* 0 means default color (not black) . Otherwise 24 bit RGB color */ + int filler[6]; +} oss_mixext; + +/* + * Recommended colors to be used in the rgbcolor field. These match the + * colors used as the audio jack colors in HD audio motherboards. + */ +#define OSS_RGB_BLUE 0x7aabde // Light blue +#define OSS_RGB_GREEN 0xb3c98c // Lime green +#define OSS_RGB_PINK 0xe88c99 +#define OSS_RGB_GRAY 0xd1ccc4 +#define OSS_RGB_BLACK 0x2b2926 // Light black +#define OSS_RGB_ORANGE 0xe89e47 +#define OSS_RGB_RED 0xff0000 +#define OSS_RGB_YELLOW 0xffff00 +#define OSS_RGB_PURPLE 0x800080 +#define OSS_RGB_WHITE 0xf8f8ff + +typedef struct oss_mixext_root +{ + char id[16]; + char name[48]; +} oss_mixext_root; + +typedef struct oss_mixer_value +{ + int dev; + int ctrl; + int value; + int flags; /* Reserved for future use. Initialize to 0 */ + int timestamp; /* Must be set to oss_mixext.timestamp */ + int filler[8]; /* Reserved for future use. Initialize to 0 */ +} oss_mixer_value; + +#define OSS_ENUM_MAXVALUE 255 +#define OSS_ENUM_STRINGSIZE 3000 +typedef struct oss_mixer_enuminfo +{ + int dev; + int ctrl; + int nvalues; + int version; /* Read the manual */ + short strindex[OSS_ENUM_MAXVALUE]; + char strings[OSS_ENUM_STRINGSIZE]; +} oss_mixer_enuminfo; + +#define OPEN_READ PCM_ENABLE_INPUT +#define OPEN_WRITE PCM_ENABLE_OUTPUT +#define OPEN_READWRITE (OPEN_READ|OPEN_WRITE) + +typedef struct oss_audioinfo +{ + int dev; /* Audio device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + int caps; /* PCM_CAP_INPUT, PCM_CAP_OUTPUT */ + int iformats, oformats; + int magic; /* Reserved for internal use */ + char cmd[64]; /* Command using the device (if known) */ + int card_number; + int port_number; + int mixer_dev; + int legacy_device; /* Obsolete field. Replaced by devnode */ + int enabled; /* 1=enabled, 0=device not ready at this moment */ + int flags; /* For internal use only - no practical meaning */ + int min_rate, max_rate; /* Sample rate limits */ + int min_channels, max_channels; /* Number of channels supported */ + int binding; /* DSP_BIND_FRONT, etc. 0 means undefined */ + int rate_source; + char handle[32]; +#define OSS_MAX_SAMPLE_RATES 20 /* Cannot be changed */ + unsigned int nrates, rates[OSS_MAX_SAMPLE_RATES]; /* Please read the manual before using these */ + oss_longname_t song_name; /* Song name (if given) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + oss_devnode_t devnode; /* Device special file name (absolute path) */ + int next_play_engine; /* Read the documentation for more info */ + int next_rec_engine; /* Read the documentation for more info */ + int filler[184]; +} oss_audioinfo; + +typedef struct oss_mixerinfo +{ + int dev; + char id[16]; + char name[32]; + int modify_counter; + int card_number; + int port_number; + char handle[32]; + int magic; /* Reserved */ + int enabled; /* Reserved */ + int caps; +#define MIXER_CAP_VIRTUAL 0x00000001 +#define MIXER_CAP_LAYOUT_B 0x00000002 /* For internal use only */ +#define MIXER_CAP_NARROW 0x00000004 /* Conserve horiz space */ + int flags; /* Reserved */ + int nrext; + /* + * The priority field can be used to select the default (motherboard) + * mixer device. The mixer with the highest priority is the + * most preferred one. -2 or less means that this device cannot be used + * as the default mixer. + */ + int priority; + oss_devnode_t devnode; /* Device special file name (absolute path) */ + int legacy_device; + int filler[245]; /* Reserved */ +} oss_mixerinfo; + +typedef struct oss_midi_info +{ + int dev; /* Midi device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + char cmd[64]; /* Command using the device (if known) */ + int caps; +#define MIDI_CAP_MPU401 0x00000001 /**** OBSOLETE ****/ +#define MIDI_CAP_INPUT 0x00000002 +#define MIDI_CAP_OUTPUT 0x00000004 +#define MIDI_CAP_INOUT (MIDI_CAP_INPUT|MIDI_CAP_OUTPUT) +#define MIDI_CAP_VIRTUAL 0x00000008 /* Pseudo device */ +#define MIDI_CAP_MTCINPUT 0x00000010 /* Supports SNDCTL_MIDI_MTCINPUT */ +#define MIDI_CAP_CLIENT 0x00000020 /* Virtual client side device */ +#define MIDI_CAP_SERVER 0x00000040 /* Virtual server side device */ +#define MIDI_CAP_INTERNAL 0x00000080 /* Internal (synth) device */ +#define MIDI_CAP_EXTERNAL 0x00000100 /* external (MIDI port) device */ +#define MIDI_CAP_PTOP 0x00000200 /* Point to point link to one device */ +#define MIDI_CAP_MTC 0x00000400 /* MTC/SMPTE (control) device */ + int magic; /* Reserved for internal use */ + int card_number; + int port_number; + int enabled; /* 1=enabled, 0=device not ready at this moment */ + int flags; /* For internal use only - no practical meaning */ + char handle[32]; + oss_longname_t song_name; /* Song name (if known) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + oss_devnode_t devnode; /* Device special file name (absolute path) */ + int legacy_device; /* Legacy device mapping */ + int filler[235]; +} oss_midi_info; + +typedef struct oss_card_info +{ + int card; + char shortname[16]; + char longname[128]; + int flags; + char hw_info[400]; + int intr_count, ack_count; + int filler[154]; +} oss_card_info; + +#define SNDCTL_SYSINFO __SIOR ('X', 1, oss_sysinfo) +#define OSS_SYSINFO SNDCTL_SYSINFO /* Old name */ + +#define SNDCTL_MIX_NRMIX __SIOR ('X', 2, int) +#define SNDCTL_MIX_NREXT __SIOWR('X', 3, int) +#define SNDCTL_MIX_EXTINFO __SIOWR('X', 4, oss_mixext) +#define SNDCTL_MIX_READ __SIOWR('X', 5, oss_mixer_value) +#define SNDCTL_MIX_WRITE __SIOWR('X', 6, oss_mixer_value) + +#define SNDCTL_AUDIOINFO __SIOWR('X', 7, oss_audioinfo) +#define SNDCTL_MIX_ENUMINFO __SIOWR('X', 8, oss_mixer_enuminfo) +#define SNDCTL_MIDIINFO __SIOWR('X', 9, oss_midi_info) +#define SNDCTL_MIXERINFO __SIOWR('X',10, oss_mixerinfo) +#define SNDCTL_CARDINFO __SIOWR('X',11, oss_card_info) +#define SNDCTL_ENGINEINFO __SIOWR('X',12, oss_audioinfo) +#define SNDCTL_AUDIOINFO_EX __SIOWR('X',13, oss_audioinfo) + +#define SNDCTL_MIX_DESCRIPTION __SIOWR('X',14, oss_mixer_enuminfo) + +/* ioctl codes 'X', 200-255 are reserved for internal use */ + +/* + * Few more "globally" available ioctl calls. + */ +#define SNDCTL_SETSONG __SIOW ('Y', 2, oss_longname_t) +#define SNDCTL_GETSONG __SIOR ('Y', 2, oss_longname_t) +#define SNDCTL_SETNAME __SIOW ('Y', 3, oss_longname_t) +#define SNDCTL_SETLABEL __SIOW ('Y', 4, oss_label_t) +#define SNDCTL_GETLABEL __SIOR ('Y', 4, oss_label_t) +/* + * The "new" mixer API definitions end here. + *************************************** + */ + +/* + ********************************************************* + * Few routines that are included in -lOSSlib + * + * At this moment this interface is not used. OSSlib contains just + * stubs that call the related system calls directly. + */ +#ifdef OSSLIB +extern int osslib_open (const char *path, int flags, int dummy); +extern void osslib_close (int fd); +extern int osslib_write (int fd, const void *buf, int count); +extern int osslib_read (int fd, void *buf, int count); +extern int osslib_ioctl (int fd, unsigned int request, void *arg); +#else +# define osslib_open open +# define osslib_close close +# define osslib_write write +# define osslib_read read +# define osslib_ioctl ioctl +#endif + +#if 1 +#define SNDCTL_DSP_NONBLOCK __SIO ('P',14) /* Obsolete. Not supported any more */ +#endif + +#if 1 +/* + * Some obsolete macros that are not part of Open Sound System API. + */ +#define SOUND_PCM_READ_RATE SOUND_PCM_READ_RATE_is_obsolete +#define SOUND_PCM_READ_BITS SOUND_PCM_READ_BITS_is_obsolete +#define SOUND_PCM_READ_CHANNELS SOUND_PCM_READ_CHANNELS_is_obsolete +#define SOUND_PCM_WRITE_RATE SOUND_PCM_WRITE_RATE_is_obsolet_use_SNDCTL_DSP_SPEED_instead +#define SOUND_PCM_WRITE_CHANNELS SOUND_PCM_WRITE_CHANNELS_is_obsolete_use_SNDCTL_DSP_CHANNELS_instead +#define SOUND_PCM_WRITE_BITS SOUND_PCM_WRITE_BITS_is_obsolete_use_SNDCTL_DSP_SETFMT_instead +#define SOUND_PCM_POST SOUND_PCM_POST_is_obsolete_use_SNDCTL_DSP_POST_instead +#define SOUND_PCM_RESET SOUND_PCM_RESET_is_obsolete_use_SNDCTL_DSP_HALT_instead +#define SOUND_PCM_SYNC SOUND_PCM_SYNC_is_obsolete_use_SNDCTL_DSP_SYNC_instead +#define SOUND_PCM_SUBDIVIDE SOUND_PCM_SUBDIVIDE_is_obsolete_use_SNDCTL_DSP_SUBDIVIDE_instead +#define SOUND_PCM_SETFRAGMENT SOUND_PCM_SETFRAGMENT_is_obsolete_use_SNDCTL_DSP_SETFRAGMENT_instead +#define SOUND_PCM_GETFMTS SOUND_PCM_GETFMTS_is_obsolete_use_SNDCTL_DSP_GETFMTS_instead +#define SOUND_PCM_SETFMT SOUND_PCM_SETFMT_is_obsolete_use_SNDCTL_DSP_SETFMT_instead +#define SOUND_PCM_GETOSPACE SOUND_PCM_GETOSPACE_is_obsolete_use_SNDCTL_DSP_GETOSPACE_instead +#define SOUND_PCM_GETISPACE SOUND_PCM_GETISPACE_is_obsolete_use_SNDCTL_DSP_GETISPACE_instead +#define SOUND_PCM_NONBLOCK SOUND_PCM_NONBLOCK_is_obsolete_use_SNDCTL_DSP_NONBLOCK_instead +#define SOUND_PCM_GETCAPS SOUND_PCM_GETCAPS_is_obsolete_use_SNDCTL_DSP_GETCAPS_instead +#define SOUND_PCM_GETTRIGGER SOUND_PCM_GETTRIGGER_is_obsolete_use_SNDCTL_DSP_GETTRIGGER_instead +#define SOUND_PCM_SETTRIGGER SOUND_PCM_SETTRIGGER_is_obsolete_use_SNDCTL_DSP_SETTRIGGER_instead +#define SOUND_PCM_SETSYNCRO SOUND_PCM_SETSYNCRO_is_obsolete_use_SNDCTL_DSP_SETSYNCRO_instead +#define SOUND_PCM_GETIPTR SOUND_PCM_GETIPTR_is_obsolete_use_SNDCTL_DSP_GETIPTR_instead +#define SOUND_PCM_GETOPTR SOUND_PCM_GETOPTR_is_obsolete_use_SNDCTL_DSP_GETOPTR_instead +#define SOUND_PCM_MAPINBUF SOUND_PCM_MAPINBUF_is_obsolete_use_SNDCTL_DSP_MAPINBUF_instead +#define SOUND_PCM_MAPOUTBUF SOUND_PCM_MAPOUTBUF_is_obsolete_use_SNDCTL_DSP_MAPOUTBUF_instead +#endif + +#endif diff --git a/sys/oss4/oss4-source.c b/sys/oss4/oss4-source.c new file mode 100644 index 0000000000..1cf2328f94 --- /dev/null +++ b/sys/oss4/oss4-source.c @@ -0,0 +1,1004 @@ +/* GStreamer OSS4 audio source + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-oss4src + * @short_description: record sound from your sound card using OSS4 + * + * + * + * This element lets you record sound using the Open Sound System (OSS) + * version 4. + * + * Example pipelines + * + * + * gst-launch -v oss4src ! queue ! audioconvert ! vorbisenc ! oggmux ! filesink location=mymusic.ogg + * + * will record sound from your sound card using OSS4 and encode it to an + * Ogg/Vorbis file (this will only work if your mixer settings are right + * and the right inputs areenabled etc.) + * + * + * + * Since: 0.10.7 + */ + +/* FIXME: make sure we're not doing ioctls from the app thread (e.g. via the + * mixer interface) while recording */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define NO_LEGACY_MIXER +#include "oss4-audio.h" +#include "oss4-source.h" +#include "oss4-property-probe.h" +#include "oss4-soundcard.h" + +#define GST_OSS4_SOURCE_IS_OPEN(src) (GST_OSS4_SOURCE(src)->fd != -1) + +GST_DEBUG_CATEGORY_EXTERN (oss4src_debug); +#define GST_CAT_DEFAULT oss4src_debug + +#define DEFAULT_DEVICE NULL +#define DEFAULT_DEVICE_NAME NULL + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DEVICE_NAME +}; + +static void gst_oss4_source_init_interfaces (GType type); + +GST_BOILERPLATE_FULL (GstOss4Source, gst_oss4_source, GstAudioSrc, + GST_TYPE_AUDIO_SRC, gst_oss4_source_init_interfaces); + +static void gst_oss4_source_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_oss4_source_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +static void gst_oss4_source_dispose (GObject * object); +static void gst_oss4_source_finalize (GstOss4Source * osssrc); + +static GstCaps *gst_oss4_source_getcaps (GstBaseSrc * bsrc); + +static gboolean gst_oss4_source_open (GstAudioSrc * asrc, + gboolean silent_errors); +static gboolean gst_oss4_source_open_func (GstAudioSrc * asrc); +static gboolean gst_oss4_source_close (GstAudioSrc * asrc); +static gboolean gst_oss4_source_prepare (GstAudioSrc * asrc, + GstRingBufferSpec * spec); +static gboolean gst_oss4_source_unprepare (GstAudioSrc * asrc); +static guint gst_oss4_source_read (GstAudioSrc * asrc, gpointer data, + guint length); +static guint gst_oss4_source_delay (GstAudioSrc * asrc); +static void gst_oss4_source_reset (GstAudioSrc * asrc); + +static void +gst_oss4_source_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GstPadTemplate *templ; + + gst_element_class_set_details_simple (element_class, + "OSS v4 Audio Source", "Source/Audio", + "Capture from a sound card via OSS version 4", + "Tim-Philipp Müller "); + + templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_oss4_audio_get_template_caps ()); + gst_element_class_add_pad_template (element_class, templ); +} +static void +gst_oss4_source_class_init (GstOss4SourceClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSrcClass *gstbasesrc_class; + GstBaseAudioSrcClass *gstbaseaudiosrc_class; + GstAudioSrcClass *gstaudiosrc_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesrc_class = (GstBaseSrcClass *) klass; + gstbaseaudiosrc_class = (GstBaseAudioSrcClass *) klass; + gstaudiosrc_class = (GstAudioSrcClass *) klass; + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_oss4_source_dispose); + gobject_class->finalize = + (GObjectFinalizeFunc) GST_DEBUG_FUNCPTR (gst_oss4_source_finalize); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_oss4_source_get_property); + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_oss4_source_set_property); + + gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_oss4_source_getcaps); + + gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_oss4_source_open_func); + gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_oss4_source_prepare); + gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_oss4_source_unprepare); + gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_oss4_source_close); + gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_oss4_source_read); + gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_oss4_source_delay); + gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_oss4_source_reset); + + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Device", + "OSS4 device (e.g. /dev/oss/hdaudio0/pcm0 or /dev/dspN) " + "(NULL = use first available device)", + DEFAULT_DEVICE, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Device name", + "Human-readable name of the sound device", DEFAULT_DEVICE_NAME, + G_PARAM_READABLE)); +} + +static void +gst_oss4_source_init (GstOss4Source * osssrc, GstOss4SourceClass * g_class) +{ + const gchar *device; + + device = g_getenv ("AUDIODEV"); + if (device == NULL) + device = DEFAULT_DEVICE; + + osssrc->fd = -1; + osssrc->device = g_strdup (device); + osssrc->device_name = g_strdup (DEFAULT_DEVICE_NAME); + osssrc->device_name = NULL; +} + +static void +gst_oss4_source_finalize (GstOss4Source * oss) +{ + g_free (oss->device); + oss->device = NULL; + + g_list_free (oss->property_probe_list); + oss->property_probe_list = NULL; + + G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (oss)); +} + +static void +gst_oss4_source_dispose (GObject * object) +{ + GstOss4Source *oss = GST_OSS4_SOURCE (object); + + if (oss->probed_caps) { + gst_caps_unref (oss->probed_caps); + oss->probed_caps = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_oss4_source_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (oss); + if (oss->fd == -1) { + g_free (oss->device); + oss->device = g_value_dup_string (value); + g_free (oss->device_name); + oss->device_name = NULL; + } else { + g_warning ("%s: can't change \"device\" property while audio source " + "is open", GST_OBJECT_NAME (oss)); + } + GST_OBJECT_UNLOCK (oss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_oss4_source_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (oss); + g_value_set_string (value, oss->device); + GST_OBJECT_UNLOCK (oss); + break; + case PROP_DEVICE_NAME: + GST_OBJECT_LOCK (oss); + /* If device is set, try to retrieve the name even if we're not open */ + if (oss->fd == -1 && oss->device != NULL) { + if (gst_oss4_source_open (GST_AUDIO_SRC (oss), TRUE)) { + g_value_set_string (value, oss->device_name); + gst_oss4_source_close (GST_AUDIO_SRC (oss)); + } else { + g_value_set_string (value, NULL); + } + } else { + g_value_set_string (value, oss->device_name); + } + GST_OBJECT_UNLOCK (oss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstCaps * +gst_oss4_source_getcaps (GstBaseSrc * bsrc) +{ + GstOss4Source *oss; + GstCaps *caps; + + oss = GST_OSS4_SOURCE (bsrc); + + if (oss->fd == -1) { + caps = gst_caps_copy (gst_oss4_audio_get_template_caps ()); + } else if (oss->probed_caps) { + caps = gst_caps_copy (oss->probed_caps); + } else { + caps = gst_oss4_audio_probe_caps (GST_OBJECT (oss), oss->fd); + if (caps != NULL && !gst_caps_is_empty (caps)) { + oss->probed_caps = gst_caps_copy (caps); + } + } + + return caps; +} + +/* note: we must not take the object lock here unless we fix up get_property */ +static gboolean +gst_oss4_source_open (GstAudioSrc * asrc, gboolean silent_errors) +{ + GstOss4Source *oss; + gchar *device; + int mode; + + oss = GST_OSS4_SOURCE (asrc); + + if (oss->device) + device = g_strdup (oss->device); + else + device = gst_oss4_audio_find_device (GST_OBJECT_CAST (oss)); + + /* desperate times, desperate measures */ + if (device == NULL) + device = g_strdup ("/dev/dsp0"); + + GST_INFO_OBJECT (oss, "Trying to open OSS4 device '%s'", device); + + /* we open in non-blocking mode even if we don't really want to do writes + * non-blocking because we can't be sure that this is really a genuine + * OSS4 device with well-behaved drivers etc. We really don't want to + * hang forever under any circumstances. */ + oss->fd = open (device, O_RDONLY | O_NONBLOCK, 0); + if (oss->fd == -1) { + switch (errno) { + case EBUSY: + goto busy; + case EACCES: + goto no_permission; + default: + goto open_failed; + } + } + + GST_INFO_OBJECT (oss, "Opened device"); + + /* Make sure it's OSS4. If it's old OSS, let osssink handle it */ + if (!gst_oss4_audio_check_version (GST_OBJECT_CAST (oss), oss->fd)) + goto legacy_oss; + + /* now remove the non-blocking flag. */ + mode = fcntl (oss->fd, F_GETFL); + mode &= ~O_NONBLOCK; + if (fcntl (oss->fd, F_SETFL, mode) < 0) { + /* some drivers do no support unsetting the non-blocking flag, try to + * close/open the device then. This is racy but we error out properly. */ + GST_WARNING_OBJECT (oss, "failed to unset O_NONBLOCK (buggy driver?), " + "will try to re-open device now"); + gst_oss4_source_close (asrc); + if ((oss->fd = open (device, O_RDONLY, 0)) == -1) + goto non_block; + } + + oss->open_device = device; + + /* not using ENGINEINFO here because it sometimes returns a different and + * less useful name than AUDIOINFO for the same device */ + if (!gst_oss4_property_probe_find_device_name (GST_OBJECT (oss), oss->fd, + oss->open_device, &oss->device_name)) { + oss->device_name = NULL; + } + + return TRUE; + + /* ERRORS */ +busy: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, BUSY, + (_("Could not open audio device for playback. " + "Device is being used by another application.")), (NULL)); + } + g_free (device); + return FALSE; + } +no_permission: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_READ, + (_("Could not open audio device for playback." + "You don't have permission to open the device.")), + GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +open_failed: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_READ, + (_("Could not open audio device for playback.")), GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +legacy_oss: + { + gst_oss4_source_close (asrc); + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_READ, + (_("Could not open audio device for playback." + "This version of the Open Sound System is not supported by this " + "element.")), ("Try the 'osssink' element instead")); + } + g_free (device); + return FALSE; + } +non_block: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, SETTINGS, (NULL), + ("Unable to set device %s into non-blocking mode: %s", + oss->device, g_strerror (errno))); + } + g_free (device); + return FALSE; + } +} + +static gboolean +gst_oss4_source_open_func (GstAudioSrc * asrc) +{ + return gst_oss4_source_open (asrc, FALSE); +} + +static void +gst_oss4_source_free_mixer_tracks (GstOss4Source * oss) +{ + g_list_foreach (oss->tracks, (GFunc) g_object_unref, NULL); + g_list_free (oss->tracks); + oss->tracks = NULL; +} + +static gboolean +gst_oss4_source_close (GstAudioSrc * asrc) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (asrc); + + if (oss->fd != -1) { + GST_DEBUG_OBJECT (oss, "closing device"); + close (oss->fd); + oss->fd = -1; + } + + oss->bytes_per_sample = 0; + + gst_caps_replace (&oss->probed_caps, NULL); + + g_free (oss->open_device); + oss->open_device = NULL; + + g_free (oss->device_name); + oss->device_name = NULL; + + gst_oss4_source_free_mixer_tracks (oss); + + return TRUE; +} + +static gboolean +gst_oss4_source_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (asrc); + + if (!gst_oss4_audio_set_format (GST_OBJECT_CAST (oss), oss->fd, spec)) { + GST_WARNING_OBJECT (oss, "Couldn't set requested format %" GST_PTR_FORMAT, + spec->caps); + return FALSE; + } + + oss->bytes_per_sample = spec->bytes_per_sample; + return TRUE; +} + +static gboolean +gst_oss4_source_unprepare (GstAudioSrc * asrc) +{ + /* could do a SNDCTL_DSP_HALT, but the OSS manual recommends a close/open, + * since HALT won't properly reset some devices, apparently */ + + if (!gst_oss4_source_close (asrc)) + goto couldnt_close; + + if (!gst_oss4_source_open_func (asrc)) + goto couldnt_reopen; + + return TRUE; + + /* ERRORS */ +couldnt_close: + { + GST_DEBUG_OBJECT (asrc, "Couldn't close the audio device"); + return FALSE; + } +couldnt_reopen: + { + GST_DEBUG_OBJECT (asrc, "Couldn't reopen the audio device"); + return FALSE; + } +} + +static guint +gst_oss4_source_read (GstAudioSrc * asrc, gpointer data, guint length) +{ + GstOss4Source *oss; + int n; + + oss = GST_OSS4_SOURCE_CAST (asrc); + + n = read (oss->fd, data, length); + GST_LOG_OBJECT (asrc, "%u bytes, %u samples", n, n / oss->bytes_per_sample); + + if (G_UNLIKELY (n < 0)) { + switch (errno) { + case ENOTSUP: + case EACCES:{ + /* This is the most likely cause, I think */ + GST_ELEMENT_ERROR (asrc, RESOURCE, READ, + (_("Recording is not supported by this audio device.")), + ("read: %s (device: %s) (maybe this is an output-only device?)", + g_strerror (errno), oss->open_device)); + break; + } + default:{ + GST_ELEMENT_ERROR (asrc, RESOURCE, READ, + (_("Error recording from audio device.")), + ("read: %s (device: %s)", g_strerror (errno), oss->open_device)); + break; + } + } + } + + return (guint) n; +} + +static guint +gst_oss4_source_delay (GstAudioSrc * asrc) +{ + audio_buf_info info = { 0, }; + GstOss4Source *oss; + guint delay; + + oss = GST_OSS4_SOURCE_CAST (asrc); + + if (ioctl (oss->fd, SNDCTL_DSP_GETISPACE, &info) == -1) { + GST_LOG_OBJECT (oss, "GETISPACE failed: %s", g_strerror (errno)); + return 0; + } + + delay = (info.fragstotal * info.fragsize) - info.bytes; + GST_LOG_OBJECT (oss, "fragstotal:%d, fragsize:%d, bytes:%d, delay:%d"); + return delay; +} + +static void +gst_oss4_source_reset (GstAudioSrc * asrc) +{ + /* There's nothing we can do here really: OSS can't handle access to the + * same device/fd from multiple threads and might deadlock or blow up in + * other ways if we try an ioctl SNDCTL_DSP_HALT or similar */ +} + +/* GstMixer interface, which we abuse here for input selection, because we + * don't have a proper interface for that and because that's what + * gnome-sound-recorder does. */ + +/* GstMixerTrack is a plain GObject, so let's just use the GLib macro here */ +G_DEFINE_TYPE (GstOss4SourceInput, gst_oss4_source_input, GST_TYPE_MIXER_TRACK); + +static void +gst_oss4_source_input_class_init (GstOss4SourceInputClass * klass) +{ + /* nothing to do here */ +} + +static void +gst_oss4_source_input_init (GstOss4SourceInput * i) +{ + /* nothing to do here */ +} + +#if 0 + +static void +gst_ossmixer_ensure_track_list (GstOssMixer * mixer) +{ + gint i, master = -1; + + g_return_if_fail (mixer->fd != -1); + + if (mixer->tracklist) + return; + + /* find master volume */ + if (mixer->devmask & SOUND_MASK_VOLUME) + master = SOUND_MIXER_VOLUME; + else if (mixer->devmask & SOUND_MASK_PCM) + master = SOUND_MIXER_PCM; + else if (mixer->devmask & SOUND_MASK_SPEAKER) + master = SOUND_MIXER_SPEAKER; /* doubtful... */ + /* else: no master, so we won't set any */ + + /* build track list */ + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (mixer->devmask & (1 << i)) { + GstMixerTrack *track; + gboolean input = FALSE, stereo = FALSE, record = FALSE; + + /* track exists, make up capabilities */ + if (MASK_BIT_IS_SET (mixer->stereomask, i)) + stereo = TRUE; + if (MASK_BIT_IS_SET (mixer->recmask, i)) + input = TRUE; + if (MASK_BIT_IS_SET (mixer->recdevs, i)) + record = TRUE; + + /* do we want mixer in our list? */ + if (!((mixer->dir & GST_OSS_MIXER_CAPTURE && input == TRUE) || + (mixer->dir & GST_OSS_MIXER_PLAYBACK && i != SOUND_MIXER_PCM))) + /* the PLAYBACK case seems hacky, but that's how 0.8 had it */ + continue; + + /* add track to list */ + track = gst_ossmixer_track_new (mixer->fd, i, stereo ? 2 : 1, + (record ? GST_MIXER_TRACK_RECORD : 0) | + (input ? GST_MIXER_TRACK_INPUT : + GST_MIXER_TRACK_OUTPUT) | + ((master != i) ? 0 : GST_MIXER_TRACK_MASTER)); + mixer->tracklist = g_list_append (mixer->tracklist, track); + } + } +} + +/* unused with G_DISABLE_* */ +static G_GNUC_UNUSED gboolean +gst_ossmixer_contains_track (GstOssMixer * mixer, GstOssMixerTrack * osstrack) +{ + const GList *item; + + for (item = mixer->tracklist; item != NULL; item = item->next) + if (item->data == osstrack) + return TRUE; + + return FALSE; +} + +const GList * +gst_ossmixer_list_tracks (GstOssMixer * mixer) +{ + gst_ossmixer_ensure_track_list (mixer); + + return (const GList *) mixer->tracklist; +} + +void +gst_ossmixer_get_volume (GstOssMixer * mixer, + GstMixerTrack * track, gint * volumes) +{ + gint volume; + GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track); + + g_return_if_fail (mixer->fd != -1); + g_return_if_fail (gst_ossmixer_contains_track (mixer, osstrack)); + + if (track->flags & GST_MIXER_TRACK_MUTE) { + volumes[0] = osstrack->lvol; + if (track->num_channels == 2) { + volumes[1] = osstrack->rvol; + } + } else { + /* get */ + if (ioctl (mixer->fd, MIXER_READ (osstrack->track_num), &volume) < 0) { + g_warning ("Error getting recording device (%d) volume: %s", + osstrack->track_num, g_strerror (errno)); + volume = 0; + } + + osstrack->lvol = volumes[0] = (volume & 0xff); + if (track->num_channels == 2) { + osstrack->rvol = volumes[1] = ((volume >> 8) & 0xff); + } + } +} + +void +gst_ossmixer_set_mute (GstOssMixer * mixer, GstMixerTrack * track, + gboolean mute) +{ + int volume; + GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track); + + g_return_if_fail (mixer->fd != -1); + g_return_if_fail (gst_ossmixer_contains_track (mixer, osstrack)); + + if (mute) { + volume = 0; + } else { + volume = (osstrack->lvol & 0xff); + if (MASK_BIT_IS_SET (mixer->stereomask, osstrack->track_num)) { + volume |= ((osstrack->rvol & 0xff) << 8); + } + } + + if (ioctl (mixer->fd, MIXER_WRITE (osstrack->track_num), &volume) < 0) { + g_warning ("Error setting mixer recording device volume (0x%x): %s", + volume, g_strerror (errno)); + return; + } + + if (mute) { + track->flags |= GST_MIXER_TRACK_MUTE; + } else { + track->flags &= ~GST_MIXER_TRACK_MUTE; + } +} +#endif + +static gint +gst_oss4_source_mixer_get_current_input (GstOss4Source * oss) +{ + int cur = -1; + + if (ioctl (oss->fd, SNDCTL_DSP_GET_RECSRC, &cur) == -1 || cur < 0) + return -1; + + return cur; +} + +static const gchar * +gst_oss4_source_mixer_update_record_flags (GstOss4Source * oss, gint cur_route) +{ + const gchar *cur_name = ""; + GList *t; + + for (t = oss->tracks; t != NULL; t = t->next) { + GstMixerTrack *track = t->data; + + if (GST_OSS4_SOURCE_INPUT (track)->route == cur_route) { + if (!GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_RECORD)) { + track->flags |= GST_MIXER_TRACK_RECORD; + /* no point in sending a mixer-record-changes message here */ + } + cur_name = track->label; + } else { + if (GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_RECORD)) { + track->flags &= ~GST_MIXER_TRACK_RECORD; + /* no point in sending a mixer-record-changes message here */ + } + } + } + + return cur_name; +} + +static const GList * +gst_oss4_source_mixer_list_tracks (GstMixer * mixer) +{ + oss_mixer_enuminfo names = { 0, }; + GstOss4Source *oss; + const gchar *cur_name; + GList *tracks = NULL; + gint i, cur; + + g_return_val_if_fail (mixer != NULL, NULL); + g_return_val_if_fail (GST_IS_OSS4_SOURCE (mixer), NULL); + g_return_val_if_fail (GST_OSS4_SOURCE_IS_OPEN (mixer), NULL); + + oss = GST_OSS4_SOURCE (mixer); + + if (oss->tracks != NULL && oss->tracks_static) + goto done; + + if (ioctl (oss->fd, SNDCTL_DSP_GET_RECSRC_NAMES, &names) == -1) + goto get_recsrc_names_error; + + oss->tracks_static = (names.version == 0); + + GST_INFO_OBJECT (oss, "%d inputs (list is static: %s):", names.nvalues, + (oss->tracks_static) ? "yes" : "no"); + + for (i = 0; i < MIN (names.nvalues, OSS_ENUM_MAXVALUE + 1); ++i) { + GstMixerTrack *track; + + track = g_object_new (GST_TYPE_OSS4_SOURCE_INPUT, NULL); + track->label = g_strdup (&names.strings[names.strindex[i]]); + track->flags = GST_MIXER_TRACK_INPUT; + track->num_channels = 2; + track->min_volume = 0; + track->max_volume = 100; + GST_OSS4_SOURCE_INPUT (track)->route = i; + + GST_INFO_OBJECT (oss, " [%d] %s", i, track->label); + tracks = g_list_append (tracks, track); + } + + gst_oss4_source_free_mixer_tracks (oss); + oss->tracks = tracks; + +done: + + /* update RECORD flags */ + cur = gst_oss4_source_mixer_get_current_input (oss); + cur_name = gst_oss4_source_mixer_update_record_flags (oss, cur); + GST_DEBUG_OBJECT (oss, "current input route: %d (%s)", cur, cur_name); + + return (const GList *) oss->tracks; + +/* ERRORS */ +get_recsrc_names_error: + { + GST_WARNING_OBJECT (oss, "GET_RECSRC_NAMES failed: %s", g_strerror (errno)); + return NULL; + } +} + +static void +gst_oss4_source_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track, + gint * volumes) +{ + GstOss4Source *oss; + int new_vol, cur; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (track != NULL); + g_return_if_fail (GST_IS_MIXER_TRACK (track)); + g_return_if_fail (GST_IS_OSS4_SOURCE (mixer)); + g_return_if_fail (GST_OSS4_SOURCE_IS_OPEN (mixer)); + + oss = GST_OSS4_SOURCE (mixer); + + cur = gst_oss4_source_mixer_get_current_input (oss); + if (cur != GST_OSS4_SOURCE_INPUT (track)->route) { + GST_DEBUG_OBJECT (oss, "track not selected input route, ignoring request"); + return; + } + + new_vol = (volumes[1] << 8) | volumes[0]; + if (ioctl (oss->fd, SNDCTL_DSP_SETRECVOL, &new_vol) == -1) { + GST_WARNING_OBJECT (oss, "SETRECVOL failed: %s", g_strerror (errno)); + } +} + +static void +gst_oss4_source_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track, + gint * volumes) +{ + GstOss4Source *oss; + int cur; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_SOURCE (mixer)); + g_return_if_fail (GST_OSS4_SOURCE_IS_OPEN (mixer)); + + oss = GST_OSS4_SOURCE (mixer); + + cur = gst_oss4_source_mixer_get_current_input (oss); + if (cur != GST_OSS4_SOURCE_INPUT (track)->route) { + volumes[0] = 0; + volumes[1] = 0; + } else { + int vol = -1; + + if (ioctl (oss->fd, SNDCTL_DSP_GETRECVOL, &vol) == -1 || vol < 0) { + GST_WARNING_OBJECT (oss, "GETRECVOL failed: %s", g_strerror (errno)); + volumes[0] = 100; + volumes[1] = 100; + } else { + volumes[0] = MIN (100, vol & 0xff); + volumes[1] = MIN (100, (vol >> 8) & 0xff); + } + } +} + +static void +gst_oss4_source_mixer_set_record (GstMixer * mixer, GstMixerTrack * track, + gboolean record) +{ + GstOss4Source *oss; + const gchar *cur_name; + gint cur; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (track != NULL); + g_return_if_fail (GST_IS_MIXER_TRACK (track)); + g_return_if_fail (GST_IS_OSS4_SOURCE (mixer)); + g_return_if_fail (GST_OSS4_SOURCE_IS_OPEN (mixer)); + + oss = GST_OSS4_SOURCE (mixer); + + cur = gst_oss4_source_mixer_get_current_input (oss); + + /* stop recording for an input that's not selected anyway => nothing to do */ + if (!record && cur != GST_OSS4_SOURCE_INPUT (track)->route) + goto done; + + /* select recording for an input that's already selected => nothing to do + * (or should we mess with the recording volume in this case maybe?) */ + if (record && cur == GST_OSS4_SOURCE_INPUT (track)->route) + goto done; + + /* make current input stop recording: we can't really make an input stop + * recording, we can only select an input FOR recording, so we'll just ignore + * all requests to stop for now */ + if (!record) { + GST_WARNING_OBJECT (oss, "Can't un-select an input as such, only switch " + "to a different input source"); + /* FIXME: set recording volume to 0 maybe? */ + } else { + int new_route = GST_OSS4_SOURCE_INPUT (track)->route; + + /* select this input for recording */ + + if (ioctl (oss->fd, SNDCTL_DSP_SET_RECSRC, &new_route) == -1) { + GST_WARNING_OBJECT (oss, "Could not select input %d for recording: %s", + new_route, g_strerror (errno)); + } else { + cur = new_route; + } + } + +done: + + cur_name = gst_oss4_source_mixer_update_record_flags (oss, cur); + GST_DEBUG_OBJECT (oss, "active input route: %d (%s)", cur, cur_name); +} + +static void +gst_oss4_source_mixer_set_mute (GstMixer * mixer, GstMixerTrack * track, + gboolean mute) +{ + GstOss4Source *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (track != NULL); + g_return_if_fail (GST_IS_MIXER_TRACK (track)); + g_return_if_fail (GST_IS_OSS4_SOURCE (mixer)); + g_return_if_fail (GST_OSS4_SOURCE_IS_OPEN (mixer)); + + oss = GST_OSS4_SOURCE (mixer); + + /* FIXME: implement gst_oss4_source_mixer_set_mute() - what to do here? */ + /* oss4_mixer_set_mute (mixer->mixer, track, mute); */ +} + +static void +gst_oss4_source_mixer_interface_init (GstMixerClass * klass) +{ + GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE; + + klass->list_tracks = gst_oss4_source_mixer_list_tracks; + klass->set_volume = gst_oss4_source_mixer_set_volume; + klass->get_volume = gst_oss4_source_mixer_get_volume; + klass->set_mute = gst_oss4_source_mixer_set_mute; + klass->set_record = gst_oss4_source_mixer_set_record; +} + +/* Implement the horror that is GstImplementsInterface */ + +static gboolean +gst_oss4_source_mixer_supported (GstImplementsInterface * iface, + GType iface_type) +{ + GstOss4Source *oss; + gboolean is_open; + + g_return_val_if_fail (GST_IS_OSS4_SOURCE (iface), FALSE); + g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE); + + oss = GST_OSS4_SOURCE (iface); + + GST_OBJECT_LOCK (oss); + is_open = GST_OSS4_SOURCE_IS_OPEN (iface); + GST_OBJECT_UNLOCK (oss); + + return is_open; +} + +static void +gst_oss4_source_mixer_implements_interface_init (GstImplementsInterfaceClass * + klass) +{ + klass->supported = gst_oss4_source_mixer_supported; +} + +static void +gst_oss4_source_init_interfaces (GType type) +{ + static const GInterfaceInfo implements_iface_info = { + (GInterfaceInitFunc) gst_oss4_source_mixer_implements_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo mixer_iface_info = { + (GInterfaceInitFunc) gst_oss4_source_mixer_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, + &implements_iface_info); + g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_iface_info); + + gst_oss4_add_property_probe_interface (type); +} diff --git a/sys/oss4/oss4-source.h b/sys/oss4/oss4-source.h new file mode 100644 index 0000000000..3a86b43ad1 --- /dev/null +++ b/sys/oss4/oss4-source.h @@ -0,0 +1,89 @@ +/* GStreamer OSS4 audio source + * Copyright (C) 2007-2008 Tim-Philipp Müller + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_OSS4_SOURCE_H +#define GST_OSS4_SOURCE_H + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_OSS4_SOURCE (gst_oss4_source_get_type()) +#define GST_OSS4_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSS4_SOURCE,GstOss4Source)) +#define GST_OSS4_SOURCE_CAST(obj) ((GstOss4Source *)(obj)) +#define GST_OSS4_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSS4_SOURCE,GstOss4SourceClass)) +#define GST_IS_OSS4_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSS4_SOURCE)) +#define GST_IS_OSS4_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSS4_SOURCE)) + +typedef struct _GstOss4Source GstOss4Source; +typedef struct _GstOss4SourceClass GstOss4SourceClass; + +struct _GstOss4Source { + GstAudioSrc audiosrc; + + gchar * device; /* NULL if none was set */ + gchar * open_device; /* the device we opened */ + gchar * device_name; /* set if the device is open */ + gint fd; /* -1 if not open */ + gint bytes_per_sample; + + GstCaps * probed_caps; + + /* property probe interface */ + GList * property_probe_list; + + /* mixer interface */ + GList * tracks; + gboolean tracks_static; /* FALSE if the list of inputs may change */ +}; + +struct _GstOss4SourceClass { + GstAudioSrcClass audiosrc_class; +}; + +GType gst_oss4_source_get_type (void); + +/* our mixer track for input selection */ +#define GST_TYPE_OSS4_SOURCE_INPUT (gst_oss4_source_input_get_type()) +#define GST_OSS4_SOURCE_INPUT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSS4_SOURCE_INPUT,GstOss4SourceInput)) +#define GST_OSS4_SOURCE_INPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSS4_SOURCE_INPUT,GstOss4SourceInputClass)) +#define GST_IS_OSS4_SOURCE_INPUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSS4_SOURCE_INPUT)) +#define GST_IS_OSS4_SOURCE_INPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSS4_SOURCE_INPUT)) + +typedef struct _GstOss4SourceInput GstOss4SourceInput; +typedef struct _GstOss4SourceInputClass GstOss4SourceInputClass; + +struct _GstOss4SourceInput { + GstMixerTrack mixer_track; + + int route; /* number for SNDCTL_DSP_SET_RECSRC etc. */ +}; + +struct _GstOss4SourceInputClass { + GstMixerTrackClass mixer_track_class; +}; + +GType gst_oss4_source_input_get_type (void); + +G_END_DECLS + +#endif /* GST_OSS4_SOURCE_H */ +