mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-21 07:46:38 +00:00
421 lines
10 KiB
C
421 lines
10 KiB
C
/* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, 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 "gstosshelper.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)
|
|
{
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
const guint probe_formats[] = { AFMT_S16_LE, AFMT_U16_LE, AFMT_U8, AFMT_S8 };
|
|
#else
|
|
const guint probe_formats[] = { AFMT_S16_BE, AFMT_U16_BE, AFMT_U8, AFMT_S8 };
|
|
#endif
|
|
GstOssProbe *probe;
|
|
int i, f;
|
|
gboolean ret;
|
|
GstStructure *structure;
|
|
GstCaps *caps;
|
|
|
|
/* FIXME test make sure we're not currently playing */
|
|
/* FIXME test both mono and stereo */
|
|
|
|
caps = gst_caps_new_empty ();
|
|
|
|
/* assume that the most significant bit of format_mask is 0 */
|
|
for (f = 0; f < G_N_ELEMENTS (probe_formats); ++f) {
|
|
GValue rate_value = { 0 };
|
|
|
|
probe = g_new0 (GstOssProbe, 1);
|
|
probe->fd = fd;
|
|
probe->format = probe_formats[f];
|
|
/* 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 (probe_formats[f]);
|
|
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");
|
|
} else {
|
|
caps = gst_caps_simplify (caps);
|
|
}
|
|
|
|
GST_DEBUG ("probed caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
return caps;
|
|
}
|
|
|
|
static GstStructure *
|
|
gst_oss_helper_get_format_structure (unsigned int format_bit)
|
|
{
|
|
GstStructure *structure;
|
|
const gchar *format;
|
|
|
|
switch (format_bit) {
|
|
case AFMT_U8:
|
|
format = "U8";
|
|
break;
|
|
case AFMT_S16_LE:
|
|
format = "S16LE";
|
|
break;
|
|
case AFMT_S16_BE:
|
|
format = "S16BE";
|
|
break;
|
|
case AFMT_S8:
|
|
format = "S8";
|
|
break;
|
|
case AFMT_U16_LE:
|
|
format = "U16LE";
|
|
break;
|
|
case AFMT_U16_BE:
|
|
format = "U16BE";
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
return NULL;
|
|
}
|
|
|
|
structure = gst_structure_new ("audio/x-raw",
|
|
"format", G_TYPE_STRING, format,
|
|
"layout", G_TYPE_STRING, "interleaved", 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;
|
|
}
|
|
|
|
gchar *
|
|
gst_oss_helper_get_card_name (const gchar * mixer_name)
|
|
{
|
|
#ifdef SOUND_MIXER_INFO
|
|
struct mixer_info minfo;
|
|
#endif
|
|
gint fd;
|
|
gchar *name = NULL;
|
|
|
|
GST_INFO ("Opening mixer for device %s", mixer_name);
|
|
fd = open (mixer_name, O_RDWR);
|
|
if (fd == -1)
|
|
goto open_failed;
|
|
|
|
/* get name, not fatal */
|
|
#ifdef SOUND_MIXER_INFO
|
|
if (ioctl (fd, SOUND_MIXER_INFO, &minfo) == 0) {
|
|
name = g_strdup (minfo.name);
|
|
GST_INFO ("Card name = %s", GST_STR_NULL (name));
|
|
} else
|
|
#endif
|
|
{
|
|
name = g_strdup ("Unknown");
|
|
GST_INFO ("Unknown card name");
|
|
}
|
|
|
|
close (fd);
|
|
return name;
|
|
|
|
/* ERRORS */
|
|
open_failed:
|
|
{
|
|
/* this is valid. OSS devices don't need to expose a mixer */
|
|
GST_DEBUG ("Failed to open mixer device %s, mixing disabled: %s",
|
|
mixer_name, strerror (errno));
|
|
return NULL;
|
|
}
|
|
}
|