diff --git a/configure.ac b/configure.ac index 4e7b73c12d..859ee3a6eb 100644 --- a/configure.ac +++ b/configure.ac @@ -809,6 +809,10 @@ AG_GST_CHECK_FEATURE(PULSE, [pulseaudio plug-in], pulseaudio, [ if test x$HAVE_PULSE_0_9_20 = xyes; then AC_DEFINE(HAVE_PULSE_0_9_20, 1, [defined if pulseaudio >= 0.9.20 is available]) fi + AG_GST_PKG_CHECK_MODULES(PULSE_1_0, libpulse >= 0.98) + if test x$HAVE_PULSE_1_0 = xyes; then + AC_DEFINE(HAVE_PULSE_1_0, 1, [defined if pulseaudio >= 1.0 is available]) + fi ]) dnl *** dv1394 *** diff --git a/ext/jack/gstjackaudiosink.c b/ext/jack/gstjackaudiosink.c index d5fd869cd4..7f4ecf73bd 100644 --- a/ext/jack/gstjackaudiosink.c +++ b/ext/jack/gstjackaudiosink.c @@ -83,6 +83,7 @@ gst_jack_audio_sink_allocate_channels (GstJackAudioSink * sink, gint channels) /* alloc enough output ports */ sink->ports = g_realloc (sink->ports, sizeof (jack_port_t *) * channels); + sink->buffers = g_realloc (sink->buffers, sizeof (sample_t *) * channels); /* create an output port for each channel */ while (sink->port_count < channels) { @@ -123,6 +124,8 @@ gst_jack_audio_sink_free_channels (GstJackAudioSink * sink) } g_free (sink->ports); sink->ports = NULL; + g_free (sink->buffers); + sink->buffers = NULL; } /* ringbuffer abstract base class */ @@ -187,19 +190,17 @@ jack_process_cb (jack_nframes_t nframes, void *arg) gint readseg, len; guint8 *readptr; gint i, j, flen, channels; - sample_t **buffers, *data; + sample_t *data; buf = GST_RING_BUFFER_CAST (arg); sink = GST_JACK_AUDIO_SINK (GST_OBJECT_PARENT (buf)); channels = buf->spec.channels; - /* alloc pointers to samples */ - buffers = g_alloca (sizeof (sample_t *) * channels); - /* get target buffers */ for (i = 0; i < channels; i++) { - buffers[i] = (sample_t *) jack_port_get_buffer (sink->ports[i], nframes); + sink->buffers[i] = + (sample_t *) jack_port_get_buffer (sink->ports[i], nframes); } if (gst_ring_buffer_prepare_read (buf, &readseg, &readptr, &len)) { @@ -217,7 +218,7 @@ jack_process_cb (jack_nframes_t nframes, void *arg) * deinterleave into the jack target buffers */ for (i = 0; i < nframes; i++) { for (j = 0; j < channels; j++) { - buffers[j][i] = *data++; + sink->buffers[j][i] = *data++; } } @@ -231,7 +232,7 @@ jack_process_cb (jack_nframes_t nframes, void *arg) /* We are not allowed to read from the ringbuffer, write silence to all * jack output buffers */ for (i = 0; i < channels; i++) { - memset (buffers[i], 0, nframes * sizeof (sample_t)); + memset (sink->buffers[i], 0, nframes * sizeof (sample_t)); } } return 0; @@ -733,6 +734,7 @@ gst_jack_audio_sink_init (GstJackAudioSink * sink) sink->jclient = NULL; sink->ports = NULL; sink->port_count = 0; + sink->buffers = NULL; } static void diff --git a/ext/jack/gstjackaudiosink.h b/ext/jack/gstjackaudiosink.h index def423329d..6f9c45c8eb 100644 --- a/ext/jack/gstjackaudiosink.h +++ b/ext/jack/gstjackaudiosink.h @@ -65,6 +65,7 @@ struct _GstJackAudioSink { /* our ports */ jack_port_t **ports; int port_count; + sample_t **buffers; }; struct _GstJackAudioSinkClass { diff --git a/ext/pulse/pulsesink.c b/ext/pulse/pulsesink.c index 40cc60ee4d..7dbd3a4e1f 100644 --- a/ext/pulse/pulsesink.c +++ b/ext/pulse/pulsesink.c @@ -54,6 +54,7 @@ #include #include #include +#include #include /* only used for GST_PLUGINS_BASE_VERSION_* */ @@ -138,7 +139,12 @@ struct _GstPulseRingBuffer pa_context *context; pa_stream *stream; +#ifdef HAVE_PULSE_1_0 + pa_format_info *format; + guint channels; +#else pa_sample_spec sample_spec; +#endif void *m_data; size_t m_towrite; @@ -222,7 +228,12 @@ gst_pulseringbuffer_init (GstPulseRingBuffer * pbuf) pbuf->context = NULL; pbuf->stream = NULL; +#ifdef HAVE_PULSE_1_0 + pbuf->format = NULL; + pbuf->channels = 0; +#else pa_sample_spec_init (&pbuf->sample_spec); +#endif pbuf->m_data = NULL; pbuf->m_towrite = 0; @@ -251,6 +262,13 @@ gst_pulsering_destroy_stream (GstPulseRingBuffer * pbuf) pbuf->m_offset = 0; pbuf->m_lastoffset = 0; } +#ifdef HAVE_PULSE_1_0 + if (pbuf->format) { + pa_format_info_free (pbuf->format); + pbuf->format = NULL; + pbuf->channels = 0; + } +#endif pa_stream_disconnect (pbuf->stream); @@ -695,11 +713,60 @@ gst_pulsering_stream_event_cb (pa_stream * p, const char *name, gst_element_post_message (GST_ELEMENT_CAST (psink), gst_message_new_request_state (GST_OBJECT_CAST (psink), GST_STATE_PLAYING)); +#ifdef HAVE_PULSE_1_0 + } else if (!strcmp (name, PA_STREAM_EVENT_FORMAT_LOST)) { + GstEvent *renego; + + if (g_atomic_int_get (&psink->format_lost)) { + /* Duplicate event before we're done reconfiguring, discard */ + return; + } + + GST_DEBUG_OBJECT (psink, "got FORMAT LOST"); + g_atomic_int_set (&psink->format_lost, 1); + psink->format_lost_time = g_ascii_strtoull (pa_proplist_gets (pl, + "stream-time"), NULL, 0) * 1000; + + g_free (psink->device); + psink->device = g_strdup (pa_proplist_gets (pl, "device")); + + renego = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_new ("pulse-format-lost", NULL)); + + if (!gst_pad_push_event (GST_BASE_SINK (psink)->sinkpad, renego)) { + /* Nobody handled the format change - emit an error */ + GST_ELEMENT_ERROR (psink, STREAM, FORMAT, ("Sink format changed"), + ("Sink format changed")); + } +#endif } else { GST_DEBUG_OBJECT (psink, "got unknown event %s", name); } } +/* Called with the mainloop locked */ +static gboolean +gst_pulsering_wait_for_stream_ready (GstPulseSink * psink, pa_stream * stream) +{ + pa_stream_state_t state; + + for (;;) { + state = pa_stream_get_state (stream); + + GST_LOG_OBJECT (psink, "stream state is now %d", state); + + if (!PA_STREAM_IS_GOOD (state)) + return FALSE; + + if (state == PA_STREAM_READY) + return TRUE; + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait (mainloop); + } +} + + /* This method should create a new stream of the given @spec. No playback should * start yet so we start in the corked state. */ static gboolean @@ -718,14 +785,25 @@ gst_pulseringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) pa_stream_flags_t flags; const gchar *name; GstAudioClock *clock; +#ifdef HAVE_PULSE_1_0 + pa_format_info *formats[1]; +#ifndef GST_DISABLE_GST_DEBUG + gchar print_buf[PA_FORMAT_INFO_SNPRINT_MAX]; +#endif +#endif psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (buf)); pbuf = GST_PULSERING_BUFFER_CAST (buf); GST_LOG_OBJECT (psink, "creating sample spec"); /* convert the gstreamer sample spec to the pulseaudio format */ +#ifdef HAVE_PULSE_1_0 + if (!gst_pulse_fill_format_info (spec, &pbuf->format, &pbuf->channels)) + goto invalid_spec; +#else if (!gst_pulse_fill_sample_spec (spec, &pbuf->sample_spec)) goto invalid_spec; +#endif pa_threaded_mainloop_lock (mainloop); @@ -742,7 +820,13 @@ gst_pulseringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) pa_operation_unref (o); /* initialize the channel map */ +#ifdef HAVE_PULSE_1_0 + if (pa_format_info_is_pcm (pbuf->format) && + gst_pulse_gst_to_channel_map (&channel_map, spec)) + pa_format_info_set_channel_map (pbuf->format, &channel_map); +#else gst_pulse_gst_to_channel_map (&channel_map, spec); +#endif /* find a good name for the stream */ if (psink->stream_name) @@ -751,10 +835,17 @@ gst_pulseringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) name = "Playback Stream"; /* create a stream */ +#ifdef HAVE_PULSE_1_0 + formats[0] = pbuf->format; + if (!(pbuf->stream = pa_stream_new_extended (pbuf->context, name, formats, 1, + psink->proplist))) + goto stream_failed; +#else GST_LOG_OBJECT (psink, "creating stream with name %s", name); if (!(pbuf->stream = pa_stream_new_with_proplist (pbuf->context, name, &pbuf->sample_spec, &channel_map, psink->proplist))) goto stream_failed; +#endif /* install essential callbacks */ pa_stream_set_state_callback (pbuf->stream, @@ -792,8 +883,17 @@ gst_pulseringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) if (psink->volume_set) { GST_LOG_OBJECT (psink, "have volume of %f", psink->volume); pv = &v; +#ifdef HAVE_PULSE_1_0 + if (pa_format_info_is_pcm (pbuf->format)) + gst_pulse_cvolume_from_linear (pv, pbuf->channels, psink->volume); + else { + GST_DEBUG_OBJECT (psink, "passthrough stream, not setting volume"); + pv = NULL; + } +#else gst_pulse_cvolume_from_linear (pv, pbuf->sample_spec.channels, psink->volume); +#endif } else { pv = NULL; } @@ -820,22 +920,19 @@ gst_pulseringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) clock = GST_AUDIO_CLOCK (GST_BASE_AUDIO_SINK (psink)->provided_clock); gst_audio_clock_reset (clock, 0); - for (;;) { - pa_stream_state_t state; + if (!gst_pulsering_wait_for_stream_ready (psink, pbuf->stream)) + goto connect_failed; - state = pa_stream_get_state (pbuf->stream); +#ifdef HAVE_PULSE_1_0 + g_free (psink->device); + psink->device = g_strdup (pa_stream_get_device_name (pbuf->stream)); - GST_LOG_OBJECT (psink, "stream state is now %d", state); - - if (!PA_STREAM_IS_GOOD (state)) - goto connect_failed; - - if (state == PA_STREAM_READY) - break; - - /* Wait until the stream is ready */ - pa_threaded_mainloop_wait (mainloop); - } +#ifndef GST_DISABLE_GST_DEBUG + pa_format_info_snprint (print_buf, sizeof (print_buf), + pa_stream_get_format_info (pbuf->stream)); + GST_INFO_OBJECT (psink, "negotiated to: %s", print_buf); +#endif +#endif /* After we passed the volume off of to PA we never want to set it again, since it is PA's job to save/restore volumes. */ @@ -909,6 +1006,16 @@ gst_pulseringbuffer_release (GstRingBuffer * buf) gst_pulsering_destroy_stream (pbuf); pa_threaded_mainloop_unlock (mainloop); +#ifdef HAVE_PULSE_1_0 + { + GstPulseSink *psink; + + psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf)); + g_atomic_int_set (&psink->format_lost, FALSE); + psink->format_lost_time = GST_CLOCK_TIME_NONE; + } +#endif + return TRUE; } @@ -930,6 +1037,13 @@ gst_pulsering_set_corked (GstPulseRingBuffer * pbuf, gboolean corked, psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf)); +#ifdef HAVE_PULSE_1_0 + if (g_atomic_int_get (&psink->format_lost)) { + /* Sink format changed, stream's gone so fake being paused */ + return TRUE; + } +#endif + GST_DEBUG_OBJECT (psink, "setting corked state to %d", corked); if (pbuf->corked != corked) { if (!(o = pa_stream_cork (pbuf->stream, corked, @@ -1102,13 +1216,22 @@ gst_pulseringbuffer_stop (GstRingBuffer * buf) psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf)); pa_threaded_mainloop_lock (mainloop); + pbuf->paused = TRUE; res = gst_pulsering_set_corked (pbuf, TRUE, TRUE); + /* Inform anyone waiting in _commit() call that it shall wakeup */ if (pbuf->in_commit) { GST_DEBUG_OBJECT (psink, "signal commit thread"); pa_threaded_mainloop_signal (mainloop, 0); } +#ifdef HAVE_PULSE_1_0 + if (g_atomic_int_get (&psink->format_lost)) { + /* Don't try to flush, the stream's probably gone by now */ + res = TRUE; + goto cleanup; + } +#endif /* then try to flush, it's not fatal when this fails */ GST_DEBUG_OBJECT (psink, "flushing"); @@ -1216,7 +1339,6 @@ G_STMT_START { \ GST_DEBUG ("rev_down end %d/%d",*accum,*toprocess); \ } G_STMT_END - /* our custom commit function because we write into the buffer of pulseaudio * instead of keeping our own buffer */ static guint @@ -1255,6 +1377,7 @@ gst_pulseringbuffer_commit (GstRingBuffer * buf, guint64 * sample, } pa_threaded_mainloop_lock (mainloop); + GST_DEBUG_OBJECT (psink, "entering commit"); pbuf->in_commit = TRUE; @@ -1279,6 +1402,13 @@ gst_pulseringbuffer_commit (GstRingBuffer * buf, guint64 * sample, * needed to properly handle reverse playback: it points to the last sample. */ data_end = data + (bps * inr); +#ifdef HAVE_PULSE_1_0 + if (g_atomic_int_get (&psink->format_lost)) { + /* Sink format changed, drop the data and hope upstream renegotiates */ + goto fake_done; + } +#endif + if (pbuf->paused) goto was_paused; @@ -1328,6 +1458,13 @@ gst_pulseringbuffer_commit (GstRingBuffer * buf, guint64 * sample, for (;;) { pbuf->m_writable = pa_stream_writable_size (pbuf->stream); +#ifdef HAVE_PULSE_1_0 + if (g_atomic_int_get (&psink->format_lost)) { + /* Sink format changed, give up and hope upstream renegotiates */ + goto fake_done; + } +#endif + if (pbuf->m_writable == (size_t) - 1) goto writable_size_failed; @@ -1386,6 +1523,14 @@ gst_pulseringbuffer_commit (GstRingBuffer * buf, guint64 * sample, GST_LOG_OBJECT (psink, "writing %u samples at offset %" G_GUINT64_FORMAT, (guint) avail, offset); +#ifdef HAVE_PULSE_1_0 + /* No trick modes for passthrough streams */ + if (G_UNLIKELY (inr != outr || reverse)) { + GST_WARNING_OBJECT (psink, "Passthrough stream can't run in trick mode"); + goto unlock_and_fail; + } +#endif + if (G_LIKELY (inr == outr && !reverse)) { /* no rate conversion, simply write out the samples */ /* copy the data into internal buffer */ @@ -1465,6 +1610,10 @@ gst_pulseringbuffer_commit (GstRingBuffer * buf, guint64 * sample, } } } + +#ifdef HAVE_PULSE_1_0 +fake_done: +#endif /* we consumed all samples here */ data = data_end + bps; @@ -1605,7 +1754,6 @@ static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink", "width = (int) 32, " "depth = (int) 32, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 32 ];" -#ifdef HAVE_PULSE_0_9_15 "audio/x-raw-int, " "endianness = (int) { " ENDIANNESS " }, " "signed = (boolean) TRUE, " @@ -1619,7 +1767,6 @@ static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink", "width = (int) 32, " "depth = (int) 24, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 32 ];" -#endif "audio/x-raw-int, " "signed = (boolean) FALSE, " "width = (int) 8, " @@ -1630,15 +1777,19 @@ static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink", "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 32 ];" "audio/x-mulaw, " - "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 32 ]") - ); + "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 32 ];" +#ifdef HAVE_PULSE_1_0 + "audio/x-ac3, framed = (boolean) true;" + "audio/x-eac3, framed = (boolean) true; " + "audio/x-dts, framed = (boolean) true, " + " block_size = (int) { 512, 1024, 2048 }; " + "audio/mpeg, mpegversion = (int)1, " + " mpegaudioversion = (int) [ 1, 2 ], parsed = (boolean) true; " +#endif + )); GST_IMPLEMENT_PULSEPROBE_METHODS (GstPulseSink, gst_pulsesink); -#define _do_init(type) \ - gst_pulsesink_init_contexts (); \ - gst_pulsesink_init_interfaces (type); - #define gst_pulsesink_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstPulseSink, gst_pulsesink, GST_TYPE_BASE_AUDIO_SINK, gst_pulsesink_init_contexts (); @@ -1659,6 +1810,40 @@ gst_pulsesink_create_ringbuffer (GstBaseAudioSink * sink) return buffer; } +static GstBuffer * +gst_pulsesink_payload (GstBaseAudioSink * sink, GstBuffer * buf) +{ + switch (sink->ringbuffer->spec.type) { + case GST_BUFTYPE_AC3: + case GST_BUFTYPE_EAC3: + case GST_BUFTYPE_DTS: + case GST_BUFTYPE_MPEG: + { + /* FIXME: alloc memory from PA if possible */ + gint framesize = gst_audio_iec61937_frame_size (&sink->ringbuffer->spec); + GstBuffer *out; + + if (framesize <= 0) + return NULL; + + out = gst_buffer_new_and_alloc (framesize); + + if (!gst_audio_iec61937_payload (GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf), GST_BUFFER_DATA (out), + GST_BUFFER_SIZE (out), &sink->ringbuffer->spec)) { + gst_buffer_unref (out); + return NULL; + } + + gst_buffer_copy_metadata (out, buf, GST_BUFFER_COPY_ALL); + return out; + } + + default: + return gst_buffer_ref (buf); + } +} + static void gst_pulsesink_class_init (GstPulseSinkClass * klass) { @@ -1683,6 +1868,7 @@ gst_pulsesink_class_init (GstPulseSinkClass * klass) gstaudiosink_class->create_ringbuffer = GST_DEBUG_FUNCPTR (gst_pulsesink_create_ringbuffer); + gstaudiosink_class->payload = GST_DEBUG_FUNCPTR (gst_pulsesink_payload); /* Overwrite GObject fields */ g_object_class_install_property (gobject_class, @@ -1771,6 +1957,14 @@ gst_pulsesink_get_time (GstClock * clock, GstBaseAudioSink * sink) pbuf = GST_PULSERING_BUFFER_CAST (sink->ringbuffer); psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf)); +#ifdef HAVE_PULSE_1_0 + if (g_atomic_int_get (&psink->format_lost)) { + /* Stream was lost in a format change, it'll get set up again once + * upstream renegotiates */ + return psink->format_lost_time; + } +#endif + pa_threaded_mainloop_lock (mainloop); if (gst_pulsering_is_dead (psink, pbuf, TRUE)) goto server_dead; @@ -1799,6 +1993,181 @@ server_dead: } } +static void +gst_pulsesink_sink_info_cb (pa_context * c, const pa_sink_info * i, int eol, + void *userdata) +{ + GstPulseRingBuffer *pbuf; + GstPulseSink *psink; +#ifdef HAVE_PULSE_1_0 + GList *l; + guint8 j; +#endif + + pbuf = GST_PULSERING_BUFFER_CAST (userdata); + psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf)); + + if (!i) + goto done; + + g_free (psink->device_description); + psink->device_description = g_strdup (i->description); + +#ifdef HAVE_PULSE_1_0 + g_mutex_lock (psink->sink_formats_lock); + + for (l = g_list_first (psink->sink_formats); l; l = g_list_next (l)) + pa_format_info_free ((pa_format_info *) l->data); + + g_list_free (psink->sink_formats); + psink->sink_formats = NULL; + + for (j = 0; j < i->n_formats; j++) + psink->sink_formats = g_list_prepend (psink->sink_formats, + pa_format_info_copy (i->formats[j])); + + g_mutex_unlock (psink->sink_formats_lock); +#endif + +done: + pa_threaded_mainloop_signal (mainloop, 0); +} + +#ifdef HAVE_PULSE_1_0 +static gboolean +gst_pulsesink_pad_acceptcaps (GstPad * pad, GstCaps * caps) +{ + GstPulseSink *psink = GST_PULSESINK (gst_pad_get_parent_element (pad)); + GstPulseRingBuffer *pbuf = GST_PULSERING_BUFFER_CAST (GST_BASE_AUDIO_SINK + (psink)->ringbuffer); + GstCaps *pad_caps; + GstStructure *st; + gboolean ret = FALSE; + + GstRingBufferSpec spec = { 0 }; + pa_stream *stream = NULL; + pa_operation *o = NULL; + pa_channel_map channel_map; + pa_stream_flags_t flags; + pa_format_info *format = NULL, *formats[1]; + guint channels; + + pad_caps = gst_pad_get_caps_reffed (pad); + if (pad_caps) { + ret = gst_caps_can_intersect (pad_caps, caps); + gst_caps_unref (pad_caps); + } + + /* Either template caps didn't match, or we're still in NULL state */ + if (!ret || !pbuf->context) + goto done; + + /* If we've not got fixed caps, creating a stream might fail, so let's just + * return from here with default acceptcaps behaviour */ + if (!gst_caps_is_fixed (caps)) + goto done; + + ret = FALSE; + + pa_threaded_mainloop_lock (mainloop); + + spec.latency_time = GST_BASE_AUDIO_SINK (psink)->latency_time; + if (!gst_ring_buffer_parse_caps (&spec, caps)) + goto out; + + if (!gst_pulse_fill_format_info (&spec, &format, &channels)) + goto out; + + /* Make sure input is framed (one frame per buffer) and can be payloaded */ + if (!pa_format_info_is_pcm (format)) { + gboolean framed = FALSE, parsed = FALSE; + st = gst_caps_get_structure (caps, 0); + + gst_structure_get_boolean (st, "framed", &framed); + gst_structure_get_boolean (st, "parsed", &parsed); + if ((!framed && !parsed) || gst_audio_iec61937_frame_size (&spec) <= 0) + goto out; + } + + /* initialize the channel map */ + if (pa_format_info_is_pcm (format) && + gst_pulse_gst_to_channel_map (&channel_map, &spec)) + pa_format_info_set_channel_map (format, &channel_map); + + if (pbuf->stream) { + /* We're already in PAUSED or above, so just reuse this stream to query + * sink formats and use those. */ + GList *i; + + if (!(o = pa_context_get_sink_info_by_name (pbuf->context, psink->device, + gst_pulsesink_sink_info_cb, pbuf))) + goto info_failed; + + while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait (mainloop); + if (gst_pulsering_is_dead (psink, pbuf, TRUE)) + goto out; + } + + g_mutex_lock (psink->sink_formats_lock); + for (i = g_list_first (psink->sink_formats); i; i = g_list_next (i)) { + if (pa_format_info_is_compatible ((pa_format_info *) i->data, format)) { + ret = TRUE; + break; + } + } + g_mutex_unlock (psink->sink_formats_lock); + } else { + /* We're in READY, let's connect a stream to see if the format is + * accpeted by whatever sink we're routed to */ + formats[0] = format; + + if (!(stream = pa_stream_new_extended (pbuf->context, "pulsesink probe", + formats, 1, psink->proplist))) + goto out; + + /* construct the flags */ + flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | + PA_STREAM_ADJUST_LATENCY | PA_STREAM_START_CORKED; + + pa_stream_set_state_callback (stream, gst_pulsering_stream_state_cb, pbuf); + + if (pa_stream_connect_playback (stream, psink->device, NULL, flags, NULL, + NULL) < 0) + goto out; + + ret = gst_pulsering_wait_for_stream_ready (psink, stream); + } + +out: + if (format) + pa_format_info_free (format); + + if (o) + pa_operation_unref (o); + + if (stream) { + pa_stream_set_state_callback (stream, NULL, NULL); + pa_stream_disconnect (stream); + pa_stream_unref (stream); + } + + pa_threaded_mainloop_unlock (mainloop); + +done: + gst_object_unref (psink); + return ret; + +info_failed: + { + GST_ELEMENT_ERROR (psink, RESOURCE, FAILED, + ("pa_context_get_sink_input_info() failed: %s", + pa_strerror (pa_context_errno (pbuf->context))), (NULL)); + goto out; + } +} +#endif + static void gst_pulsesink_init (GstPulseSink * pulsesink) { @@ -1807,6 +2176,11 @@ gst_pulsesink_init (GstPulseSink * pulsesink) pulsesink->device_description = NULL; pulsesink->client_name = gst_pulse_client_name (); +#ifdef HAVE_PULSE_1_0 + pulsesink->sink_formats_lock = g_mutex_new (); + pulsesink->sink_formats = NULL; +#endif + pulsesink->volume = DEFAULT_VOLUME; pulsesink->volume_set = FALSE; @@ -1815,6 +2189,11 @@ gst_pulsesink_init (GstPulseSink * pulsesink) pulsesink->notify = 0; +#ifdef HAVE_PULSE_1_0 + g_atomic_int_set (&pulsesink->format_lost, FALSE); + pulsesink->format_lost_time = GST_CLOCK_TIME_NONE; +#endif + pulsesink->properties = NULL; pulsesink->proplist = NULL; @@ -1826,6 +2205,11 @@ gst_pulsesink_init (GstPulseSink * pulsesink) gst_audio_clock_new ("GstPulseSinkClock", (GstAudioClockGetTimeFunc) gst_pulsesink_get_time, pulsesink); +#ifdef HAVE_PULSE_1_0 + gst_pad_set_acceptcaps_function (GST_BASE_SINK (pulsesink)->sinkpad, + GST_DEBUG_FUNCPTR (gst_pulsesink_pad_acceptcaps)); +#endif + /* TRUE for sinks, FALSE for sources */ pulsesink->probe = gst_pulseprobe_new (G_OBJECT (pulsesink), G_OBJECT_GET_CLASS (pulsesink), PROP_DEVICE, pulsesink->device, @@ -1836,12 +2220,23 @@ static void gst_pulsesink_finalize (GObject * object) { GstPulseSink *pulsesink = GST_PULSESINK_CAST (object); +#ifdef HAVE_PULSE_1_0 + GList *i; +#endif g_free (pulsesink->server); g_free (pulsesink->device); g_free (pulsesink->device_description); g_free (pulsesink->client_name); +#ifdef HAVE_PULSE_1_0 + for (i = g_list_first (pulsesink->sink_formats); i; i = g_list_next (i)) + pa_format_info_free ((pa_format_info *) i->data); + + g_list_free (pulsesink->sink_formats); + g_mutex_free (pulsesink->sink_formats_lock); +#endif + if (pulsesink->properties) gst_structure_free (pulsesink->properties); if (pulsesink->proplist) @@ -1877,7 +2272,16 @@ gst_pulsesink_set_volume (GstPulseSink * psink, gdouble volume) if ((idx = pa_stream_get_index (pbuf->stream)) == PA_INVALID_INDEX) goto no_index; +#ifdef HAVE_PULSE_1_0 + if (pa_format_info_is_pcm (pbuf->format)) + gst_pulse_cvolume_from_linear (&v, pbuf->channels, volume); + else + /* FIXME: this will eventually be superceded by checks to see if the volume + * is readable/writable */ + goto unlock; +#else gst_pulse_cvolume_from_linear (&v, pbuf->sample_spec.channels, volume); +#endif if (!(o = pa_context_set_sink_input_volume (pbuf->context, idx, &v, NULL, NULL))) @@ -2154,26 +2558,6 @@ info_failed: } } -static void -gst_pulsesink_sink_info_cb (pa_context * c, const pa_sink_info * i, int eol, - void *userdata) -{ - GstPulseRingBuffer *pbuf; - GstPulseSink *psink; - - pbuf = GST_PULSERING_BUFFER_CAST (userdata); - psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf)); - - if (!i) - goto done; - - g_free (psink->device_description); - psink->device_description = g_strdup (i->description); - -done: - pa_threaded_mainloop_signal (mainloop, 0); -} - static gchar * gst_pulsesink_device_description (GstPulseSink * psink) { @@ -2570,6 +2954,7 @@ gst_pulsesink_change_state (GstElement * element, GstStateChange transition) gst_message_new_clock_provide (GST_OBJECT_CAST (element), GST_BASE_AUDIO_SINK (pulsesink)->provided_clock, TRUE)); break; + default: break; } @@ -2580,6 +2965,7 @@ gst_pulsesink_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: + /* format_lost is reset in release() in baseaudiosink */ gst_element_post_message (element, gst_message_new_clock_lost (GST_OBJECT_CAST (element), GST_BASE_AUDIO_SINK (pulsesink)->provided_clock)); diff --git a/ext/pulse/pulsesink.h b/ext/pulse/pulsesink.h index e3029295ef..ad8831f393 100644 --- a/ext/pulse/pulsesink.h +++ b/ext/pulse/pulsesink.h @@ -72,6 +72,13 @@ struct _GstPulseSink GstStructure *properties; pa_proplist *proplist; + +#ifdef HAVE_PULSE_1_0 + GMutex *sink_formats_lock; + GList *sink_formats; + volatile gint format_lost; + GstClockTime format_lost_time; +#endif }; struct _GstPulseSinkClass diff --git a/ext/pulse/pulsesrc.c b/ext/pulse/pulsesrc.c index 66ffd69dc9..3ae8da9798 100644 --- a/ext/pulse/pulsesrc.c +++ b/ext/pulse/pulsesrc.c @@ -63,6 +63,7 @@ enum PROP_DEVICE_NAME, PROP_CLIENT, PROP_STREAM_PROPERTIES, + PROP_SOURCE_OUTPUT_INDEX, PROP_LAST }; @@ -153,6 +154,7 @@ gst_pulsesrc_class_init (GstPulseSrcClass * klass) GstAudioSrcClass *gstaudiosrc_class = GST_AUDIO_SRC_CLASS (klass); GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass); GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + gchar *clientname; gobject_class->finalize = gst_pulsesrc_finalize; gobject_class->set_property = gst_pulsesrc_set_property; @@ -189,6 +191,7 @@ gst_pulsesrc_class_init (GstPulseSrcClass * klass) "Human-readable name of the sound device", DEFAULT_DEVICE_NAME, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + clientname = gst_pulse_client_name (); /** * GstPulseSink:client * @@ -199,9 +202,10 @@ gst_pulsesrc_class_init (GstPulseSrcClass * klass) g_object_class_install_property (gobject_class, PROP_CLIENT, g_param_spec_string ("client", "Client", - "The PulseAudio client_name_to_use", gst_pulse_client_name (), + "The PulseAudio client_name_to_use", clientname, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)); + g_free (clientname); /** * GstPulseSrc:stream-properties @@ -225,6 +229,19 @@ gst_pulsesrc_class_init (GstPulseSrcClass * klass) g_param_spec_boxed ("stream-properties", "stream properties", "list of pulseaudio stream properties", GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstPulseSrc:source-output-index + * + * The index of the PulseAudio source output corresponding to this element. + * + * Since: 0.10.31 + */ + g_object_class_install_property (gobject_class, + PROP_SOURCE_OUTPUT_INDEX, + g_param_spec_uint ("source-output-index", "source output index", + "The index of the PulseAudio source output corresponding to this " + "record stream", 0, G_MAXUINT, PA_INVALID_INDEX, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); gst_element_class_set_details_simple (gstelement_class, "PulseAudio Audio Source", @@ -244,6 +261,7 @@ gst_pulsesrc_init (GstPulseSrc * pulsesrc) pulsesrc->context = NULL; pulsesrc->stream = NULL; + pulsesrc->source_output_idx = PA_INVALID_INDEX; pulsesrc->read_buffer = NULL; pulsesrc->read_buffer_length = 0; @@ -273,6 +291,8 @@ gst_pulsesrc_destroy_stream (GstPulseSrc * pulsesrc) pa_stream_disconnect (pulsesrc->stream); pa_stream_unref (pulsesrc->stream); pulsesrc->stream = NULL; + pulsesrc->source_output_idx = PA_INVALID_INDEX; + g_object_notify (G_OBJECT (pulsesrc), "source-output-index"); } g_free (pulsesrc->device_description); @@ -470,6 +490,9 @@ gst_pulsesrc_get_property (GObject * object, case PROP_STREAM_PROPERTIES: gst_value_set_structure (value, pulsesrc->properties); break; + case PROP_SOURCE_OUTPUT_INDEX: + g_value_set_uint (value, pulsesrc->source_output_idx); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1011,6 +1034,10 @@ gst_pulsesrc_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec) pa_threaded_mainloop_wait (pulsesrc->mainloop); } + /* store the source output index so it can be accessed via a property */ + pulsesrc->source_output_idx = pa_stream_get_index (pulsesrc->stream); + g_object_notify (G_OBJECT (pulsesrc), "source-output-index"); + /* get the actual buffering properties now */ actual = pa_stream_get_buffer_attr (pulsesrc->stream); diff --git a/ext/pulse/pulsesrc.h b/ext/pulse/pulsesrc.h index 6e6322b9c3..a308afd334 100644 --- a/ext/pulse/pulsesrc.h +++ b/ext/pulse/pulsesrc.h @@ -61,6 +61,7 @@ struct _GstPulseSrc pa_context *context; pa_stream *stream; + guint32 source_output_idx; pa_sample_spec sample_spec; diff --git a/ext/pulse/pulseutil.c b/ext/pulse/pulseutil.c index 8fbb3caece..0d8af79d90 100644 --- a/ext/pulse/pulseutil.c +++ b/ext/pulse/pulseutil.c @@ -118,6 +118,89 @@ gst_pulse_fill_sample_spec (GstRingBufferSpec * spec, pa_sample_spec * ss) return TRUE; } +#ifdef HAVE_PULSE_1_0 +gboolean +gst_pulse_fill_format_info (GstRingBufferSpec * spec, pa_format_info ** f, + guint * channels) +{ + pa_format_info *format; + pa_sample_format_t sf = PA_SAMPLE_INVALID; + + format = pa_format_info_new (); + + if (spec->format == GST_MU_LAW && spec->width == 8) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_ULAW; + } else if (spec->format == GST_A_LAW && spec->width == 8) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_ALAW; + } else if (spec->format == GST_U8 && spec->width == 8) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_U8; + } else if (spec->format == GST_S16_LE && spec->width == 16) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S16LE; + } else if (spec->format == GST_S16_BE && spec->width == 16) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S16BE; + } else if (spec->format == GST_FLOAT32_LE && spec->width == 32) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_FLOAT32LE; + } else if (spec->format == GST_FLOAT32_BE && spec->width == 32) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_FLOAT32BE; + } else if (spec->format == GST_S32_LE && spec->width == 32) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S32LE; + } else if (spec->format == GST_S32_BE && spec->width == 32) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S32BE; + } else if (spec->format == GST_S24_3LE && spec->width == 24) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S24LE; + } else if (spec->format == GST_S24_3BE && spec->width == 24) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S24BE; + } else if (spec->format == GST_S24_LE && spec->width == 32) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S24_32LE; + } else if (spec->format == GST_S24_BE && spec->width == 32) { + format->encoding = PA_ENCODING_PCM; + sf = PA_SAMPLE_S24_32BE; + } else if (spec->format == GST_AC3) { + format->encoding = PA_ENCODING_AC3_IEC61937; + } else if (spec->format == GST_EAC3) { + format->encoding = PA_ENCODING_EAC3_IEC61937; + } else if (spec->format == GST_DTS) { + format->encoding = PA_ENCODING_DTS_IEC61937; + } else if (spec->format == GST_MPEG) { + format->encoding = PA_ENCODING_MPEG_IEC61937; + } else { + goto fail; + } + + if (format->encoding == PA_ENCODING_PCM) { + pa_format_info_set_sample_format (format, sf); + pa_format_info_set_channels (format, spec->channels); + } + + pa_format_info_set_rate (format, spec->rate); + + if (!pa_format_info_valid (format)) + goto fail; + + *f = format; + *channels = spec->channels; + + return TRUE; + +fail: + if (format) + pa_format_info_free (format); + return FALSE; +} +#endif + /* PATH_MAX is not defined everywhere, e.g. on GNU Hurd */ #ifndef PATH_MAX #define PATH_MAX 4096 diff --git a/ext/pulse/pulseutil.h b/ext/pulse/pulseutil.h index ec04ffc0cb..91c8502e39 100644 --- a/ext/pulse/pulseutil.h +++ b/ext/pulse/pulseutil.h @@ -28,6 +28,10 @@ gboolean gst_pulse_fill_sample_spec (GstRingBufferSpec * spec, pa_sample_spec * ss); +#ifdef HAVE_PULSE_1_0 +gboolean gst_pulse_fill_format_info (GstRingBufferSpec * spec, + pa_format_info ** f, guint * channels); +#endif gchar *gst_pulse_client_name (void); diff --git a/gst/audioparsers/gstac3parse.c b/gst/audioparsers/gstac3parse.c index cca432d918..aa9156ec49 100644 --- a/gst/audioparsers/gstac3parse.c +++ b/gst/audioparsers/gstac3parse.c @@ -163,6 +163,8 @@ static gboolean gst_ac3_parse_check_valid_frame (GstBaseParse * parse, GstBaseParseFrame * frame, guint * size, gint * skipsize); static GstFlowReturn gst_ac3_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame); +static gboolean gst_ac3_parse_src_event (GstBaseParse * parse, + GstEvent * event); #define gst_ac3_parse_parent_class parent_class G_DEFINE_TYPE (GstAc3Parse, gst_ac3_parse, GST_TYPE_BASE_PARSE); @@ -193,6 +195,8 @@ gst_ac3_parse_class_init (GstAc3ParseClass * klass) parse_class->check_valid_frame = GST_DEBUG_FUNCPTR (gst_ac3_parse_check_valid_frame); parse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_ac3_parse_parse_frame); + + parse_class->src_event = GST_DEBUG_FUNCPTR (gst_ac3_parse_src_event); } static void @@ -202,6 +206,7 @@ gst_ac3_parse_reset (GstAc3Parse * ac3parse) ac3parse->sample_rate = -1; ac3parse->blocks = -1; ac3parse->eac = FALSE; + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_NONE); } static void @@ -237,9 +242,58 @@ gst_ac3_parse_stop (GstBaseParse * parse) return TRUE; } +static void +gst_ac3_parse_set_alignment (GstAc3Parse * ac3parse, gboolean eac) +{ + GstCaps *caps; + GstStructure *st; + const gchar *str = NULL; + int i; + + if (G_LIKELY (!eac)) + goto done; + + caps = gst_pad_get_allowed_caps (GST_BASE_PARSE_SRC_PAD (ac3parse)); + + if (!caps) + goto done; + + for (i = 0; i < gst_caps_get_size (caps); i++) { + st = gst_caps_get_structure (caps, i); + + if (!g_str_equal (gst_structure_get_name (st), "audio/x-eac3")) + continue; + + if ((str = gst_structure_get_string (st, "alignment"))) { + if (g_str_equal (str, "iec61937")) { + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_IEC61937); + GST_DEBUG_OBJECT (ac3parse, "picked iec61937 alignment"); + } else if (g_str_equal (str, "frame") == 0) { + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_FRAME); + GST_DEBUG_OBJECT (ac3parse, "picked frame alignment"); + } else { + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_FRAME); + GST_WARNING_OBJECT (ac3parse, "unknown alignment: %s", str); + } + break; + } + } + + if (caps) + gst_caps_unref (caps); + +done: + /* default */ + if (ac3parse->align == GST_AC3_PARSE_ALIGN_NONE) { + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_FRAME); + GST_DEBUG_OBJECT (ac3parse, "picked syncframe alignment"); + } +} + static gboolean gst_ac3_parse_frame_header_ac3 (GstAc3Parse * ac3parse, GstBuffer * buf, - guint * frame_size, guint * rate, guint * chans, guint * blks, guint * sid) + gint skip, guint * frame_size, guint * rate, guint * chans, guint * blks, + guint * sid) { GstBitReader bits; gpointer data; @@ -251,6 +305,7 @@ gst_ac3_parse_frame_header_ac3 (GstAc3Parse * ac3parse, GstBuffer * buf, data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ); gst_bit_reader_init (&bits, data, size); + gst_bit_reader_skip_unchecked (&bits, skip * 8); gst_bit_reader_skip_unchecked (&bits, 16 + 16); fscod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 2); @@ -304,7 +359,8 @@ cleanup: static gboolean gst_ac3_parse_frame_header_eac3 (GstAc3Parse * ac3parse, GstBuffer * buf, - guint * frame_size, guint * rate, guint * chans, guint * blks, guint * sid) + gint skip, guint * frame_size, guint * rate, guint * chans, guint * blks, + guint * sid) { GstBitReader bits; gpointer data; @@ -317,6 +373,7 @@ gst_ac3_parse_frame_header_eac3 (GstAc3Parse * ac3parse, GstBuffer * buf, data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ); gst_bit_reader_init (&bits, data, size); + gst_bit_reader_skip_unchecked (&bits, skip * 8); gst_bit_reader_skip_unchecked (&bits, 16); strmtyp = gst_bit_reader_get_bits_uint8_unchecked (&bits, 2); /* strmtyp */ @@ -367,7 +424,7 @@ cleanup: } static gboolean -gst_ac3_parse_frame_header (GstAc3Parse * parse, GstBuffer * buf, +gst_ac3_parse_frame_header (GstAc3Parse * parse, GstBuffer * buf, gint skip, guint * framesize, guint * rate, guint * chans, guint * blocks, guint * sid, gboolean * eac) { @@ -383,6 +440,8 @@ gst_ac3_parse_frame_header (GstAc3Parse * parse, GstBuffer * buf, GST_MEMDUMP_OBJECT (parse, "AC3 frame sync", data, MIN (size, 16)); + gst_bit_reader_skip_unchecked (&bits, skip * 8); + sync = gst_bit_reader_get_bits_uint16_unchecked (&bits, 16); gst_bit_reader_skip_unchecked (&bits, 16 + 8); bsid = gst_bit_reader_peek_bits_uint8_unchecked (&bits, 5); @@ -395,17 +454,18 @@ gst_ac3_parse_frame_header (GstAc3Parse * parse, GstBuffer * buf, if (bsid <= 10) { if (eac) *eac = FALSE; - ret = gst_ac3_parse_frame_header_ac3 (parse, buf, framesize, rate, chans, - blocks, sid); + ret = gst_ac3_parse_frame_header_ac3 (parse, buf, skip, framesize, rate, + chans, blocks, sid); goto cleanup; - } - - if (bsid <= 16) { + } else if (bsid <= 16) { if (eac) *eac = TRUE; - ret = gst_ac3_parse_frame_header_eac3 (parse, buf, framesize, rate, chans, - blocks, sid); + ret = gst_ac3_parse_frame_header_eac3 (parse, buf, skip, framesize, rate, + chans, blocks, sid); goto cleanup; + } else { + GST_DEBUG_OBJECT (parse, "unexpected bsid %d", bsid); + return FALSE; } GST_DEBUG_OBJECT (parse, "unexpected bsid %d", bsid); @@ -424,7 +484,9 @@ gst_ac3_parse_check_valid_frame (GstBaseParse * parse, GstBuffer *buf = frame->buffer; GstByteReader reader; gint off; - gboolean lost_sync, draining; + gboolean lost_sync, draining, eac, more = FALSE; + guint frmsiz, blocks, sid; + gint have_blocks = 0; gpointer data; gsize size; gboolean ret = FALSE; @@ -452,23 +514,69 @@ gst_ac3_parse_check_valid_frame (GstBaseParse * parse, } /* make sure the values in the frame header look sane */ - if (!gst_ac3_parse_frame_header (ac3parse, buf, framesize, NULL, NULL, - NULL, NULL, NULL)) { + if (!gst_ac3_parse_frame_header (ac3parse, buf, 0, &frmsiz, NULL, NULL, + &blocks, &sid, &eac)) { *skipsize = off + 2; goto cleanup; } + *framesize = frmsiz; + + if (G_UNLIKELY (g_atomic_int_get (&ac3parse->align) == + GST_AC3_PARSE_ALIGN_NONE)) + gst_ac3_parse_set_alignment (ac3parse, eac); + GST_LOG_OBJECT (parse, "got frame"); lost_sync = GST_BASE_PARSE_LOST_SYNC (parse); draining = GST_BASE_PARSE_DRAINING (parse); + if (g_atomic_int_get (&ac3parse->align) == GST_AC3_PARSE_ALIGN_IEC61937) { + /* We need 6 audio blocks from each substream, so we keep going forwards + * till we have it */ + + g_assert (blocks > 0); + GST_LOG_OBJECT (ac3parse, "Need %d frames before pushing", 6 / blocks); + + if (sid != 0) { + /* We need the first substream to be the one with id 0 */ + GST_LOG_OBJECT (ac3parse, "Skipping till we find sid 0"); + *skipsize = off + 2; + return FALSE; + } + + *framesize = 0; + + /* Loop till we have 6 blocks per substream */ + for (have_blocks = 0; !more && have_blocks < 6; have_blocks += blocks) { + /* Loop till we get one frame from each substream */ + do { + *framesize += frmsiz; + + if (!gst_byte_reader_skip (&reader, frmsiz) || + GST_BUFFER_SIZE (buf) < (*framesize + 6)) { + more = TRUE; + break; + } + + if (!gst_ac3_parse_frame_header (ac3parse, buf, *framesize, &frmsiz, + NULL, NULL, NULL, &sid, &eac)) { + *skipsize = off + 2; + return FALSE; + } + } while (sid); + } + + /* We're now at the next frame, so no need to skip if resyncing */ + frmsiz = 0; + } + if (lost_sync && !draining) { guint16 word = 0; GST_DEBUG_OBJECT (ac3parse, "resyncing; checking next frame syncword"); - if (!gst_byte_reader_skip (&reader, *framesize) || + if (more || !gst_byte_reader_skip (&reader, frmsiz) || !gst_byte_reader_get_uint16_be (&reader, &word)) { GST_DEBUG_OBJECT (ac3parse, "... but not sufficient data"); gst_base_parse_set_min_frame_size (parse, *framesize + 6); @@ -502,7 +610,7 @@ gst_ac3_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame) guint fsize, rate, chans, blocks, sid; gboolean eac, update_rate = FALSE; - if (!gst_ac3_parse_frame_header (ac3parse, buf, &fsize, &rate, &chans, + if (!gst_ac3_parse_frame_header (ac3parse, buf, 0, &fsize, &rate, &chans, &blocks, &sid, &eac)) goto broken_header; @@ -528,6 +636,9 @@ gst_ac3_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame) GstCaps *caps = gst_caps_new_simple (eac ? "audio/x-eac3" : "audio/x-ac3", "framed", G_TYPE_BOOLEAN, TRUE, "rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, chans, NULL); + gst_caps_set_simple (caps, "alignment", G_TYPE_STRING, + g_atomic_int_get (&ac3parse->align) == GST_AC3_PARSE_ALIGN_IEC61937 ? + "iec61937" : "frame", NULL); gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); gst_caps_unref (caps); @@ -557,3 +668,33 @@ broken_header: return GST_FLOW_ERROR; } } + +static gboolean +gst_ac3_parse_src_event (GstBaseParse * parse, GstEvent * event) +{ + GstAc3Parse *ac3parse = GST_AC3_PARSE (parse); + + if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) && + gst_event_has_name (event, "ac3parse-set-alignment")) { + const GstStructure *st = gst_event_get_structure (event); + const gchar *align = gst_structure_get_string (st, "alignment"); + + if (g_str_equal (align, "iec61937")) { + GST_DEBUG_OBJECT (ac3parse, "Switching to iec61937 alignment"); + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_IEC61937); + } else if (g_str_equal (align, "frame")) { + GST_DEBUG_OBJECT (ac3parse, "Switching to frame alignment"); + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_FRAME); + } else { + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_FRAME); + GST_WARNING_OBJECT (ac3parse, "Got unknown alignment request (%s) " + "reverting to frame alignment.", + gst_structure_get_string (st, "alignment")); + } + + gst_event_unref (event); + return TRUE; + } + + return GST_BASE_PARSE_CLASS (parent_class)->src_event (parse, event); +} diff --git a/gst/audioparsers/gstac3parse.h b/gst/audioparsers/gstac3parse.h index 13270bb8ad..545419f14f 100644 --- a/gst/audioparsers/gstac3parse.h +++ b/gst/audioparsers/gstac3parse.h @@ -42,6 +42,12 @@ G_BEGIN_DECLS typedef struct _GstAc3Parse GstAc3Parse; typedef struct _GstAc3ParseClass GstAc3ParseClass; +enum { + GST_AC3_PARSE_ALIGN_NONE, + GST_AC3_PARSE_ALIGN_FRAME, + GST_AC3_PARSE_ALIGN_IEC61937, +}; + /** * GstAc3Parse: * @@ -51,10 +57,11 @@ struct _GstAc3Parse { GstBaseParse baseparse; /*< private >*/ - gint sample_rate; - gint channels; - gint blocks; - gboolean eac; + gint sample_rate; + gint channels; + gint blocks; + gboolean eac; + volatile gint align; }; /** diff --git a/gst/auparse/gstauparse.c b/gst/auparse/gstauparse.c index 05aedcc745..f39fcbf04e 100644 --- a/gst/auparse/gstauparse.c +++ b/gst/auparse/gstauparse.c @@ -692,6 +692,12 @@ gst_au_parse_sink_event (GstPad * pad, GstEvent * event) gst_event_unref (event); break; } + case GST_EVENT_EOS: + if (!auparse->srcpad) { + GST_ELEMENT_ERROR (auparse, STREAM, WRONG_TYPE, + ("No valid input found before end of stream"), (NULL)); + } + /* fall-through */ default: ret = gst_pad_event_default (pad, event); break; diff --git a/gst/deinterlace/gstdeinterlace.c b/gst/deinterlace/gstdeinterlace.c index 415cb00691..c4d6cf94dd 100644 --- a/gst/deinterlace/gstdeinterlace.c +++ b/gst/deinterlace/gstdeinterlace.c @@ -609,7 +609,7 @@ gst_deinterlace_class_init (GstDeinterlaceClass * klass) * processing latency and accuracy of timestamp adjustment for telecine * streams. * - * Since: 0.10.29. + * Since: 0.10.31 * */ g_object_class_install_property (gobject_class, PROP_LOCKING, @@ -623,7 +623,7 @@ gst_deinterlace_class_init (GstDeinterlaceClass * klass) * This selects whether to ignore obscure/rare telecine patterns. * NTSC 2:3 pulldown variants are the only really common patterns. * - * Since: 0.10.29. + * Since: 0.10.31 * */ g_object_class_install_property (gobject_class, PROP_IGNORE_OBSCURE, @@ -638,7 +638,7 @@ gst_deinterlace_class_init (GstDeinterlaceClass * klass) * This selects whether to drop orphan fields at the beginning of telecine * patterns in active locking mode. * - * Since: 0.10.29. + * Since: 0.10.31 * */ g_object_class_install_property (gobject_class, PROP_DROP_ORPHANS, diff --git a/gst/goom/plugin_info.c b/gst/goom/plugin_info.c index aba15ca8fb..b50c9dd9ab 100644 --- a/gst/goom/plugin_info.c +++ b/gst/goom/plugin_info.c @@ -35,8 +35,6 @@ #if defined (HAVE_CPU_PPC64) || defined (HAVE_CPU_PPC) -#include -#include #include "ppc_zoom_ultimate.h" #include "ppc_drawings.h" #endif /* HAVE_CPU_PPC64 || HAVE_CPU_PPC */ diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index 21d8033cd2..5ce5476614 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -7474,7 +7474,7 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux) /* Subtract the header size */ GST_DEBUG_OBJECT (qtdemux, "Total size %" G_GINT64_FORMAT ", header size %u", size, qtdemux->header_size); - g_assert (size > qtdemux->header_size); + g_assert (size >= qtdemux->header_size); size = size - qtdemux->header_size; if (!gst_qtdemux_get_duration (qtdemux, &duration) || diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c index 0eac2b769c..c2d1e4caf0 100644 --- a/gst/matroska/matroska-demux.c +++ b/gst/matroska/matroska-demux.c @@ -4873,16 +4873,20 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * /* pixel width and height are the w and h of the video in pixels */ if (videocontext->pixel_width > 0 && videocontext->pixel_height > 0) { gint w = videocontext->pixel_width; - gint h = videocontext->pixel_height; gst_structure_set (structure, "width", G_TYPE_INT, w, "height", G_TYPE_INT, h, NULL); } - if (videocontext->display_width > 0 && videocontext->display_height > 0) { + if (videocontext->display_width > 0 || videocontext->display_height > 0) { int n, d; + if (videocontext->display_width <= 0) + videocontext->display_width = videocontext->pixel_width; + if (videocontext->display_height <= 0) + videocontext->display_height = videocontext->pixel_height; + /* calculate the pixel aspect ratio using the display and pixel w/h */ n = videocontext->display_width * videocontext->pixel_height; d = videocontext->display_height * videocontext->pixel_width; diff --git a/gst/matroska/matroska-read-common.c b/gst/matroska/matroska-read-common.c index 4431a51af8..32b513e12b 100644 --- a/gst/matroska/matroska-read-common.c +++ b/gst/matroska/matroska-read-common.c @@ -45,7 +45,7 @@ #include "ebml-read.h" #include "matroska-read-common.h" -GST_DEBUG_CATEGORY_STATIC (matroskareadcommon_debug); +GST_DEBUG_CATEGORY (matroskareadcommon_debug); #define GST_CAT_DEFAULT matroskareadcommon_debug #define DEBUG_ELEMENT_START(common, ebml, element) \ diff --git a/gst/matroska/matroska-read-common.h b/gst/matroska/matroska-read-common.h index 68b0f6c34f..cf617e6e8e 100644 --- a/gst/matroska/matroska-read-common.h +++ b/gst/matroska/matroska-read-common.h @@ -31,6 +31,8 @@ G_BEGIN_DECLS +GST_DEBUG_CATEGORY_EXTERN(matroskareadcommon_debug); + typedef enum { GST_MATROSKA_READ_STATE_START, GST_MATROSKA_READ_STATE_SEGMENT, diff --git a/gst/matroska/matroska.c b/gst/matroska/matroska.c index 57db7a31e0..5391400401 100644 --- a/gst/matroska/matroska.c +++ b/gst/matroska/matroska.c @@ -25,6 +25,7 @@ #include "matroska-demux.h" #include "matroska-parse.h" +#include "matroska-read-common.h" #include "matroska-mux.h" #include "matroska-ids.h" #include "webm-mux.h" @@ -40,6 +41,9 @@ plugin_init (GstPlugin * plugin) gst_matroska_register_tags (); + GST_DEBUG_CATEGORY_INIT (matroskareadcommon_debug, "matroskareadcommon", 0, + "Matroska demuxer/parser shared debug"); + ret = gst_matroska_demux_plugin_init (plugin); ret &= gst_matroska_parse_plugin_init (plugin); ret &= gst_element_register (plugin, "matroskamux", GST_RANK_PRIMARY, diff --git a/gst/multifile/gstmultifilesink.c b/gst/multifile/gstmultifilesink.c index 44b4e8a0c0..e47d5e2360 100644 --- a/gst/multifile/gstmultifilesink.c +++ b/gst/multifile/gstmultifilesink.c @@ -125,6 +125,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_multi_file_sink_debug); #define DEFAULT_INDEX 0 #define DEFAULT_POST_MESSAGES FALSE #define DEFAULT_NEXT_FILE GST_MULTI_FILE_SINK_NEXT_BUFFER +#define DEFAULT_MAX_FILES 0 enum { @@ -133,6 +134,7 @@ enum PROP_INDEX, PROP_POST_MESSAGES, PROP_NEXT_FILE, + PROP_MAX_FILES, PROP_LAST }; @@ -148,6 +150,12 @@ static GstFlowReturn gst_multi_file_sink_render (GstBaseSink * sink, GstBuffer * buffer); static gboolean gst_multi_file_sink_set_caps (GstBaseSink * sink, GstCaps * caps); +static gboolean gst_multi_file_sink_open_next_file (GstMultiFileSink * + multifilesink); +static void gst_multi_file_sink_close_file (GstMultiFileSink * multifilesink, + GstBuffer * buffer); +static void gst_multi_file_sink_ensure_max_files (GstMultiFileSink * + multifilesink); #define GST_TYPE_MULTI_FILE_SINK_NEXT (gst_multi_file_sink_next_get_type ()) static GType @@ -219,6 +227,22 @@ gst_multi_file_sink_class_init (GstMultiFileSinkClass * klass) GST_TYPE_MULTI_FILE_SINK_NEXT, DEFAULT_NEXT_FILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_STATIC_STRINGS)); + + /** + * GstMultiFileSink:max-files + * + * Maximum number of files to keep on disk. Once the maximum is reached, old + * files start to be deleted to make room for new ones. + * + * Since: 0.10.31 + */ + g_object_class_install_property (gobject_class, PROP_MAX_FILES, + g_param_spec_uint ("max-files", "Max files", + "Maximum number of files to keep on disk. Once the maximum is reached," + "old files start to be deleted to make room for new ones.", + 0, G_MAXUINT, DEFAULT_MAX_FILES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + gobject_class->finalize = gst_multi_file_sink_finalize; gstbasesink_class->get_times = NULL; @@ -244,6 +268,9 @@ gst_multi_file_sink_init (GstMultiFileSink * multifilesink) multifilesink->filename = g_strdup (DEFAULT_LOCATION); multifilesink->index = DEFAULT_INDEX; multifilesink->post_messages = DEFAULT_POST_MESSAGES; + multifilesink->max_files = DEFAULT_MAX_FILES; + multifilesink->files = NULL; + multifilesink->n_files = 0; gst_base_sink_set_sync (GST_BASE_SINK (multifilesink), FALSE); @@ -256,6 +283,8 @@ gst_multi_file_sink_finalize (GObject * object) GstMultiFileSink *sink = GST_MULTI_FILE_SINK (object); g_free (sink->filename); + g_slist_foreach (sink->files, (GFunc) g_free, NULL); + g_slist_free (sink->files); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -290,6 +319,9 @@ gst_multi_file_sink_set_property (GObject * object, guint prop_id, case PROP_NEXT_FILE: sink->next_file = g_value_get_enum (value); break; + case PROP_MAX_FILES: + sink->max_files = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -315,6 +347,9 @@ gst_multi_file_sink_get_property (GObject * object, guint prop_id, case PROP_NEXT_FILE: g_value_set_enum (value, sink->next_file); break; + case PROP_MAX_FILES: + g_value_set_uint (value, sink->max_files); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -399,39 +434,29 @@ gst_multi_file_sink_render (GstBaseSink * sink, GstBuffer * buffer) switch (multifilesink->next_file) { case GST_MULTI_FILE_SINK_NEXT_BUFFER: + gst_multi_file_sink_ensure_max_files (multifilesink); + filename = g_strdup_printf (multifilesink->filename, multifilesink->index); - ret = g_file_set_contents (filename, (char *) data, size, &error); if (!ret) goto write_error; + multifilesink->files = g_slist_append (multifilesink->files, filename); + multifilesink->n_files += 1; + gst_multi_file_sink_post_message (multifilesink, buffer, filename); multifilesink->index++; - g_free (filename); break; case GST_MULTI_FILE_SINK_NEXT_DISCONT: if (GST_BUFFER_IS_DISCONT (buffer)) { - if (multifilesink->file) { - fclose (multifilesink->file); - multifilesink->file = NULL; - - filename = g_strdup_printf (multifilesink->filename, - multifilesink->index); - gst_multi_file_sink_post_message (multifilesink, buffer, filename); - g_free (filename); - multifilesink->index++; - } + if (multifilesink->file) + gst_multi_file_sink_close_file (multifilesink, buffer); } if (multifilesink->file == NULL) { - filename = g_strdup_printf (multifilesink->filename, - multifilesink->index); - multifilesink->file = g_fopen (filename, "wb"); - g_free (filename); - - if (multifilesink->file == NULL) + if (!gst_multi_file_sink_open_next_file (multifilesink)) goto stdio_write_error; } @@ -451,16 +476,8 @@ gst_multi_file_sink_render (GstBaseSink * sink, GstBuffer * buffer) if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && GST_BUFFER_TIMESTAMP (buffer) >= multifilesink->next_segment && !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { - if (multifilesink->file) { - fclose (multifilesink->file); - multifilesink->file = NULL; - - filename = g_strdup_printf (multifilesink->filename, - multifilesink->index); - gst_multi_file_sink_post_message (multifilesink, buffer, filename); - g_free (filename); - multifilesink->index++; - } + if (multifilesink->file) + gst_multi_file_sink_close_file (multifilesink, buffer); multifilesink->next_segment += 10 * GST_SECOND; } @@ -468,12 +485,7 @@ gst_multi_file_sink_render (GstBaseSink * sink, GstBuffer * buffer) if (multifilesink->file == NULL) { int i; - filename = g_strdup_printf (multifilesink->filename, - multifilesink->index); - multifilesink->file = g_fopen (filename, "wb"); - g_free (filename); - - if (multifilesink->file == NULL) + if (!gst_multi_file_sink_open_next_file (multifilesink)) goto stdio_write_error; if (multifilesink->streamheaders) { @@ -571,3 +583,57 @@ gst_multi_file_sink_set_caps (GstBaseSink * sink, GstCaps * caps) return TRUE; } + +static void +gst_multi_file_sink_ensure_max_files (GstMultiFileSink * multifilesink) +{ + char *filename; + + while (multifilesink->max_files && + multifilesink->n_files >= multifilesink->max_files) { + filename = multifilesink->files->data; + g_remove (filename); + multifilesink->files = g_slist_delete_link (multifilesink->files, + multifilesink->files); + multifilesink->n_files -= 1; + } +} + +static gboolean +gst_multi_file_sink_open_next_file (GstMultiFileSink * multifilesink) +{ + char *filename; + + g_return_val_if_fail (multifilesink->file == NULL, FALSE); + + gst_multi_file_sink_ensure_max_files (multifilesink); + filename = g_strdup_printf (multifilesink->filename, multifilesink->index); + multifilesink->file = g_fopen (filename, "wb"); + if (multifilesink->file == NULL) { + g_free (filename); + return FALSE; + } + + multifilesink->files = g_slist_append (multifilesink->files, filename); + multifilesink->n_files += 1; + + return TRUE; +} + +static void +gst_multi_file_sink_close_file (GstMultiFileSink * multifilesink, + GstBuffer * buffer) +{ + char *filename; + + fclose (multifilesink->file); + multifilesink->file = NULL; + + if (buffer) { + filename = g_strdup_printf (multifilesink->filename, multifilesink->index); + gst_multi_file_sink_post_message (multifilesink, buffer, filename); + g_free (filename); + } + + multifilesink->index++; +} diff --git a/gst/multifile/gstmultifilesink.h b/gst/multifile/gstmultifilesink.h index c5515706a8..f2f005ff57 100644 --- a/gst/multifile/gstmultifilesink.h +++ b/gst/multifile/gstmultifilesink.h @@ -67,6 +67,9 @@ struct _GstMultiFileSink gboolean post_messages; GstMultiFileSinkNext next_file; FILE *file; + guint max_files; + GSList *files; + guint n_files; gint64 next_segment; diff --git a/gst/multipart/multipartdemux.c b/gst/multipart/multipartdemux.c index 722fcd2ece..b99c0fc65a 100644 --- a/gst/multipart/multipartdemux.c +++ b/gst/multipart/multipartdemux.c @@ -159,7 +159,7 @@ gst_multipart_demux_class_init (GstMultipartDemuxClass * klass) * not change and emit no-more-pads as soon as the first boundary * content is parsed, decoded, and pads are linked. * - * Since: 0.10.30 + * Since: 0.10.31 */ g_object_class_install_property (gobject_class, PROP_SINGLE_STREAM, g_param_spec_boolean ("single-stream", "Single Stream", diff --git a/gst/multipart/multipartmux.c b/gst/multipart/multipartmux.c index 1641910b01..d2a79061a4 100644 --- a/gst/multipart/multipartmux.c +++ b/gst/multipart/multipartmux.c @@ -517,7 +517,7 @@ gst_multipart_mux_collected (GstCollectPads * pads, GstMultipartMux * mux) GST_BUFFER_OFFSET_END (footerbuf) = mux->offset; GST_BUFFER_FLAG_SET (footerbuf, GST_BUFFER_FLAG_DELTA_UNIT); - GST_DEBUG_OBJECT (mux, "pushing %" G_GSIZE_FORMAT " bytes footer buffer", 2); + GST_DEBUG_OBJECT (mux, "pushing 2 bytes footer buffer"); ret = gst_pad_push (mux->srcpad, footerbuf); beach: diff --git a/gst/rtp/gstrtph264depay.c b/gst/rtp/gstrtph264depay.c index 8f4f3e760a..884d1f97c1 100644 --- a/gst/rtp/gstrtph264depay.c +++ b/gst/rtp/gstrtph264depay.c @@ -96,6 +96,8 @@ static GstBuffer *gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf); static gboolean gst_rtp_h264_depay_setcaps (GstBaseRTPDepayload * filter, GstCaps * caps); +static gboolean gst_rtp_h264_depay_handle_event (GstBaseRTPDepayload * depay, + GstEvent * event); static void gst_rtp_h264_depay_class_init (GstRtpH264DepayClass * klass) @@ -135,6 +137,7 @@ gst_rtp_h264_depay_class_init (GstRtpH264DepayClass * klass) gstbasertpdepayload_class->process = gst_rtp_h264_depay_process; gstbasertpdepayload_class->set_caps = gst_rtp_h264_depay_setcaps; + gstbasertpdepayload_class->handle_event = gst_rtp_h264_depay_handle_event; GST_DEBUG_CATEGORY_INIT (rtph264depay_debug, "rtph264depay", 0, "H264 Video RTP Depayloader"); @@ -149,6 +152,18 @@ gst_rtp_h264_depay_init (GstRtpH264Depay * rtph264depay) rtph264depay->merge = DEFAULT_ACCESS_UNIT; } +static void +gst_rtp_h264_depay_reset (GstRtpH264Depay * rtph264depay) +{ + gst_adapter_clear (rtph264depay->adapter); + rtph264depay->wait_start = TRUE; + gst_adapter_clear (rtph264depay->picture_adapter); + rtph264depay->picture_start = FALSE; + rtph264depay->last_keyframe = FALSE; + rtph264depay->last_ts = 0; + rtph264depay->current_fu_type = 0; +} + static void gst_rtp_h264_depay_finalize (GObject * object) { @@ -461,13 +476,34 @@ incomplete_caps: } } +static GstBuffer * +gst_rtp_h264_complete_au (GstRtpH264Depay * rtph264depay, + GstClockTime * out_timestamp, gboolean * out_keyframe) +{ + guint outsize; + GstBuffer *outbuf; + + /* we had a picture in the adapter and we completed it */ + GST_DEBUG_OBJECT (rtph264depay, "taking completed AU"); + outsize = gst_adapter_available (rtph264depay->picture_adapter); + outbuf = gst_adapter_take_buffer (rtph264depay->picture_adapter, outsize); + + *out_timestamp = rtph264depay->last_ts; + *out_keyframe = rtph264depay->last_keyframe; + + rtph264depay->last_keyframe = FALSE; + rtph264depay->picture_start = FALSE; + + return outbuf; +} + /* SPS/PPS/IDR considered key, all others DELTA; * so downstream waiting for keyframe can pick up at SPS/PPS/IDR */ #define NAL_TYPE_IS_KEY(nt) (((nt) == 5) || ((nt) == 7) || ((nt) == 8)) static gboolean gst_rtp_h264_depay_handle_nal (GstRtpH264Depay * rtph264depay, GstBuffer * nal, - GstClockTime in_timestamp) + GstClockTime in_timestamp, gboolean marker) { GstBaseRTPDepayload *depayload = GST_BASE_RTP_DEPAYLOAD (rtph264depay); gint nal_type; @@ -511,20 +547,9 @@ gst_rtp_h264_depay_handle_nal (GstRtpH264Depay * rtph264depay, GstBuffer * nal, } GST_DEBUG_OBJECT (depayload, "start %d, complete %d", start, complete); - if (complete && rtph264depay->picture_start) { - guint outsize; - - /* we had a picture in the adapter and we completed it */ - GST_DEBUG_OBJECT (depayload, "taking completed AU"); - outsize = gst_adapter_available (rtph264depay->picture_adapter); - outbuf = gst_adapter_take_buffer (rtph264depay->picture_adapter, outsize); - - out_timestamp = rtph264depay->last_ts; - out_keyframe = rtph264depay->last_keyframe; - - rtph264depay->last_keyframe = FALSE; - rtph264depay->picture_start = FALSE; - } + if (complete && rtph264depay->picture_start) + outbuf = gst_rtp_h264_complete_au (rtph264depay, &out_timestamp, + &out_keyframe); /* add to adapter */ GST_DEBUG_OBJECT (depayload, "adding NAL to picture adapter"); @@ -532,6 +557,10 @@ gst_rtp_h264_depay_handle_nal (GstRtpH264Depay * rtph264depay, GstBuffer * nal, rtph264depay->last_ts = in_timestamp; rtph264depay->last_keyframe |= keyframe; rtph264depay->picture_start |= start; + + if (marker) + outbuf = gst_rtp_h264_complete_au (rtph264depay, &out_timestamp, + &out_keyframe); } else { /* no merge, output is input nal */ GST_DEBUG_OBJECT (depayload, "using NAL as output"); @@ -571,6 +600,35 @@ short_nal: } } +static void +gst_rtp_h264_push_fragmentation_unit (GstRtpH264Depay * rtph264depay) +{ + guint outsize; + guint8 *outdata; + GstBuffer *outbuf; + + outsize = gst_adapter_available (rtph264depay->adapter); + outbuf = gst_adapter_take_buffer (rtph264depay->adapter, outsize); + outdata = GST_BUFFER_DATA (outbuf); + + GST_DEBUG_OBJECT (rtph264depay, "output %d bytes", outsize); + + if (rtph264depay->byte_stream) { + memcpy (outdata, sync_bytes, sizeof (sync_bytes)); + } else { + outsize -= 4; + outdata[0] = (outsize >> 24); + outdata[1] = (outsize >> 16); + outdata[2] = (outsize >> 8); + outdata[3] = (outsize); + } + + gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, + rtph264depay->fu_timestamp, rtph264depay->fu_marker); + + rtph264depay->current_fu_type = 0; +} + static GstBuffer * gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) { @@ -585,6 +643,7 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) if (GST_BUFFER_IS_DISCONT (buf)) { gst_adapter_clear (rtph264depay->adapter); rtph264depay->wait_start = TRUE; + rtph264depay->current_fu_type = 0; } { @@ -595,6 +654,7 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) guint8 *outdata; guint outsize, nalu_size; GstClockTime timestamp; + gboolean marker; timestamp = GST_BUFFER_TIMESTAMP (buf); @@ -602,6 +662,7 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) payload_len = gst_rtp_buffer_get_payload_len (&rtp); payload = gst_rtp_buffer_get_payload (&rtp); + marker = gst_rtp_buffer_get_marker (&rtp); GST_DEBUG_OBJECT (rtph264depay, "receiving %d bytes", payload_len); @@ -625,6 +686,13 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) GST_DEBUG_OBJECT (rtph264depay, "NRI %d, Type %d", nal_ref_idc, nal_unit_type); + /* If FU unit was being processed, but the current nal is of a different + * type. Assume that the remote payloader is buggy (didn't set the end bit + * when the FU ended) and send out what we gathered thusfar */ + if (G_UNLIKELY (rtph264depay->current_fu_type != 0 && + nal_unit_type != rtph264depay->current_fu_type)) + gst_rtp_h264_push_fragmentation_unit (rtph264depay); + switch (nal_unit_type) { case 0: case 30: @@ -686,7 +754,7 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) outsize = gst_adapter_available (rtph264depay->adapter); outbuf = gst_adapter_take_buffer (rtph264depay->adapter, outsize); - gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp); + gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp, marker); break; } case 26: @@ -725,6 +793,15 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) /* NAL unit starts here */ guint8 nal_header; + /* If a new FU unit started, while still processing an older one. + * Assume that the remote payloader is buggy (doesn't set the end + * bit) and send out what we've gathered thusfar */ + if (G_UNLIKELY (rtph264depay->current_fu_type != 0)) + gst_rtp_h264_push_fragmentation_unit (rtph264depay); + + rtph264depay->current_fu_type = nal_unit_type; + rtph264depay->fu_timestamp = timestamp; + rtph264depay->wait_start = FALSE; /* reconstruct NAL header */ @@ -766,28 +843,11 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) gst_adapter_push (rtph264depay->adapter, outbuf); } + rtph264depay->fu_marker = marker; + /* if NAL unit ends, flush the adapter */ - if (E) { - GST_DEBUG_OBJECT (rtph264depay, "output %d bytes", outsize); - - outsize = gst_adapter_available (rtph264depay->adapter); - outbuf = gst_adapter_take_buffer (rtph264depay->adapter, outsize); - - outdata = gst_buffer_map (outbuf, NULL, NULL, GST_MAP_WRITE); - if (rtph264depay->byte_stream) { - memcpy (outdata, sync_bytes, sizeof (sync_bytes)); - } else { - outsize -= 4; - outdata[0] = (outsize >> 24); - outdata[1] = (outsize >> 16); - outdata[2] = (outsize >> 8); - outdata[3] = (outsize); - outsize += 4; - } - gst_buffer_unmap (outbuf, outdata, outsize); - - gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp); - } + if (E) + gst_rtp_h264_push_fragmentation_unit (rtph264depay); break; } default: @@ -811,7 +871,7 @@ gst_rtp_h264_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) memcpy (outdata + sizeof (sync_bytes), payload, nalu_size); gst_buffer_unmap (outbuf, outdata, outsize); - gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp); + gst_rtp_h264_depay_handle_nal (rtph264depay, outbuf, timestamp, marker); break; } } @@ -843,6 +903,25 @@ not_implemented: } } +static gboolean +gst_rtp_h264_depay_handle_event (GstBaseRTPDepayload * depay, GstEvent * event) +{ + GstRtpH264Depay *rtph264depay; + + rtph264depay = GST_RTP_H264_DEPAY (depay); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + gst_rtp_h264_depay_reset (rtph264depay); + break; + default: + break; + } + + return + GST_BASE_RTP_DEPAYLOAD_CLASS (parent_class)->handle_event (depay, event); +} + static GstStateChangeReturn gst_rtp_h264_depay_change_state (GstElement * element, GstStateChange transition) @@ -856,11 +935,7 @@ gst_rtp_h264_depay_change_state (GstElement * element, case GST_STATE_CHANGE_NULL_TO_READY: break; case GST_STATE_CHANGE_READY_TO_PAUSED: - gst_adapter_clear (rtph264depay->adapter); - rtph264depay->wait_start = TRUE; - gst_adapter_clear (rtph264depay->picture_adapter); - rtph264depay->picture_start = FALSE; - rtph264depay->last_keyframe = FALSE; + gst_rtp_h264_depay_reset (rtph264depay); break; default: break; diff --git a/gst/rtp/gstrtph264depay.h b/gst/rtp/gstrtph264depay.h index e801f1fe82..f50ffe6a37 100644 --- a/gst/rtp/gstrtph264depay.h +++ b/gst/rtp/gstrtph264depay.h @@ -56,6 +56,11 @@ struct _GstRtpH264Depay gboolean picture_start; GstClockTime last_ts; gboolean last_keyframe; + + /* Work around broken payloaders wrt. FU-A & FU-B */ + guint8 current_fu_type; + GstClockTime fu_timestamp; + gboolean fu_marker; }; struct _GstRtpH264DepayClass diff --git a/gst/rtp/gstrtph264pay.c b/gst/rtp/gstrtph264pay.c index 52021cb7c8..7e08be3a45 100644 --- a/gst/rtp/gstrtph264pay.c +++ b/gst/rtp/gstrtph264pay.c @@ -423,6 +423,7 @@ gst_rtp_h264_pay_setcaps (GstBaseRTPPayload * basepayload, GstCaps * caps) guint8 *data, *bdata; gsize size, bsize; GstBuffer *buffer; + const gchar *alignment; rtph264pay = GST_RTP_H264_PAY (basepayload); @@ -432,6 +433,12 @@ gst_rtp_h264_pay_setcaps (GstBaseRTPPayload * basepayload, GstCaps * caps) * NALs */ gst_basertppayload_set_options (basepayload, "video", TRUE, "H264", 90000); + alignment = gst_structure_get_string (str, "alignment"); + if (alignment && !strcmp (alignment, "au")) + rtph264pay->au_alignment = TRUE; + else + rtph264pay->au_alignment = FALSE; + /* packetized AVC video has a codec_data */ if ((value = gst_structure_get_value (str, "codec_data"))) { guint num_sps, num_pps; @@ -777,7 +784,7 @@ gst_rtp_h264_pay_decode_nal (GstRtpH264Pay * payloader, static GstFlowReturn gst_rtp_h264_pay_payload_nal (GstBaseRTPPayload * basepayload, const guint8 * data, guint size, GstClockTime timestamp, - GstBuffer * buffer_orig); + GstBuffer * buffer_orig, gboolean end_of_au); static GstFlowReturn gst_rtp_h264_pay_send_sps_pps (GstBaseRTPPayload * basepayload, @@ -795,7 +802,7 @@ gst_rtp_h264_pay_send_sps_pps (GstBaseRTPPayload * basepayload, /* resend SPS */ data = gst_buffer_map (sps_buf, &size, NULL, GST_MAP_READ); ret = gst_rtp_h264_pay_payload_nal (basepayload, - data, size, timestamp, sps_buf); + data, size, timestamp, sps_buf, FALSE); gst_buffer_unmap (sps_buf, data, size); /* Not critical here; but throw a warning */ if (ret != GST_FLOW_OK) @@ -808,7 +815,7 @@ gst_rtp_h264_pay_send_sps_pps (GstBaseRTPPayload * basepayload, /* resend PPS */ data = gst_buffer_map (pps_buf, &size, NULL, GST_MAP_READ); ret = gst_rtp_h264_pay_payload_nal (basepayload, - data, size, timestamp, pps_buf); + data, size, timestamp, pps_buf, FALSE); gst_buffer_unmap (pps_buf, data, size); /* Not critical here; but throw a warning */ if (ret != GST_FLOW_OK) @@ -824,7 +831,7 @@ gst_rtp_h264_pay_send_sps_pps (GstBaseRTPPayload * basepayload, static GstFlowReturn gst_rtp_h264_pay_payload_nal (GstBaseRTPPayload * basepayload, const guint8 * data, guint size, GstClockTime timestamp, - GstBuffer * buffer_orig) + GstBuffer * buffer_orig, gboolean end_of_au) { GstRtpH264Pay *rtph264pay; GstFlowReturn ret; @@ -910,7 +917,7 @@ gst_rtp_h264_pay_payload_nal (GstBaseRTPPayload * basepayload, gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp); /* only set the marker bit on packets containing access units */ - if (IS_ACCESS_UNIT (nalType)) { + if (IS_ACCESS_UNIT (nalType) && end_of_au) { gst_rtp_buffer_set_marker (&rtp, 1); } @@ -1007,7 +1014,7 @@ gst_rtp_h264_pay_payload_nal (GstBaseRTPPayload * basepayload, end = 1; } if (IS_ACCESS_UNIT (nalType)) { - gst_rtp_buffer_set_marker (&rtp, end); + gst_rtp_buffer_set_marker (&rtp, end && end_of_au); } /* FU indicator */ @@ -1120,6 +1127,7 @@ gst_rtp_h264_pay_handle_buffer (GstBaseRTPPayload * basepayload, while (size > nal_length_size) { gint i; + gboolean end_of_au = FALSE; nal_len = 0; for (i = 0; i < nal_length_size; i++) { @@ -1138,9 +1146,16 @@ gst_rtp_h264_pay_handle_buffer (GstBaseRTPPayload * basepayload, nal_len); } + /* If we're at the end of the buffer, then we're at the end of the + * access unit + */ + if (rtph264pay->au_alignment && size - nal_len <= nal_length_size) { + end_of_au = TRUE; + } + ret = gst_rtp_h264_pay_payload_nal (basepayload, data, nal_len, timestamp, - buffer); + buffer, end_of_au); if (ret != GST_FLOW_OK) break; @@ -1242,6 +1257,7 @@ gst_rtp_h264_pay_handle_buffer (GstBaseRTPPayload * basepayload, for (i = 0; i < nal_queue->len; i++) { guint size; + gboolean end_of_au = FALSE; nal_len = g_array_index (nal_queue, guint, i); /* skip start code */ @@ -1257,10 +1273,22 @@ gst_rtp_h264_pay_handle_buffer (GstBaseRTPPayload * basepayload, for (; size > 1 && data[size - 1] == 0x0; size--) /* skip */ ; + /* If it's the last nal unit we have in non-bytestream mode, we can + * assume it's the end of an access-unit + * + * FIXME: We need to wait until the next packet or EOS to + * actually payload the NAL so we can know if the current NAL is + * the last one of an access unit or not if we are in bytestream mode + */ + if (rtph264pay->au_alignment && + rtph264pay->scan_mode != GST_H264_SCAN_MODE_BYTESTREAM && + i == nal_queue->len - 1) + end_of_au = TRUE; + /* put the data in one or more RTP packets */ ret = gst_rtp_h264_pay_payload_nal (basepayload, data, size, timestamp, - buffer); + buffer, end_of_au); if (ret != GST_FLOW_OK) { break; } diff --git a/gst/rtp/gstrtph264pay.h b/gst/rtp/gstrtph264pay.h index 19e5aaa269..6303e308a9 100644 --- a/gst/rtp/gstrtph264pay.h +++ b/gst/rtp/gstrtph264pay.h @@ -55,6 +55,7 @@ struct _GstRtpH264Pay GList *sps, *pps; gboolean packetized; + gboolean au_alignment; guint nal_length_size; GArray *queue; diff --git a/gst/rtp/gstrtpjpegpay.c b/gst/rtp/gstrtpjpegpay.c index 24fae79e00..3c8ca57e84 100644 --- a/gst/rtp/gstrtpjpegpay.c +++ b/gst/rtp/gstrtpjpegpay.c @@ -89,6 +89,7 @@ typedef enum _RtpJpegMarker RtpJpegMarker; * @JPEG_MARKER_SOS: Start of Scan marker * @JPEG_MARKER_EOI: End of Image marker * @JPEG_MARKER_DRI: Define Restart Interval marker + * @JPEG_MARKER_H264: H264 marker * * Identifers for markers in JPEG header */ @@ -103,7 +104,8 @@ enum _RtpJpegMarker JPEG_MARKER_DHT = 0xC4, JPEG_MARKER_SOS = 0xDA, JPEG_MARKER_EOI = 0xD9, - JPEG_MARKER_DRI = 0xDD + JPEG_MARKER_DRI = 0xDD, + JPEG_MARKER_H264 = 0xE4 }; #define DEFAULT_JPEG_QUANT 255 @@ -586,7 +588,7 @@ gst_rtp_jpeg_pay_scan_marker (const guint8 * data, guint size, guint * offset) guint8 marker; marker = data[*offset]; - GST_LOG ("found %02x marker at offset %u", marker, *offset); + GST_LOG ("found 0x%02x marker at offset %u", marker, *offset); (*offset)++; return marker; } @@ -638,6 +640,7 @@ gst_rtp_jpeg_pay_handle_buffer (GstBaseRTPPayload * basepayload, case JPEG_MARKER_JFIF: case JPEG_MARKER_CMT: case JPEG_MARKER_DHT: + case JPEG_MARKER_H264: GST_LOG_OBJECT (pay, "skipping marker"); offset += gst_rtp_jpeg_pay_header_size (data, offset); break; diff --git a/gst/rtpmanager/gstrtpssrcdemux.c b/gst/rtpmanager/gstrtpssrcdemux.c index 4527b8f605..13573c85c7 100644 --- a/gst/rtpmanager/gstrtpssrcdemux.c +++ b/gst/rtpmanager/gstrtpssrcdemux.c @@ -83,8 +83,8 @@ GST_STATIC_PAD_TEMPLATE ("rtcp_src_%d", GST_STATIC_CAPS ("application/x-rtcp") ); -#define GST_PAD_LOCK(obj) (g_mutex_lock ((obj)->padlock)) -#define GST_PAD_UNLOCK(obj) (g_mutex_unlock ((obj)->padlock)) +#define GST_PAD_LOCK(obj) (g_static_rec_mutex_lock (&(obj)->padlock)) +#define GST_PAD_UNLOCK(obj) (g_static_rec_mutex_unlock (&(obj)->padlock)) /* signals */ enum @@ -155,6 +155,7 @@ find_demux_pad_for_ssrc (GstRtpSsrcDemux * demux, guint32 ssrc) return NULL; } +/* with PAD_LOCK */ static GstRtpSsrcDemuxPad * find_or_create_demux_pad_for_ssrc (GstRtpSsrcDemux * demux, guint32 ssrc) { @@ -167,11 +168,8 @@ find_or_create_demux_pad_for_ssrc (GstRtpSsrcDemux * demux, guint32 ssrc) GST_DEBUG_OBJECT (demux, "creating pad for SSRC %08x", ssrc); - GST_OBJECT_LOCK (demux); - demuxpad = find_demux_pad_for_ssrc (demux, ssrc); if (demuxpad != NULL) { - GST_OBJECT_UNLOCK (demux); return demuxpad; } @@ -218,8 +216,6 @@ find_or_create_demux_pad_for_ssrc (GstRtpSsrcDemux * demux, guint32 ssrc) gst_rtp_ssrc_demux_iterate_internal_links_src); gst_pad_set_active (rtcp_pad, TRUE); - GST_OBJECT_UNLOCK (demux); - gst_element_add_pad (GST_ELEMENT_CAST (demux), rtp_pad); gst_element_add_pad (GST_ELEMENT_CAST (demux), rtcp_pad); @@ -333,7 +329,7 @@ gst_rtp_ssrc_demux_init (GstRtpSsrcDemux * demux) gst_rtp_ssrc_demux_iterate_internal_links_sink); gst_element_add_pad (GST_ELEMENT_CAST (demux), demux->rtcp_sink); - demux->padlock = g_mutex_new (); + g_static_rec_mutex_init (&demux->padlock); gst_segment_init (&demux->segment, GST_FORMAT_UNDEFINED); } @@ -375,7 +371,7 @@ gst_rtp_ssrc_demux_finalize (GObject * object) GstRtpSsrcDemux *demux; demux = GST_RTP_SSRC_DEMUX (object); - g_mutex_free (demux->padlock); + g_static_rec_mutex_free (&demux->padlock); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -514,6 +510,7 @@ gst_rtp_ssrc_demux_chain (GstPad * pad, GstBuffer * buf) guint32 ssrc; GstRtpSsrcDemuxPad *dpad; GstRTPBuffer rtp; + GstPad *srcpad; demux = GST_RTP_SSRC_DEMUX (GST_OBJECT_PARENT (pad)); @@ -526,12 +523,19 @@ gst_rtp_ssrc_demux_chain (GstPad * pad, GstBuffer * buf) GST_DEBUG_OBJECT (demux, "received buffer of SSRC %08x", ssrc); + GST_PAD_LOCK (demux); dpad = find_or_create_demux_pad_for_ssrc (demux, ssrc); - if (dpad == NULL) + if (dpad == NULL) { + GST_PAD_UNLOCK (demux); goto create_failed; + } + srcpad = gst_object_ref (dpad->rtp_pad); + GST_PAD_UNLOCK (demux); /* push to srcpad */ - ret = gst_pad_push (dpad->rtp_pad, buf); + ret = gst_pad_push (srcpad, buf); + + gst_object_unref (srcpad); return ret; @@ -562,6 +566,7 @@ gst_rtp_ssrc_demux_rtcp_chain (GstPad * pad, GstBuffer * buf) GstRtpSsrcDemuxPad *dpad; GstRTCPPacket packet; GstRTCPBuffer rtcp; + GstPad *srcpad; demux = GST_RTP_SSRC_DEMUX (GST_OBJECT_PARENT (pad)); @@ -588,13 +593,19 @@ gst_rtp_ssrc_demux_rtcp_chain (GstPad * pad, GstBuffer * buf) GST_DEBUG_OBJECT (demux, "received RTCP of SSRC %08x", ssrc); + GST_PAD_LOCK (demux); dpad = find_or_create_demux_pad_for_ssrc (demux, ssrc); - if (dpad == NULL) + if (dpad == NULL) { + GST_PAD_UNLOCK (demux); goto create_failed; - + } + srcpad = gst_object_ref (dpad->rtcp_pad); + GST_PAD_UNLOCK (demux); /* push to srcpad */ - ret = gst_pad_push (dpad->rtcp_pad, buf); + ret = gst_pad_push (srcpad, buf); + + gst_object_unref (srcpad); return ret; diff --git a/gst/rtpmanager/gstrtpssrcdemux.h b/gst/rtpmanager/gstrtpssrcdemux.h index d5a13caf4b..6f792d9682 100644 --- a/gst/rtpmanager/gstrtpssrcdemux.h +++ b/gst/rtpmanager/gstrtpssrcdemux.h @@ -41,7 +41,7 @@ struct _GstRtpSsrcDemux GstPad *rtp_sink; GstPad *rtcp_sink; - GMutex *padlock; + GStaticRecMutex padlock; GSList *srcpads; }; diff --git a/gst/rtpmanager/rtpsession.c b/gst/rtpmanager/rtpsession.c index bc483c3f6b..41784128f0 100644 --- a/gst/rtpmanager/rtpsession.c +++ b/gst/rtpmanager/rtpsession.c @@ -60,6 +60,7 @@ enum #define DEFAULT_SOURCES NULL #define DEFAULT_RTCP_MIN_INTERVAL (RTP_STATS_MIN_INTERVAL * GST_SECOND) #define DEFAULT_RTCP_FEEDBACK_RETENTION_WINDOW (2 * GST_SECOND) +#define DEFAULT_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD (3) enum { @@ -78,6 +79,7 @@ enum PROP_FAVOR_NEW, PROP_RTCP_MIN_INTERVAL, PROP_RTCP_FEEDBACK_RETENTION_WINDOW, + PROP_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD, PROP_LAST }; @@ -492,6 +494,14 @@ rtp_session_class_init (RTPSessionClass * klass) 0, G_MAXUINT64, DEFAULT_RTCP_FEEDBACK_RETENTION_WINDOW, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD, + g_param_spec_uint ("rtcp-immediate-feedback-threshold", + "RTCP Immediate Feedback threshold", + "The maximum number of members of a RTP session for which immediate" + " feedback is used", + 0, G_MAXUINT, DEFAULT_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); klass->get_source_by_ssrc = GST_DEBUG_FUNCPTR (rtp_session_get_source_by_ssrc); @@ -534,6 +544,9 @@ rtp_session_init (RTPSession * sess) sess->stats.active_sources++; INIT_AVG (sess->stats.avg_rtcp_packet_size, 100); + rtp_stats_set_min_interval (&sess->stats, + (gdouble) DEFAULT_RTCP_MIN_INTERVAL / GST_SECOND); + /* default UDP header length */ sess->header_len = 28; sess->mtu = DEFAULT_RTCP_MTU; @@ -550,6 +563,8 @@ rtp_session_init (RTPSession * sess) sess->first_rtcp = TRUE; sess->allow_early = TRUE; sess->rtcp_feedback_retention_window = DEFAULT_RTCP_FEEDBACK_RETENTION_WINDOW; + sess->rtcp_immediate_feedback_threshold = + DEFAULT_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD; sess->rtcp_pli_requests = g_array_new (FALSE, FALSE, sizeof (guint32)); @@ -649,6 +664,9 @@ rtp_session_set_property (GObject * object, guint prop_id, rtp_stats_set_min_interval (&sess->stats, (gdouble) g_value_get_uint64 (value) / GST_SECOND); break; + case PROP_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD: + sess->rtcp_immediate_feedback_threshold = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -703,6 +721,9 @@ rtp_session_get_property (GObject * object, guint prop_id, case PROP_RTCP_MIN_INTERVAL: g_value_set_uint64 (value, sess->stats.min_interval * GST_SECOND); break; + case PROP_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD: + g_value_set_uint (value, sess->rtcp_immediate_feedback_threshold); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2465,7 +2486,7 @@ calculate_rtcp_interval (RTPSession * sess, gboolean deterministic, g_hash_table_foreach (sess->cnames, (GHFunc) add_bitrates, &bandwidth); bandwidth /= 8.0; } - if (bandwidth == 0) + if (bandwidth < 8000) bandwidth = RTP_STATS_BANDWIDTH; rtp_stats_set_bandwidths (&sess->stats, bandwidth, @@ -3083,7 +3104,8 @@ rtp_session_on_timeout (RTPSession * sess, GstClockTime current_time, /* check for outdated collisions */ GST_DEBUG ("Timing out collisions"); rtp_source_timeout (sess->source, current_time, - data.interval * RTCP_INTERVAL_COLLISION_TIMEOUT, + /* "a relatively long time" -- RFC 3550 section 8.2 */ + RTP_STATS_MIN_INTERVAL * GST_SECOND * 10, running_time - sess->rtcp_feedback_retention_window); if (sess->change_ssrc) { @@ -3175,8 +3197,13 @@ rtp_session_request_early_rtcp (RTPSession * sess, GstClockTime current_time, if (current_time + T_dither_max > sess->next_rtcp_check_time) goto dont_send; - /* RFC 4585 section 3.5.2 step 4 */ - if (sess->allow_early == FALSE) + /* RFC 4585 section 3.5.2 step 4 + * Don't send if allow_early is FALSE, but not if we are in + * immediate mode, meaning we are part of a group of at most the + * application-specific threshold. + */ + if (sess->total_sources > sess->rtcp_immediate_feedback_threshold && + sess->allow_early == FALSE) goto dont_send; if (T_dither_max) { diff --git a/gst/rtpmanager/rtpsession.h b/gst/rtpmanager/rtpsession.h index 93fd300d95..30b74bb5db 100644 --- a/gst/rtpmanager/rtpsession.h +++ b/gst/rtpmanager/rtpsession.h @@ -232,6 +232,7 @@ struct _RTPSession { gboolean change_ssrc; gboolean favor_new; GstClockTime rtcp_feedback_retention_window; + guint rtcp_immediate_feedback_threshold; GArray *rtcp_pli_requests; GstClockTime last_keyframe_request; diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index f20230889a..6bcdbb216e 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -510,6 +510,8 @@ gst_rtspsrc_init (GstRTSPSrc * src) g_static_rec_mutex_init (src->state_rec_lock); src->state = GST_RTSP_STATE_INVALID; + + GST_OBJECT_FLAG_SET (src, GST_ELEMENT_IS_SOURCE); } static void @@ -5438,7 +5440,6 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp, } src->state = GST_RTSP_STATE_INIT; - GST_OBJECT_FLAG_SET (src, GST_ELEMENT_IS_SOURCE); /* setup streams */ if ((res = gst_rtspsrc_setup_streams (src, async)) < 0) diff --git a/sys/v4l2/gstv4l2object.c b/sys/v4l2/gstv4l2object.c index 1104db4f7b..e0fa836dd2 100644 --- a/sys/v4l2/gstv4l2object.c +++ b/sys/v4l2/gstv4l2object.c @@ -465,7 +465,7 @@ gst_v4l2_object_install_properties_helper (GObjectClass * gobject_class, * * TV norm * - * Since: 0.10.30 + * Since: 0.10.31 */ g_object_class_install_property (gobject_class, PROP_TV_NORM, g_param_spec_enum ("norm", "TV norm", diff --git a/tests/check/elements/ac3parse.c b/tests/check/elements/ac3parse.c index 03e8e1dc85..eb25004971 100644 --- a/tests/check/elements/ac3parse.c +++ b/tests/check/elements/ac3parse.c @@ -110,7 +110,7 @@ GST_END_TEST; GST_START_TEST (test_parse_detect_stream) { gst_parser_test_output_caps (ac3_frame, sizeof (ac3_frame), - NULL, SINK_CAPS_TMPL ",channels=1,rate=48000"); + NULL, SINK_CAPS_TMPL ",channels=1,rate=48000,alignment=frame"); } GST_END_TEST; diff --git a/tests/check/elements/multifile.c b/tests/check/elements/multifile.c index 6fe527effb..479e028b66 100644 --- a/tests/check/elements/multifile.c +++ b/tests/check/elements/multifile.c @@ -54,7 +54,7 @@ g_mkdtemp (const gchar * template) return tmpdir; } -GST_START_TEST (test_multifilesink) +GST_START_TEST (test_multifilesink_key_frame) { GstElement *pipeline; GstElement *mfs; @@ -98,6 +98,57 @@ GST_START_TEST (test_multifilesink) GST_END_TEST; +GST_START_TEST (test_multifilesink_max_files) +{ + GstElement *pipeline; + GstElement *mfs; + int i; + const gchar *tmpdir; + gchar *my_tmpdir; + gchar *template; + gchar *mfs_pattern; + + tmpdir = g_get_tmp_dir (); + template = g_build_filename (tmpdir, "multifile-test-XXXXXX", NULL); + my_tmpdir = g_mkdtemp (template); + fail_if (my_tmpdir == NULL); + + pipeline = + gst_parse_launch + ("videotestsrc num-buffers=10 ! video/x-raw-yuv,format=(fourcc)I420,width=320,height=240 ! multifilesink name=mfs", + NULL); + fail_if (pipeline == NULL); + mfs = gst_bin_get_by_name (GST_BIN (pipeline), "mfs"); + fail_if (mfs == NULL); + mfs_pattern = g_build_filename (my_tmpdir, "%05d", NULL); + g_object_set (G_OBJECT (mfs), "location", mfs_pattern, "max-files", 3, NULL); + g_object_unref (mfs); + run_pipeline (pipeline); + gst_object_unref (pipeline); + + for (i = 0; i < 7; i++) { + char *s; + + s = g_strdup_printf (mfs_pattern, i); + fail_unless (g_remove (s) != 0); + g_free (s); + } + for (i = 7; i < 10; i++) { + char *s; + + s = g_strdup_printf (mfs_pattern, i); + fail_if (g_remove (s) != 0); + g_free (s); + } + fail_if (g_remove (my_tmpdir) != 0); + + g_free (mfs_pattern); + g_free (my_tmpdir); + g_free (template); +} + +GST_END_TEST; + GST_START_TEST (test_multifilesrc) { GstElement *pipeline; @@ -164,7 +215,8 @@ libvisual_suite (void) suite_add_tcase (s, tc_chain); - tcase_add_test (tc_chain, test_multifilesink); + tcase_add_test (tc_chain, test_multifilesink_key_frame); + tcase_add_test (tc_chain, test_multifilesink_max_files); tcase_add_test (tc_chain, test_multifilesrc); return s;