gstosssink.c 14.5 KB
Newer Older
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1 2
/* GStreamer
 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3
 *               2000,2005 Wim Taymans <wim@fluendo.com>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * gstosssink.c: 
 *
 * 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.
 */

23 24 25 26
/**
 * SECTION:element-osssink
 *
 * This element lets you output sound using the Open Sound System (OSS).
27
 *
28 29 30 31
 * 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).
32 33
 *
 * <refsect2>
34
 * <title>Example pipelines</title>
35
 * |[
36
 * gst-launch -v audiotestsrc ! audioconvert ! volume volume=0.1 ! osssink
37
 * ]| will output a sine wave (continuous beep sound) to your sound card (with
38
 * a very low volume as precaution).
39
 * |[
40
 * gst-launch -v filesrc location=music.ogg ! decodebin ! audioconvert ! audioresample ! osssink
41
 * ]| will play an Ogg/Vorbis audio file and output it using the Open Sound System.
42 43 44
 * </refsect2>
 */

45 46 47
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
48
#include <sys/ioctl.h>
49
#include <fcntl.h>
50
#include <errno.h>
51
#include <unistd.h>
52
#include <string.h>
53

54 55 56 57 58 59 60 61 62 63 64 65 66
#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 */
67

68
#include "common.h"
69
#include "gstosssink.h"
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
70

71 72
#include <gst/gst-i18n-plugin.h>

73 74 75
GST_DEBUG_CATEGORY_EXTERN (oss_debug);
#define GST_CAT_DEFAULT oss_debug

76 77 78
static void gst_oss_sink_base_init (gpointer g_class);
static void gst_oss_sink_class_init (GstOssSinkClass * klass);
static void gst_oss_sink_init (GstOssSink * osssink);
79

80
static void gst_oss_sink_dispose (GObject * object);
81
static void gst_oss_sink_finalise (GObject * object);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
82

83 84 85 86 87
static void gst_oss_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_oss_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);

88
static GstCaps *gst_oss_sink_getcaps (GstBaseSink * bsink);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
89

90
static gboolean gst_oss_sink_open (GstAudioSink * asink);
91
static gboolean gst_oss_sink_close (GstAudioSink * asink);
92 93 94
static gboolean gst_oss_sink_prepare (GstAudioSink * asink,
    GstRingBufferSpec * spec);
static gboolean gst_oss_sink_unprepare (GstAudioSink * asink);
95
static guint gst_oss_sink_write (GstAudioSink * asink, gpointer data,
96
    guint length);
97 98
static guint gst_oss_sink_delay (GstAudioSink * asink);
static void gst_oss_sink_reset (GstAudioSink * asink);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
99 100

/* OssSink signals and args */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
101 102
enum
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
103 104 105
  LAST_SIGNAL
};

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
106
#define DEFAULT_DEVICE  "/dev/dsp"
107 108 109 110 111 112
enum
{
  PROP_0,
  PROP_DEVICE,
};

David Schleef's avatar
David Schleef committed
113
static GstStaticPadTemplate osssink_sink_factory =
114
    GST_STATIC_PAD_TEMPLATE ("sink",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
115 116 117
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw-int, "
118 119
        "endianness = (int) { " G_STRINGIFY (G_BYTE_ORDER) " }, "
        "signed = (boolean) { TRUE, FALSE }, "
120
        "width = (int) 16, "
121
        "depth = (int) 16, "
122 123 124 125 126
        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; "
        "audio/x-raw-int, "
        "signed = (boolean) { TRUE, FALSE }, "
        "width = (int) 8, "
        "depth = (int) 8, "
127
        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
128
    );
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
129 130

static GstElementClass *parent_class = NULL;
131

132
/* static guint gst_oss_sink_signals[LAST_SIGNAL] = { 0 }; */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
133 134

GType
135
gst_oss_sink_get_type (void)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
136 137 138 139 140
{
  static GType osssink_type = 0;

  if (!osssink_type) {
    static const GTypeInfo osssink_info = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
141
      sizeof (GstOssSinkClass),
142
      gst_oss_sink_base_init,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
143
      NULL,
144
      (GClassInitFunc) gst_oss_sink_class_init,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
145 146
      NULL,
      NULL,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
147
      sizeof (GstOssSink),
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
148
      0,
149
      (GInstanceInitFunc) gst_oss_sink_init,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
150
    };
151

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
152
    osssink_type =
153
        g_type_register_static (GST_TYPE_AUDIO_SINK, "GstOssSink",
154
        &osssink_info, 0);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
155 156 157 158 159
  }

  return osssink_type;
}

