From 6e9ee0d19a736e2a010c5795c6941ebf55411328 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 9 Dec 2006 16:17:33 +0000 Subject: sys/sunaudio/: Use the sunaudio debug category. Original commit message from CVS: * sys/sunaudio/gstsunaudiomixerctrl.c: * sys/sunaudio/gstsunaudiosrc.c: Use the sunaudio debug category. * sys/sunaudio/gstsunaudiosink.c: (gst_sunaudiosink_finalize), (gst_sunaudiosink_class_init), (gst_sunaudiosink_init), (gst_sunaudiosink_set_property), (gst_sunaudiosink_get_property), (gst_sunaudiosink_open), (gst_sunaudiosink_close), (gst_sunaudiosink_prepare), (gst_sunaudio_sink_do_delay), (gst_sunaudiosink_write), (gst_sunaudiosink_delay), (gst_sunaudiosink_reset): * sys/sunaudio/gstsunaudiosink.h: Uses the sunaudio debug category for all debug output Implements the _delay() callback to synchronise video playback better Change the segtotal and segsize values back to the parent class defaults (taken from buffer_time and latency_times of 200ms and 10ms respectively) Measure the samples written to the device vs. played. Keep track of segments in the device by writing empty eof frames, and sleep using a GCond when we get too far ahead and risk overrunning the sink's ringbuffer. Fixes: #360673 --- sys/sunaudio/gstsunaudiomixerctrl.c | 3 + sys/sunaudio/gstsunaudiosink.c | 288 +++++++++++++++++++++++++++++++----- sys/sunaudio/gstsunaudiosink.h | 15 +- sys/sunaudio/gstsunaudiosrc.c | 3 + 4 files changed, 275 insertions(+), 34 deletions(-) (limited to 'sys/sunaudio') diff --git a/sys/sunaudio/gstsunaudiomixerctrl.c b/sys/sunaudio/gstsunaudiomixerctrl.c index 39df6ff5..09aa3da7 100644 --- a/sys/sunaudio/gstsunaudiomixerctrl.c +++ b/sys/sunaudio/gstsunaudiomixerctrl.c @@ -35,6 +35,9 @@ #include "gstsunaudiomixerctrl.h" #include "gstsunaudiomixertrack.h" +GST_DEBUG_CATEGORY_EXTERN (sunaudio_debug); +#define GST_CAT_DEFAULT sunaudio_debug + #define SCALE_FACTOR 2.55 /* 255/100 */ static gboolean diff --git a/sys/sunaudio/gstsunaudiosink.c b/sys/sunaudio/gstsunaudiosink.c index ab7f4e3c..6e52de90 100644 --- a/sys/sunaudio/gstsunaudiosink.c +++ b/sys/sunaudio/gstsunaudiosink.c @@ -48,6 +48,9 @@ #include "gstsunaudiosink.h" +GST_DEBUG_CATEGORY_EXTERN (sunaudio_debug); +#define GST_CAT_DEFAULT sunaudio_debug + /* elementfactory information */ static const GstElementDetails plugin_details = GST_ELEMENT_DETAILS ("Sun Audio Sink", @@ -60,6 +63,7 @@ static void gst_sunaudiosink_base_init (gpointer g_class); static void gst_sunaudiosink_class_init (GstSunAudioSinkClass * klass); static void gst_sunaudiosink_init (GstSunAudioSink * filter); static void gst_sunaudiosink_dispose (GObject * object); +static void gst_sunaudiosink_finalize (GObject * object); static void gst_sunaudiosink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -128,6 +132,24 @@ gst_sunaudiosink_dispose (GObject * object) G_OBJECT_CLASS (parent_class)->dispose (object); } +static void +gst_sunaudiosink_finalize (GObject * object) +{ + GstSunAudioSink *sunaudiosink = GST_SUNAUDIO_SINK (object); + + g_mutex_free (sunaudiosink->write_mutex); + g_cond_free (sunaudiosink->sleep_cond); + + g_free (sunaudiosink->device); + + if (sunaudiosink->fd != -1) { + close (sunaudiosink->fd); + sunaudiosink->fd = -1; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + static void gst_sunaudiosink_base_init (gpointer g_class) { @@ -156,6 +178,8 @@ gst_sunaudiosink_class_init (GstSunAudioSinkClass * klass) parent_class = g_type_class_peek_parent (klass); gobject_class->dispose = gst_sunaudiosink_dispose; + gobject_class->finalize = gst_sunaudiosink_finalize; + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_sunaudiosink_set_property); gobject_class->get_property = @@ -175,12 +199,12 @@ gst_sunaudiosink_class_init (GstSunAudioSinkClass * klass) g_object_class_install_property (gobject_class, PROP_DEVICE, g_param_spec_string ("device", "Device", "Audio Device (/dev/audio)", DEFAULT_DEVICE, G_PARAM_READWRITE)); - } static void gst_sunaudiosink_init (GstSunAudioSink * sunaudiosink) { + GstBaseAudioSink *ba_sink = GST_BASE_AUDIO_SINK (sunaudiosink); const char *audiodev; GST_DEBUG_OBJECT (sunaudiosink, "initializing sunaudiosink"); @@ -191,6 +215,10 @@ gst_sunaudiosink_init (GstSunAudioSink * sunaudiosink) if (audiodev == NULL) audiodev = DEFAULT_DEVICE; sunaudiosink->device = g_strdup (audiodev); + + /* mutex and gconf used to control the write method */ + sunaudiosink->write_mutex = g_mutex_new (); + sunaudiosink->sleep_cond = g_cond_new (); } static void @@ -203,8 +231,10 @@ gst_sunaudiosink_set_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_DEVICE: + GST_OBJECT_LOCK (sunaudiosink); g_free (sunaudiosink->device); sunaudiosink->device = g_strdup (g_value_get_string (value)); + GST_OBJECT_UNLOCK (sunaudiosink); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -222,7 +252,9 @@ gst_sunaudiosink_get_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_DEVICE: + GST_OBJECT_LOCK (sunaudiosink); g_value_set_string (value, sunaudiosink->device); + GST_OBJECT_UNLOCK (sunaudiosink); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -254,6 +286,7 @@ gst_sunaudiosink_open (GstAudioSink * asink) int fd, ret; /* First try to open non-blocking */ + GST_OBJECT_LOCK (sunaudiosink); fd = open (sunaudiosink->device, O_WRONLY | O_NONBLOCK); if (fd >= 0) { @@ -262,31 +295,24 @@ gst_sunaudiosink_open (GstAudioSink * asink) } if (fd == -1) { - GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, OPEN_WRITE, (NULL), - ("can't open connection to Sun Audio device %s", sunaudiosink->device)); - - return FALSE; + GST_OBJECT_UNLOCK (sunaudiosink); + goto open_failed; } sunaudiosink->fd = fd; + GST_OBJECT_UNLOCK (sunaudiosink); ret = ioctl (fd, AUDIO_GETDEV, &sunaudiosink->dev); - if (ret == -1) { - GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, SETTINGS, (NULL), ("%s", - strerror (errno))); - return FALSE; - } + if (ret == -1) + goto ioctl_error; GST_DEBUG_OBJECT (sunaudiosink, "name %s", sunaudiosink->dev.name); GST_DEBUG_OBJECT (sunaudiosink, "version %s", sunaudiosink->dev.version); GST_DEBUG_OBJECT (sunaudiosink, "config %s", sunaudiosink->dev.config); ret = ioctl (fd, AUDIO_GETINFO, &sunaudiosink->info); - if (ret == -1) { - GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, SETTINGS, (NULL), ("%s", - strerror (errno))); - return FALSE; - } + if (ret == -1) + goto ioctl_error; GST_DEBUG_OBJECT (sunaudiosink, "monitor_gain %d", sunaudiosink->info.monitor_gain); @@ -300,6 +326,16 @@ gst_sunaudiosink_open (GstAudioSink * asink) sunaudiosink->info.sw_features_enabled); return TRUE; + +open_failed: + GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, OPEN_WRITE, (NULL), + ("can't open connection to Sun Audio device %s", sunaudiosink->device)); + return FALSE; +ioctl_error: + close (sunaudiosink->fd); + GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, SETTINGS, (NULL), ("%s", + strerror (errno))); + return FALSE; } static gboolean @@ -307,8 +343,10 @@ gst_sunaudiosink_close (GstAudioSink * asink) { GstSunAudioSink *sunaudiosink = GST_SUNAUDIO_SINK (asink); - close (sunaudiosink->fd); - sunaudiosink->fd = -1; + if (sunaudiosink->fd != -1) { + close (sunaudiosink->fd); + sunaudiosink->fd = -1; + } return TRUE; } @@ -340,16 +378,12 @@ gst_sunaudiosink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) ainfo.play.encoding = AUDIO_ENCODING_LINEAR; ainfo.play.port = ports; - /* - * SunAudio doesn't really give access to buffer size, these values work. Setting - * the buffer so large (512K) is a bit annoying because this causes the volume - * control in audio players to be slow in responding since the audio volume won't - * change until the buffer empties. SunAudio doesn't seem to allow changing the - * audio output buffer size to anything smaller, though. I notice setting the - * values smaller causes the audio to stutter, which is worse. - */ - spec->segsize = 4096; - spec->segtotal = 128; + /* buffer_time for playback is not implemented in Solaris at the moment, + but at some point in the future, it might be */ + ainfo.play.buffer_size = + gst_util_uint64_scale (spec->rate * spec->bytes_per_sample, + spec->buffer_time, GST_SECOND / GST_USECOND); + spec->silence_sample[0] = 0; spec->silence_sample[1] = 0; spec->silence_sample[2] = 0; @@ -362,6 +396,28 @@ gst_sunaudiosink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) return FALSE; } + /* Now read back the info to find out the actual buffer size and set + segtotal */ + AUDIO_INITINFO (&ainfo); + + ret = ioctl (sunaudiosink->fd, AUDIO_GETINFO, &ainfo); + if (ret == -1) { + GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, SETTINGS, (NULL), ("%s", + strerror (errno))); + return FALSE; + } + sunaudiosink->segtotal = spec->segtotal = + ainfo.play.buffer_size / spec->segsize; + sunaudiosink->segtotal_samples = + spec->segtotal * spec->segsize / spec->bytes_per_sample; + + sunaudiosink->segs_written = (gint) ainfo.play.eof; + sunaudiosink->samples_written = ainfo.play.samples; + sunaudiosink->bytes_per_sample = spec->bytes_per_sample; + + GST_DEBUG_OBJECT (sunaudiosink, "Got device buffer_size of %u", + ainfo.play.buffer_size); + return TRUE; } @@ -371,21 +427,163 @@ gst_sunaudiosink_unprepare (GstAudioSink * asink) return TRUE; } +#define LOOP_WHILE_EINTR(v,func) do { (v) = (func); } \ + while ((v) == -1 && errno == EINTR); + +/* Called with the write_mutex held */ +static void +gst_sunaudio_sink_do_delay (GstSunAudioSink * sink) +{ + GstBaseAudioSink *ba_sink = GST_BASE_AUDIO_SINK (sink); + GstClockTime total_sleep; + GstClockTime max_sleep; + gint sleep_usecs; + GTimeVal sleep_end; + gint err; + audio_info_t ainfo; + guint diff; + + /* This code below ensures that we don't race any further than buffer_time + * ahead of the audio output, by sleeping if the next write call would cause + * us to advance too far in the ring-buffer */ + LOOP_WHILE_EINTR (err, ioctl (sink->fd, AUDIO_GETINFO, &ainfo)); + if (err < 0) + goto write_error; + + /* Compute our offset from the output (copes with overflow) */ + diff = (guint) (sink->segs_written) - ainfo.play.eof; + if (diff > sink->segtotal) { + /* This implies that reset did a flush just as the sound device aquired + * some buffers internally, and it causes us to be out of sync with the + * eof measure. This corrects it */ + sink->segs_written = ainfo.play.eof; + diff = 0; + } + + if (diff + 1 < sink->segtotal) + return; /* no need to sleep at all */ + + /* Never sleep longer than the initial number of undrained segments in the + device plus one */ + total_sleep = 0; + max_sleep = (diff + 1) * (ba_sink->latency_time * GST_USECOND); + /* sleep for a segment period between .eof polls */ + sleep_usecs = ba_sink->latency_time; + + /* Current time is our reference point */ + g_get_current_time (&sleep_end); + + /* If the next segment would take us too far along the ring buffer, + * sleep for a bit to free up a slot. If there were a way to find out + * when the eof field actually increments, we could use, but the only + * notification mechanism seems to be SIGPOLL, which we can't use from + * a support library */ + while (diff + 1 >= sink->segtotal && total_sleep < max_sleep) { + GST_LOG_OBJECT (sink, "need to block to drain segment(s). " + "Sleeping for %d us", sleep_usecs); + + g_time_val_add (&sleep_end, sleep_usecs); + + if (g_cond_timed_wait (sink->sleep_cond, sink->write_mutex, &sleep_end)) { + GST_LOG_OBJECT (sink, "Waking up early due to reset"); + return; /* Got told to wake up */ + } + total_sleep += (sleep_usecs * GST_USECOND); + + LOOP_WHILE_EINTR (err, ioctl (sink->fd, AUDIO_GETINFO, &ainfo)); + if (err < 0) + goto write_error; + + /* Compute our (new) offset from the output (copes with overflow) */ + diff = (guint) g_atomic_int_get (&sink->segs_written) - ainfo.play.eof; + } + + return; + +write_error: + GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, (NULL), + ("Playback error on device '%s': %s", sink->device, strerror (errno))); + return; +poll_failed: + GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, (NULL), + ("Playback error on device '%s': %s", sink->device, strerror (errno))); + return; +} + static guint gst_sunaudiosink_write (GstAudioSink * asink, gpointer data, guint length) { - return write (GST_SUNAUDIO_SINK (asink)->fd, data, length); + GstSunAudioSink *sink = GST_SUNAUDIO_SINK (asink); + + gint bytes_written, err; + + g_mutex_lock (sink->write_mutex); + if (sink->flushing) { + /* Exit immediately if reset tells us to */ + g_mutex_unlock (sink->write_mutex); + return length; + } + + LOOP_WHILE_EINTR (bytes_written, write (sink->fd, data, length)); + if (bytes_written < 0) { + err = bytes_written; + goto write_error; + } + + /* Increment our sample counter, for delay calcs */ + g_atomic_int_add (&sink->samples_written, length / sink->bytes_per_sample); + + /* Don't consider the segment written if we didn't output the whole lot yet */ + if (bytes_written < length) { + g_mutex_unlock (sink->write_mutex); + return (guint) bytes_written; + } + + /* Write a zero length output to trigger increment of the eof field */ + LOOP_WHILE_EINTR (err, write (sink->fd, NULL, 0)); + if (err < 0) + goto write_error; + + /* Count this extra segment we've written */ + sink->segs_written += 1; + + /* Now delay so we don't overrun the ring buffer */ + gst_sunaudio_sink_do_delay (sink); + + g_mutex_unlock (sink->write_mutex); + return length; + +write_error: + g_mutex_unlock (sink->write_mutex); + + GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, (NULL), + ("Playback error on device '%s': %s", sink->device, strerror (errno))); + return length; /* Say we wrote the segment to let the ringbuffer exit */ } /* - * Should provide the current delay between writing a sample to the - * audio device and that sample being actually played. Returning 0 for - * now, but this isn't good for synchronization - */ + * Provide the current number of unplayed samples that have been written + * to the device */ static guint gst_sunaudiosink_delay (GstAudioSink * asink) { - return 0; + GstSunAudioSink *sink = GST_SUNAUDIO_SINK (asink); + audio_info_t ainfo; + gint ret; + guint offset; + + ret = ioctl (sink->fd, AUDIO_GETINFO, &ainfo); + if (G_UNLIKELY (ret == -1)) + return 0; + + offset = (g_atomic_int_get (&sink->samples_written) - ainfo.play.samples); + + /* If the offset is larger than the total ringbuffer size, then we asked + between the write call and when samples_written is updated */ + if (G_UNLIKELY (offset > sink->segtotal_samples)) + return 0; + + return offset; } static void @@ -425,6 +623,21 @@ gst_sunaudiosink_reset (GstAudioSink * asink) strerror (errno))); } + /* Now, we take the write_mutex and signal to ensure the write thread + * is not busy, and we signal the condition to wake up any sleeper, + * then we flush again in case the write wrote something after we flushed, + * and finally release the lock and unpause */ + g_mutex_lock (sunaudiosink->write_mutex); + sunaudiosink->flushing = TRUE; + + g_cond_signal (sunaudiosink->sleep_cond); + + ret = ioctl (sunaudiosink->fd, I_FLUSH, FLUSHW); + if (ret == -1) { + GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, SETTINGS, (NULL), ("%s", + strerror (errno))); + } + /* unpause the audio */ ainfo.play.pause = NULL; ret = ioctl (sunaudiosink->fd, AUDIO_SETINFO, &ainfo); @@ -432,4 +645,13 @@ gst_sunaudiosink_reset (GstAudioSink * asink) GST_ELEMENT_ERROR (sunaudiosink, RESOURCE, SETTINGS, (NULL), ("%s", strerror (errno))); } + + /* After flushing the audio device, we need to remeasure the sample count + * and segments written count so we're in sync with the device */ + + sunaudiosink->segs_written = ainfo.play.eof; + g_atomic_int_set (&sunaudiosink->samples_written, ainfo.play.samples); + + sunaudiosink->flushing = FALSE; + g_mutex_unlock (sunaudiosink->write_mutex); } diff --git a/sys/sunaudio/gstsunaudiosink.h b/sys/sunaudio/gstsunaudiosink.h index 7c47f3e4..b1a3cc26 100644 --- a/sys/sunaudio/gstsunaudiosink.h +++ b/sys/sunaudio/gstsunaudiosink.h @@ -47,7 +47,20 @@ struct _GstSunAudioSink { audio_device_t dev; audio_info_t info; - gint bytes_per_sample; + /* Number of segments the ringbuffer is configured for */ + guint segtotal; + guint segtotal_samples; + + /* Number of segments written to the device */ + gint segs_written; + /* Number of samples written to the device */ + gint samples_written; + guint bytes_per_sample; + + /* mutex and gconf used to control the write method */ + GMutex *write_mutex; + GCond *sleep_cond; + gboolean flushing; }; struct _GstSunAudioSinkClass { diff --git a/sys/sunaudio/gstsunaudiosrc.c b/sys/sunaudio/gstsunaudiosrc.c index 1a6ff969..d9b47f1d 100644 --- a/sys/sunaudio/gstsunaudiosrc.c +++ b/sys/sunaudio/gstsunaudiosrc.c @@ -50,6 +50,9 @@ #include "gstsunaudiosrc.h" +GST_DEBUG_CATEGORY_EXTERN (sunaudio_debug); +#define GST_CAT_DEFAULT sunaudio_debug + static GstElementDetails plugin_details = GST_ELEMENT_DETAILS ("Sun Audio Source", "Source/Audio", -- cgit