static gboolean gst_audio_aggregator_sink_event (GstAggregator * agg, GstAggregatorPad * aggpad, GstEvent * event) { gboolean res = TRUE; GST_DEBUG_OBJECT (aggpad, "Got %s event on sink pad", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEGMENT: { const GstSegment *segment; gst_event_parse_segment (event, &segment); if (segment->format != GST_FORMAT_TIME) { GST_ERROR_OBJECT (agg, "Segment of type %s are not supported," " only TIME segments are supported", gst_format_get_name (segment->format)); gst_event_unref (event); event = NULL; res = FALSE; break; } GST_OBJECT_LOCK (agg); if (segment->rate != agg->segment.rate) { GST_ERROR_OBJECT (aggpad, "Got segment event with wrong rate %lf, expected %lf", segment->rate, agg->segment.rate); res = FALSE; gst_event_unref (event); event = NULL; } else if (segment->rate < 0.0) { GST_ERROR_OBJECT (aggpad, "Negative rates not supported yet"); res = FALSE; gst_event_unref (event); event = NULL; } else { GstAudioAggregatorPad *pad = GST_AUDIO_AGGREGATOR_PAD (aggpad); GST_OBJECT_LOCK (pad); pad->priv->new_segment = TRUE; GST_OBJECT_UNLOCK (pad); } GST_OBJECT_UNLOCK (agg); break; } default: break; } if (event != NULL) return GST_AGGREGATOR_CLASS (gst_audio_aggregator_parent_class)->sink_event (agg, aggpad, event); return res; }
static gboolean gst_audio_aggregator_pad_flush_pad (GstAggregatorPad * aggpad, GstAggregator * aggregator) { GstAudioAggregatorPad *pad = GST_AUDIO_AGGREGATOR_PAD (aggpad); GST_OBJECT_LOCK (aggpad); pad->priv->position = pad->priv->size = 0; pad->priv->output_offset = pad->priv->next_offset = -1; pad->priv->discont_time = GST_CLOCK_TIME_NONE; gst_buffer_replace (&pad->priv->buffer, NULL); GST_OBJECT_UNLOCK (aggpad); return TRUE; }
static GstBuffer * gst_audio_aggregator_do_clip (GstAggregator * agg, GstAggregatorPad * bpad, GstBuffer * buffer) { GstAudioAggregatorPad *pad = GST_AUDIO_AGGREGATOR_PAD (bpad); gint rate, bpf; rate = GST_AUDIO_INFO_RATE (&pad->info); bpf = GST_AUDIO_INFO_BPF (&pad->info); GST_OBJECT_LOCK (bpad); buffer = gst_audio_buffer_clip (buffer, &bpad->segment, rate, bpf); GST_OBJECT_UNLOCK (bpad); return buffer; }
/* the first caps we receive on any of the sinkpads will define the caps for all * the other sinkpads because we can only mix streams with the same caps. */ static gboolean gst_audiomixer_setcaps (GstAudioMixer * audiomixer, GstPad * pad, GstCaps * orig_caps) { GstAggregator *agg = GST_AGGREGATOR (audiomixer); GstAudioAggregator *aagg = GST_AUDIO_AGGREGATOR (audiomixer); GstCaps *caps; GstAudioInfo info; GstStructure *s; gint channels = 0; caps = gst_caps_copy (orig_caps); s = gst_caps_get_structure (caps, 0); if (gst_structure_get_int (s, "channels", &channels)) if (channels <= 2) gst_structure_remove_field (s, "channel-mask"); if (!gst_audio_info_from_caps (&info, caps)) goto invalid_format; if (channels == 1) { GstCaps *filter; GstCaps *downstream_caps; if (audiomixer->filter_caps) filter = gst_caps_intersect_full (caps, audiomixer->filter_caps, GST_CAPS_INTERSECT_FIRST); else filter = gst_caps_ref (caps); downstream_caps = gst_pad_peer_query_caps (agg->srcpad, filter); gst_caps_unref (filter); if (downstream_caps) { gst_caps_unref (caps); caps = downstream_caps; if (gst_caps_is_empty (caps)) { gst_caps_unref (caps); return FALSE; } caps = gst_caps_fixate (caps); } } GST_OBJECT_LOCK (audiomixer); /* don't allow reconfiguration for now; there's still a race between the * different upstream threads doing query_caps + accept_caps + sending * (possibly different) CAPS events, but there's not much we can do about * that, upstream needs to deal with it. */ if (aagg->current_caps != NULL) { if (gst_audio_info_is_equal (&info, &aagg->info)) { GST_OBJECT_UNLOCK (audiomixer); gst_caps_unref (caps); gst_audio_aggregator_set_sink_caps (aagg, GST_AUDIO_AGGREGATOR_PAD (pad), orig_caps); return TRUE; } else { GST_DEBUG_OBJECT (pad, "got input caps %" GST_PTR_FORMAT ", but " "current caps are %" GST_PTR_FORMAT, caps, aagg->current_caps); GST_OBJECT_UNLOCK (audiomixer); gst_pad_push_event (pad, gst_event_new_reconfigure ()); gst_caps_unref (caps); return FALSE; } } else { gst_caps_replace (&aagg->current_caps, caps); aagg->info = info; gst_pad_mark_reconfigure (GST_AGGREGATOR_SRC_PAD (agg)); } GST_OBJECT_UNLOCK (audiomixer); gst_audio_aggregator_set_sink_caps (aagg, GST_AUDIO_AGGREGATOR_PAD (pad), orig_caps); GST_INFO_OBJECT (pad, "handle caps change to %" GST_PTR_FORMAT, caps); gst_caps_unref (caps); return TRUE; /* ERRORS */ invalid_format: { gst_caps_unref (caps); GST_WARNING_OBJECT (audiomixer, "invalid format set as caps"); return FALSE; } }
static GstFlowReturn gst_audio_aggregator_aggregate (GstAggregator * agg, gboolean timeout) { /* Get all pads that have data for us and store them in a * new list. * * Calculate the current output offset/timestamp and * offset_end/timestamp_end. Allocate a silence buffer * for this and store it. * * For all pads: * 1) Once per input buffer (cached) * 1) Check discont (flag and timestamp with tolerance) * 2) If discont or new, resync. That means: * 1) Drop all start data of the buffer that comes before * the current position/offset. * 2) Calculate the offset (output segment!) that the first * frame of the input buffer corresponds to. Base this on * the running time. * * 2) If the current pad's offset/offset_end overlaps with the output * offset/offset_end, mix it at the appropiate position in the output * buffer and advance the pad's position. Remember if this pad needs * a new buffer to advance behind the output offset_end. * * 3) If we had no pad with a buffer, go EOS. * * 4) If we had at least one pad that did not advance behind output * offset_end, let collected be called again for the current * output offset/offset_end. */ GstElement *element; GstAudioAggregator *aagg; GList *iter; GstFlowReturn ret; GstBuffer *outbuf = NULL; gint64 next_offset; gint64 next_timestamp; gint rate, bpf; gboolean dropped = FALSE; gboolean is_eos = TRUE; gboolean is_done = TRUE; guint blocksize; element = GST_ELEMENT (agg); aagg = GST_AUDIO_AGGREGATOR (agg); /* Sync pad properties to the stream time */ gst_aggregator_iterate_sinkpads (agg, (GstAggregatorPadForeachFunc) GST_DEBUG_FUNCPTR (sync_pad_values), NULL); GST_AUDIO_AGGREGATOR_LOCK (aagg); GST_OBJECT_LOCK (agg); /* Update position from the segment start/stop if needed */ if (agg->segment.position == -1) { if (agg->segment.rate > 0.0) agg->segment.position = agg->segment.start; else agg->segment.position = agg->segment.stop; } if (G_UNLIKELY (aagg->info.finfo->format == GST_AUDIO_FORMAT_UNKNOWN)) { if (timeout) { GST_DEBUG_OBJECT (aagg, "Got timeout before receiving any caps, don't output anything"); /* Advance position */ if (agg->segment.rate > 0.0) agg->segment.position += aagg->priv->output_buffer_duration; else if (agg->segment.position > aagg->priv->output_buffer_duration) agg->segment.position -= aagg->priv->output_buffer_duration; else agg->segment.position = 0; GST_OBJECT_UNLOCK (agg); GST_AUDIO_AGGREGATOR_UNLOCK (aagg); return GST_FLOW_OK; } else { GST_OBJECT_UNLOCK (agg); goto not_negotiated; } } if (aagg->priv->send_caps) { GST_OBJECT_UNLOCK (agg); gst_aggregator_set_src_caps (agg, aagg->current_caps); GST_OBJECT_LOCK (agg); aagg->priv->send_caps = FALSE; } rate = GST_AUDIO_INFO_RATE (&aagg->info); bpf = GST_AUDIO_INFO_BPF (&aagg->info); if (aagg->priv->offset == -1) { aagg->priv->offset = gst_util_uint64_scale (agg->segment.position - agg->segment.start, rate, GST_SECOND); GST_DEBUG_OBJECT (aagg, "Starting at offset %" G_GINT64_FORMAT, aagg->priv->offset); } blocksize = gst_util_uint64_scale (aagg->priv->output_buffer_duration, rate, GST_SECOND); blocksize = MAX (1, blocksize); /* for the next timestamp, use the sample counter, which will * never accumulate rounding errors */ /* FIXME: Reverse mixing does not work at all yet */ if (agg->segment.rate > 0.0) { next_offset = aagg->priv->offset + blocksize; } else { next_offset = aagg->priv->offset - blocksize; } next_timestamp = agg->segment.start + gst_util_uint64_scale (next_offset, GST_SECOND, rate); if (aagg->priv->current_buffer == NULL) { GST_OBJECT_UNLOCK (agg); aagg->priv->current_buffer = GST_AUDIO_AGGREGATOR_GET_CLASS (aagg)->create_output_buffer (aagg, blocksize); /* Be careful, some things could have changed ? */ GST_OBJECT_LOCK (agg); GST_BUFFER_FLAG_SET (aagg->priv->current_buffer, GST_BUFFER_FLAG_GAP); } outbuf = aagg->priv->current_buffer; GST_LOG_OBJECT (agg, "Starting to mix %u samples for offset %" G_GINT64_FORMAT " with timestamp %" GST_TIME_FORMAT, blocksize, aagg->priv->offset, GST_TIME_ARGS (agg->segment.position)); for (iter = element->sinkpads; iter; iter = iter->next) { GstBuffer *inbuf; GstAudioAggregatorPad *pad = (GstAudioAggregatorPad *) iter->data; GstAggregatorPad *aggpad = (GstAggregatorPad *) iter->data; gboolean drop_buf = FALSE; gboolean pad_eos = gst_aggregator_pad_is_eos (aggpad); if (!pad_eos) is_eos = FALSE; inbuf = gst_aggregator_pad_get_buffer (aggpad); GST_OBJECT_LOCK (pad); if (!inbuf) { if (timeout) { if (pad->priv->output_offset < next_offset) { gint64 diff = next_offset - pad->priv->output_offset; GST_LOG_OBJECT (pad, "Timeout, missing %" G_GINT64_FORMAT " frames (%" GST_TIME_FORMAT ")", diff, GST_TIME_ARGS (gst_util_uint64_scale (diff, GST_SECOND, GST_AUDIO_INFO_RATE (&aagg->info)))); } } else if (!pad_eos) { is_done = FALSE; } GST_OBJECT_UNLOCK (pad); continue; } g_assert (!pad->priv->buffer || pad->priv->buffer == inbuf); /* New buffer? */ if (!pad->priv->buffer) { /* Takes ownership of buffer */ if (!gst_audio_aggregator_fill_buffer (aagg, pad, inbuf)) { dropped = TRUE; GST_OBJECT_UNLOCK (pad); gst_aggregator_pad_drop_buffer (aggpad); continue; } } else { gst_buffer_unref (inbuf); } if (!pad->priv->buffer && !dropped && pad_eos) { GST_DEBUG_OBJECT (aggpad, "Pad is in EOS state"); GST_OBJECT_UNLOCK (pad); continue; } g_assert (pad->priv->buffer); /* This pad is lacking behind, we need to update the offset * and maybe drop the current buffer */ if (pad->priv->output_offset < aagg->priv->offset) { gint64 diff = aagg->priv->offset - pad->priv->output_offset; gint64 odiff = diff; if (pad->priv->position + diff > pad->priv->size) diff = pad->priv->size - pad->priv->position; pad->priv->position += diff; pad->priv->output_offset += diff; if (pad->priv->position == pad->priv->size) { GST_LOG_OBJECT (pad, "Buffer was late by %" GST_TIME_FORMAT ", dropping %" GST_PTR_FORMAT, GST_TIME_ARGS (gst_util_uint64_scale (odiff, GST_SECOND, GST_AUDIO_INFO_RATE (&aagg->info))), pad->priv->buffer); /* Buffer done, drop it */ gst_buffer_replace (&pad->priv->buffer, NULL); dropped = TRUE; GST_OBJECT_UNLOCK (pad); gst_aggregator_pad_drop_buffer (aggpad); continue; } } if (pad->priv->output_offset >= aagg->priv->offset && pad->priv->output_offset < aagg->priv->offset + blocksize && pad->priv->buffer) { GST_LOG_OBJECT (aggpad, "Mixing buffer for current offset"); drop_buf = !gst_audio_aggregator_mix_buffer (aagg, pad, pad->priv->buffer, outbuf); if (pad->priv->output_offset >= next_offset) { GST_DEBUG_OBJECT (pad, "Pad is after current offset: %" G_GUINT64_FORMAT " >= %" G_GINT64_FORMAT, pad->priv->output_offset, next_offset); } else { is_done = FALSE; } } GST_OBJECT_UNLOCK (pad); if (drop_buf) gst_aggregator_pad_drop_buffer (aggpad); } GST_OBJECT_UNLOCK (agg); if (dropped) { /* We dropped a buffer, retry */ GST_INFO_OBJECT (aagg, "A pad dropped a buffer, wait for the next one"); GST_AUDIO_AGGREGATOR_UNLOCK (aagg); return GST_FLOW_OK; } if (!is_done && !is_eos) { /* Get more buffers */ GST_INFO_OBJECT (aagg, "We're not done yet for the current offset," " waiting for more data"); GST_AUDIO_AGGREGATOR_UNLOCK (aagg); return GST_FLOW_OK; } if (is_eos) { gint64 max_offset = 0; GST_DEBUG_OBJECT (aagg, "We're EOS"); GST_OBJECT_LOCK (agg); for (iter = GST_ELEMENT (agg)->sinkpads; iter; iter = iter->next) { GstAudioAggregatorPad *pad = GST_AUDIO_AGGREGATOR_PAD (iter->data); max_offset = MAX ((gint64) max_offset, (gint64) pad->priv->output_offset); } GST_OBJECT_UNLOCK (agg); /* This means EOS or nothing mixed in at all */ if (aagg->priv->offset == max_offset) { gst_buffer_replace (&aagg->priv->current_buffer, NULL); GST_AUDIO_AGGREGATOR_UNLOCK (aagg); return GST_FLOW_EOS; } if (max_offset <= next_offset) { GST_DEBUG_OBJECT (aagg, "Last buffer is incomplete: %" G_GUINT64_FORMAT " <= %" G_GINT64_FORMAT, max_offset, next_offset); next_offset = max_offset; next_timestamp = agg->segment.start + gst_util_uint64_scale (next_offset, GST_SECOND, rate); if (next_offset > aagg->priv->offset) gst_buffer_resize (outbuf, 0, (next_offset - aagg->priv->offset) * bpf); } } /* set timestamps on the output buffer */ GST_OBJECT_LOCK (agg); if (agg->segment.rate > 0.0) { GST_BUFFER_PTS (outbuf) = agg->segment.position; GST_BUFFER_OFFSET (outbuf) = aagg->priv->offset; GST_BUFFER_OFFSET_END (outbuf) = next_offset; GST_BUFFER_DURATION (outbuf) = next_timestamp - agg->segment.position; } else { GST_BUFFER_PTS (outbuf) = next_timestamp; GST_BUFFER_OFFSET (outbuf) = next_offset; GST_BUFFER_OFFSET_END (outbuf) = aagg->priv->offset; GST_BUFFER_DURATION (outbuf) = agg->segment.position - next_timestamp; } GST_OBJECT_UNLOCK (agg); /* send it out */ GST_LOG_OBJECT (aagg, "pushing outbuf %p, timestamp %" GST_TIME_FORMAT " offset %" G_GINT64_FORMAT, outbuf, GST_TIME_ARGS (GST_BUFFER_PTS (outbuf)), GST_BUFFER_OFFSET (outbuf)); GST_AUDIO_AGGREGATOR_UNLOCK (aagg); ret = gst_aggregator_finish_buffer (agg, aagg->priv->current_buffer); aagg->priv->current_buffer = NULL; GST_LOG_OBJECT (aagg, "pushed outbuf, result = %s", gst_flow_get_name (ret)); GST_AUDIO_AGGREGATOR_LOCK (aagg); GST_OBJECT_LOCK (agg); aagg->priv->offset = next_offset; agg->segment.position = next_timestamp; /* If there was a timeout and there was a gap in data in out of the streams, * then it's a very good time to for a resync with the timestamps. */ if (timeout) { for (iter = element->sinkpads; iter; iter = iter->next) { GstAudioAggregatorPad *pad = GST_AUDIO_AGGREGATOR_PAD (iter->data); GST_OBJECT_LOCK (pad); if (pad->priv->output_offset < aagg->priv->offset) pad->priv->output_offset = -1; GST_OBJECT_UNLOCK (pad); } } GST_OBJECT_UNLOCK (agg); GST_AUDIO_AGGREGATOR_UNLOCK (aagg); return ret; /* ERRORS */ not_negotiated: { GST_AUDIO_AGGREGATOR_UNLOCK (aagg); GST_ELEMENT_ERROR (aagg, STREAM, FORMAT, (NULL), ("Unknown data received, not negotiated")); return GST_FLOW_NOT_NEGOTIATED; } }