160
static void
161
gst_oss_sink_dispose (GObject * object)
162
{
163 164 165 166 167 168 169
  GstOssSink *osssink = GST_OSSSINK (object);

  if (osssink->probed_caps) {
    gst_caps_unref (osssink->probed_caps);
    osssink->probed_caps = NULL;
  }

170 171 172
  G_OBJECT_CLASS (parent_class)->dispose (object);
}

173
static void
174
gst_oss_sink_base_init (gpointer g_class)
175 176
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
177

178 179 180 181 182
  gst_element_class_set_details_simple (element_class, "Audio Sink (OSS)",
      "Sink/Audio",
      "Output to a sound card via OSS",
      "Erik Walthinsen <omega@cse.ogi.edu>, "
      "Wim Taymans <wim.taymans@chello.be>");
183

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
184 185
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&osssink_sink_factory));
186
}
187

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
188
static void
189
gst_oss_sink_class_init (GstOssSinkClass * klass)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
190 191
{
  GObjectClass *gobject_class;
192 193
  GstBaseSinkClass *gstbasesink_class;
  GstAudioSinkClass *gstaudiosink_class;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
194

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
195
  gobject_class = (GObjectClass *) klass;
196 197 198
  gstbasesink_class = (GstBaseSinkClass *) klass;
  gstaudiosink_class = (GstAudioSinkClass *) klass;

199
  parent_class = g_type_class_peek_parent (klass);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
200

201 202 203 204
  gobject_class->dispose = gst_oss_sink_dispose;
  gobject_class->finalize = gst_oss_sink_finalise;
  gobject_class->get_property = gst_oss_sink_get_property;
  gobject_class->set_property = gst_oss_sink_set_property;
205 206 207 208

  g_object_class_install_property (gobject_class, PROP_DEVICE,
      g_param_spec_string ("device", "Device",
          "OSS device (usually /dev/dspN)", DEFAULT_DEVICE, G_PARAM_READWRITE));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
209

210
  gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_oss_sink_getcaps);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
211

212 213
  gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_oss_sink_open);
  gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_oss_sink_close);
214 215
  gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_oss_sink_prepare);
  gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_oss_sink_unprepare);
216 217 218
  gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_oss_sink_write);
  gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_oss_sink_delay);
  gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_oss_sink_reset);
Wim Taymans's avatar
Wim Taymans committed
219 220
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
221
static void
222
gst_oss_sink_init (GstOssSink * osssink)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
223
{
224 225
  const gchar *device;

226
  GST_DEBUG_OBJECT (osssink, "initializing osssink");
227

228 229 230 231
  device = g_getenv ("AUDIODEV");
  if (device == NULL)
    device = DEFAULT_DEVICE;
  osssink->device = g_strdup (device);
232
  osssink->fd = -1;
233
}
234

235 236 237 238 239 240
static void
gst_oss_sink_finalise (GObject * object)
{
  GstOssSink *osssink = GST_OSSSINK (object);

  g_free (osssink->device);
241 242

  G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (object));
243 244
}

245 246 247 248 249 250 251 252 253 254 255 256
static void
gst_oss_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstOssSink *sink;

  sink = GST_OSSSINK (object);

  switch (prop_id) {
    case PROP_DEVICE:
      g_free (sink->device);
      sink->device = g_value_dup_string (value);
257 258 259 260
      if (sink->probed_caps) {
        gst_caps_unref (sink->probed_caps);
        sink->probed_caps = NULL;
      }
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_oss_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstOssSink *sink;

  sink = GST_OSSSINK (object);

  switch (prop_id) {
    case PROP_DEVICE:
      g_value_set_string (value, sink->device);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

286
static GstCaps *
287
gst_oss_sink_getcaps (GstBaseSink * bsink)
288
{
289
  GstOssSink *osssink;
290 291
  GstCaps *caps;

292 293
  osssink = GST_OSSSINK (bsink);

294 295
  if (osssink->fd == -1) {
    caps = gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD
296
            (bsink)));
297 298
  } else if (osssink->probed_caps) {
    caps = gst_caps_copy (osssink->probed_caps);
299
  } else {
300
    caps = gst_oss_helper_probe_caps (osssink->fd);
301 302 303
    if (caps && !gst_caps_is_empty (caps)) {
      osssink->probed_caps = gst_caps_copy (caps);
    }
304 305 306 307 308
  }

  return caps;
}

