diff options
Diffstat (limited to 'sys/oss/gstosshelper.c')
-rw-r--r-- | sys/oss/gstosshelper.c | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/sys/oss/gstosshelper.c b/sys/oss/gstosshelper.c new file mode 100644 index 0000000..6d7e6bd --- /dev/null +++ b/sys/oss/gstosshelper.c @@ -0,0 +1,407 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wim.taymans@chello.be> + * + * gstosshelper.c: OSS helper routines + * + * 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 "gst/gst-i18n-plugin.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#ifdef HAVE_OSS_INCLUDE_IN_SYS +# include <sys/soundcard.h> +#else +# ifdef HAVE_OSS_INCLUDE_IN_ROOT +# include <soundcard.h> +# else +# ifdef HAVE_OSS_INCLUDE_IN_MACHINE +# include <machine/soundcard.h> +# else +# error "What to include?" +# endif /* HAVE_OSS_INCLUDE_IN_MACHINE */ +# endif /* HAVE_OSS_INCLUDE_IN_ROOT */ +#endif /* HAVE_OSS_INCLUDE_IN_SYS */ + +#include <gst/interfaces/propertyprobe.h> + +#include "gstosshelper.h" +#include "gstossmixer.h" + +GST_DEBUG_CATEGORY_EXTERN (oss_debug); +#define GST_CAT_DEFAULT oss_debug + +typedef struct _GstOssProbe GstOssProbe; +struct _GstOssProbe +{ + int fd; + int format; + int n_channels; + GArray *rates; + int min; + int max; +}; + +typedef struct _GstOssRange GstOssRange; +struct _GstOssRange +{ + int min; + int max; +}; + +static GstStructure *gst_oss_helper_get_format_structure (unsigned int + format_bit); +static gboolean gst_oss_helper_rate_probe_check (GstOssProbe * probe); +static int gst_oss_helper_rate_check_rate (GstOssProbe * probe, int irate); +static void gst_oss_helper_rate_add_range (GQueue * queue, int min, int max); +static void gst_oss_helper_rate_add_rate (GArray * array, int rate); +static int gst_oss_helper_rate_int_compare (gconstpointer a, gconstpointer b); + +GstCaps * +gst_oss_helper_probe_caps (gint fd) +{ + GstOssProbe *probe; + int i; + gboolean ret; + GstStructure *structure; + unsigned int format_bit; + unsigned int format_mask; + GstCaps *caps; + + /* FIXME test make sure we're not currently playing */ + /* FIXME test both mono and stereo */ + + format_mask = AFMT_U8 | AFMT_S8; + + if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + format_mask |= AFMT_S16_LE | AFMT_U16_LE; + else + format_mask |= AFMT_S16_BE | AFMT_U16_BE; + + caps = gst_caps_new_empty (); + + /* assume that the most significant bit of format_mask is 0 */ + for (format_bit = 1 << 31; format_bit > 0; format_bit >>= 1) { + if (format_bit & format_mask) { + GValue rate_value = { 0 }; + + probe = g_new0 (GstOssProbe, 1); + probe->fd = fd; + probe->format = format_bit; + /* FIXME: this is not working for all cards, see bug #518474 */ + probe->n_channels = 2; + + ret = gst_oss_helper_rate_probe_check (probe); + if (probe->min == -1 || probe->max == -1) { + g_array_free (probe->rates, TRUE); + g_free (probe); + continue; + } + + if (ret) { + GValue value = { 0 }; + + g_array_sort (probe->rates, gst_oss_helper_rate_int_compare); + + g_value_init (&rate_value, GST_TYPE_LIST); + g_value_init (&value, G_TYPE_INT); + + for (i = 0; i < probe->rates->len; i++) { + g_value_set_int (&value, g_array_index (probe->rates, int, i)); + + gst_value_list_append_value (&rate_value, &value); + } + + g_value_unset (&value); + } else { + /* one big range */ + g_value_init (&rate_value, GST_TYPE_INT_RANGE); + gst_value_set_int_range (&rate_value, probe->min, probe->max); + } + + g_array_free (probe->rates, TRUE); + g_free (probe); + + structure = gst_oss_helper_get_format_structure (format_bit); + gst_structure_set (structure, "channels", GST_TYPE_INT_RANGE, 1, 2, NULL); + gst_structure_set_value (structure, "rate", &rate_value); + g_value_unset (&rate_value); + + gst_caps_append_structure (caps, structure); + } + } + + if (gst_caps_is_empty (caps)) { + /* fixme: make user-visible */ + GST_WARNING ("Your OSS device could not be probed correctly"); + } + + GST_DEBUG ("probed caps: %" GST_PTR_FORMAT, caps); + + return caps; +} + +static GstStructure * +gst_oss_helper_get_format_structure (unsigned int format_bit) +{ + GstStructure *structure; + int endianness; + gboolean sign; + int width; + + switch (format_bit) { + case AFMT_U8: + endianness = 0; + sign = FALSE; + width = 8; + break; + case AFMT_S16_LE: + endianness = G_LITTLE_ENDIAN; + sign = TRUE; + width = 16; + break; + case AFMT_S16_BE: + endianness = G_BIG_ENDIAN; + sign = TRUE; + width = 16; + break; + case AFMT_S8: + endianness = 0; + sign = TRUE; + width = 8; + break; + case AFMT_U16_LE: + endianness = G_LITTLE_ENDIAN; + sign = FALSE; + width = 16; + break; + case AFMT_U16_BE: + endianness = G_BIG_ENDIAN; + sign = FALSE; + width = 16; + break; + default: + g_assert_not_reached (); + return NULL; + } + + structure = gst_structure_new ("audio/x-raw-int", + "width", G_TYPE_INT, width, + "depth", G_TYPE_INT, width, "signed", G_TYPE_BOOLEAN, sign, NULL); + + if (endianness) { + gst_structure_set (structure, "endianness", G_TYPE_INT, endianness, NULL); + } + + return structure; +} + +static gboolean +gst_oss_helper_rate_probe_check (GstOssProbe * probe) +{ + GstOssRange *range; + GQueue *ranges; + int exact_rates = 0; + gboolean checking_exact_rates = TRUE; + int n_checks = 0; + gboolean result = TRUE; + + ranges = g_queue_new (); + + probe->rates = g_array_new (FALSE, FALSE, sizeof (int)); + + probe->min = gst_oss_helper_rate_check_rate (probe, 1000); + n_checks++; + probe->max = gst_oss_helper_rate_check_rate (probe, 100000); + /* a little bug workaround */ + { + int max; + + max = gst_oss_helper_rate_check_rate (probe, 48000); + if (max > probe->max) { + GST_ERROR + ("Driver bug recognized (driver does not round rates correctly). Please file a bug report."); + probe->max = max; + } + } + n_checks++; + if (probe->min == -1 || probe->max == -1) { + /* This is a workaround for drivers that return -EINVAL (or another + * error) for rates outside of [8000,48000]. If this fails, the + * driver is seriously buggy, and probably doesn't work with other + * media libraries/apps. */ + probe->min = gst_oss_helper_rate_check_rate (probe, 8000); + probe->max = gst_oss_helper_rate_check_rate (probe, 48000); + } + if (probe->min == -1 || probe->max == -1) { + GST_DEBUG ("unexpected check_rate error"); + return FALSE; + } + gst_oss_helper_rate_add_range (ranges, probe->min + 1, probe->max - 1); + + while ((range = g_queue_pop_head (ranges))) { + int min1; + int max1; + int mid; + int mid_ret; + + GST_DEBUG ("checking [%d,%d]", range->min, range->max); + + mid = (range->min + range->max) / 2; + mid_ret = gst_oss_helper_rate_check_rate (probe, mid); + if (mid_ret == -1) { + /* FIXME ioctl returned an error. do something */ + GST_DEBUG ("unexpected check_rate error"); + } + n_checks++; + + if (mid == mid_ret && checking_exact_rates) { + int max_exact_matches = 20; + + exact_rates++; + if (exact_rates > max_exact_matches) { + GST_DEBUG ("got %d exact rates, assuming all are exact", + max_exact_matches); + result = FALSE; + g_free (range); + break; + } + } else { + checking_exact_rates = FALSE; + } + + /* Assume that the rate is arithmetically rounded to the nearest + * supported rate. */ + if (mid == mid_ret) { + min1 = mid - 1; + max1 = mid + 1; + } else { + if (mid < mid_ret) { + min1 = mid - (mid_ret - mid); + max1 = mid_ret + 1; + } else { + min1 = mid_ret - 1; + max1 = mid + (mid - mid_ret); + } + } + + gst_oss_helper_rate_add_range (ranges, range->min, min1); + gst_oss_helper_rate_add_range (ranges, max1, range->max); + + g_free (range); + } + + while ((range = g_queue_pop_head (ranges))) { + g_free (range); + } + g_queue_free (ranges); + + return result; +} + +static void +gst_oss_helper_rate_add_range (GQueue * queue, int min, int max) +{ + if (min <= max) { + GstOssRange *range = g_new0 (GstOssRange, 1); + + range->min = min; + range->max = max; + + g_queue_push_tail (queue, range); + /* push_head also works, but has different probing behavior */ + /*g_queue_push_head (queue, range); */ + } +} + +static int +gst_oss_helper_rate_check_rate (GstOssProbe * probe, int irate) +{ + int rate; + int format; + int n_channels; + int ret; + + rate = irate; + format = probe->format; + n_channels = probe->n_channels; + + GST_LOG ("checking format %d, channels %d, rate %d", + format, n_channels, rate); + ret = ioctl (probe->fd, SNDCTL_DSP_SETFMT, &format); + if (ret < 0 || format != probe->format) { + GST_DEBUG ("unsupported format: %d (%d)", probe->format, format); + return -1; + } + ret = ioctl (probe->fd, SNDCTL_DSP_CHANNELS, &n_channels); + if (ret < 0 || n_channels != probe->n_channels) { + GST_DEBUG ("unsupported channels: %d (%d)", probe->n_channels, n_channels); + return -1; + } + ret = ioctl (probe->fd, SNDCTL_DSP_SPEED, &rate); + if (ret < 0) { + GST_DEBUG ("unsupported rate: %d (%d)", irate, rate); + return -1; + } + + GST_DEBUG ("rate %d -> %d", irate, rate); + + if (rate == irate - 1 || rate == irate + 1) { + rate = irate; + } + gst_oss_helper_rate_add_rate (probe->rates, rate); + return rate; +} + +static void +gst_oss_helper_rate_add_rate (GArray * array, int rate) +{ + int i; + int val; + + for (i = 0; i < array->len; i++) { + val = g_array_index (array, int, i); + + if (val == rate) + return; + } + GST_DEBUG ("supported rate: %d", rate); + g_array_append_val (array, rate); +} + +static int +gst_oss_helper_rate_int_compare (gconstpointer a, gconstpointer b) +{ + const int *va = (const int *) a; + const int *vb = (const int *) b; + + if (*va < *vb) + return -1; + if (*va > *vb) + return 1; + return 0; +} |