309 310
static gint
ilog2 (gint x)
311
{
312 313 314 315 316 317 318 319 320 321 322 323
  /* 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;
324 325
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
static gint
gst_oss_sink_get_format (GstBufferFormat fmt)
{
  gint result;

  switch (fmt) {
    case GST_MU_LAW:
      result = AFMT_MU_LAW;
      break;
    case GST_A_LAW:
      result = AFMT_A_LAW;
      break;
    case GST_IMA_ADPCM:
      result = AFMT_IMA_ADPCM;
      break;
    case GST_U8:
      result = AFMT_U8;
      break;
    case GST_S16_LE:
      result = AFMT_S16_LE;
      break;
    case GST_S16_BE:
      result = AFMT_S16_BE;
      break;
    case GST_S8:
      result = AFMT_S8;
      break;
    case GST_U16_LE:
      result = AFMT_U16_LE;
      break;
    case GST_U16_BE:
      result = AFMT_U16_BE;
      break;
    case GST_MPEG:
      result = AFMT_MPEG;
      break;
    default:
      result = 0;
      break;
  }
  return result;
}

369
static gboolean
370
gst_oss_sink_open (GstAudioSink * asink)
Wim Taymans's avatar
Wim Taymans committed
371
{
372
  GstOssSink *oss;
373
  int mode;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
374

375
  oss = GST_OSSSINK (asink);
Wim Taymans's avatar
Wim Taymans committed
376

377 378
  mode = O_WRONLY;
  mode |= O_NONBLOCK;
Wim Taymans's avatar
Wim Taymans committed
379

380
  oss->fd = open (oss->device, mode, 0);
381 382 383 384
  if (oss->fd == -1) {
    switch (errno) {
      case EBUSY:
        goto busy;
385 386
      case EACCES:
        goto no_permission;
387 388 389 390
      default:
        goto open_failed;
    }
  }
391 392

  return TRUE;
393

Wim Taymans's avatar
Wim Taymans committed
394
  /* ERRORS */
395 396
busy:
  {
397 398 399 400 401 402 403 404
    GST_ELEMENT_ERROR (oss, RESOURCE, BUSY,
        (_("Could not open audio device for playback. "
                "Device is being used by another application.")), (NULL));
    return FALSE;
  }
no_permission:
  {
    GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_WRITE,
405
        (_("Could not open audio device for playback. "
406 407
                "You don't have permission to open the device.")),
        GST_ERROR_SYSTEM);
408 409
    return FALSE;
  }
410 411
open_failed:
  {
412 413
    GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_WRITE,
        (_("Could not open audio device for playback.")), GST_ERROR_SYSTEM);
414 415
    return FALSE;
  }
416 417 418 419 420 421
}

static gboolean
gst_oss_sink_close (GstAudioSink * asink)
{
  close (GST_OSSSINK (asink)->fd);
422
  GST_OSSSINK (asink)->fd = -1;
423 424 425 426 427 428 429 430 431 432 433 434 435
  return TRUE;
}

static gboolean
gst_oss_sink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
{
  GstOssSink *oss;
  struct audio_buf_info info;
  int mode;
  int tmp;

  oss = GST_OSSSINK (asink);

436 437
  /* we opened non-blocking so that we can detect if the device is available
   * without hanging forever. We now want to remove the non-blocking flag. */
438 439
  mode = fcntl (oss->fd, F_GETFL);
  mode &= ~O_NONBLOCK;
440 441 442 443 444 445 446
  if (fcntl (oss->fd, F_SETFL, mode) == -1) {
    /* 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_oss_sink_close (asink);
    if ((oss->fd = open (oss->device, O_WRONLY, 0)) == -1)
      goto non_block;
  }
Wim Taymans's avatar
Wim Taymans committed
447

448 449 450 451
  tmp = gst_oss_sink_get_format (spec->format);
  if (tmp == 0)
    goto wrong_format;

452 453 454
  if (spec->width != 16 && spec->width != 8)
    goto dodgy_width;

455
  SET_PARAM (oss, SNDCTL_DSP_SETFMT, tmp, "SETFMT");
456
  if (spec->channels == 2)
457 458 459
    SET_PARAM (oss, SNDCTL_DSP_STEREO, 1, "STEREO");
  SET_PARAM (oss, SNDCTL_DSP_CHANNELS, spec->channels, "CHANNELS");
  SET_PARAM (oss, SNDCTL_DSP_SPEED, spec->rate, "SPEED");
460

461 462
  tmp = ilog2 (spec->segsize);
  tmp = ((spec->segtotal & 0x7fff) << 16) | tmp;
463 464
  GST_DEBUG_OBJECT (oss, "set segsize: %d, segtotal: %d, value: %08x",
      spec->segsize, spec->segtotal, tmp);
Wim Taymans's avatar
Wim Taymans committed
465

466 467
  SET_PARAM (oss, SNDCTL_DSP_SETFRAGMENT, tmp, "SETFRAGMENT");
  GET_PARAM (oss, SNDCTL_DSP_GETOSPACE, &info, "GETOSPACE");
Ronald S. Bultje's avatar
Ronald S. Bultje committed
468

469 470
  spec->segsize = info.fragsize;
  spec->segtotal = info.fragstotal;
471 472 473

  spec->bytes_per_sample = (spec->width / 8) * spec->channels;
  oss->bytes_per_sample = (spec->width / 8) * spec->channels;
474

475 476
  GST_DEBUG_OBJECT (oss, "got segsize: %d, segtotal: %d, value: %08x",
      spec->segsize, spec->segtotal, tmp);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
477

478
  return TRUE;
479

Wim Taymans's avatar
Wim Taymans committed
480
  /* ERRORS */
481 482
non_block:
  {
483
    GST_ELEMENT_ERROR (oss, RESOURCE, SETTINGS, (NULL),
484
        ("Unable to set device %s in non blocking mode: %s",
485
            oss->device, g_strerror (errno)));
486 487
    return FALSE;
  }
488 489
wrong_format:
  {
490 491
    GST_ELEMENT_ERROR (oss, RESOURCE, SETTINGS, (NULL),
        ("Unable to get format %d", spec->format));
492 493
    return FALSE;
  }
494 495
dodgy_width:
  {
496 497
    GST_ELEMENT_ERROR (oss, RESOURCE, SETTINGS, (NULL),
        ("unexpected width %d", spec->width));
498 499
    return FALSE;
  }
500 501
}

502
static gboolean
503
gst_oss_sink_unprepare (GstAudioSink * asink)
504
{
505 506 507 508 509 510 511 512
  /* could do a SNDCTL_DSP_RESET, but the OSS manual recommends a close/open */

  if (!gst_oss_sink_close (asink))
    goto couldnt_close;

  if (!gst_oss_sink_open (asink))
    goto couldnt_reopen;

513
  return TRUE;
514

Wim Taymans's avatar
Wim Taymans committed
515
  /* ERRORS */
516 517
couldnt_close:
  {
518
    GST_DEBUG_OBJECT (asink, "Could not close the audio device");
519 520 521 522
    return FALSE;
  }
couldnt_reopen:
  {
523
    GST_DEBUG_OBJECT (asink, "Could not reopen the audio device");
524 525
    return FALSE;
  }
526 527
}

528
static guint
529
gst_oss_sink_write (GstAudioSink * asink, gpointer data, guint length)
530
{
531
  return write (GST_OSSSINK (asink)->fd, data, length);
532 533
}

534
static guint
535
gst_oss_sink_delay (GstAudioSink * asink)
536
{
537 538 539
  GstOssSink *oss;
  gint delay = 0;
  gint ret;
540

541
  oss = GST_OSSSINK (asink);
542

543 544 545 546 547 548 549
#ifdef SNDCTL_DSP_GETODELAY
  ret = ioctl (oss->fd, SNDCTL_DSP_GETODELAY, &delay);
#else
  ret = -1;
#endif
  if (ret < 0) {
    audio_buf_info info;
550

551
    ret = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &info);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
552

553
    delay = (ret < 0 ? 0 : (info.fragstotal * info.fragsize) - info.bytes);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
554
  }
555
  return delay / oss->bytes_per_sample;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
556 557
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
558
static void
559
gst_oss_sink_reset (GstAudioSink * asink)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
560
{
561 562 563
  /* 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_RESET or similar */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
564
}