コード例 #1
0
static GstClock *
gst_base_audio_src_provide_clock (GstElement * elem)
{
  GstBaseAudioSrc *src;
  GstClock *clock;

  src = GST_BASE_AUDIO_SRC (elem);

  /* we have no ringbuffer (must be NULL state) */
  if (src->ringbuffer == NULL)
    goto wrong_state;

  if (!gst_ring_buffer_is_acquired (src->ringbuffer))
    goto wrong_state;

  GST_OBJECT_LOCK (src);
  if (!src->priv->provide_clock)
    goto clock_disabled;

  clock = GST_CLOCK_CAST (gst_object_ref (src->clock));
  GST_OBJECT_UNLOCK (src);

  return clock;

  /* ERRORS */
wrong_state:
  {
    GST_DEBUG_OBJECT (src, "ringbuffer not acquired");
    return NULL;
  }
clock_disabled:
  {
    GST_DEBUG_OBJECT (src, "clock provide disabled");
    GST_OBJECT_UNLOCK (src);
    return NULL;
  }
}
static GstFlowReturn
gst_audio_ringbuffer_render (GstAudioRingbuffer * ringbuffer, GstBuffer * buf)
{
  GstRingBuffer *rbuf;
  gint bps, accum;
  guint size;
  guint samples, written, out_samples;
  gint64 diff, align, ctime, cstop;
  guint8 *data;
  guint64 in_offset;
  GstClockTime time, stop, render_start, render_stop, sample_offset;
  gboolean align_next;

  rbuf = ringbuffer->buffer;

  /* can't do anything when we don't have the device */
  if (G_UNLIKELY (!gst_ring_buffer_is_acquired (rbuf)))
    goto wrong_state;

  bps = rbuf->spec.bytes_per_sample;

  size = GST_BUFFER_SIZE (buf);
  if (G_UNLIKELY (size % bps) != 0)
    goto wrong_size;

  samples = size / bps;
  out_samples = samples;

  in_offset = GST_BUFFER_OFFSET (buf);
  time = GST_BUFFER_TIMESTAMP (buf);

  GST_DEBUG_OBJECT (ringbuffer,
      "time %" GST_TIME_FORMAT ", offset %llu, start %" GST_TIME_FORMAT
      ", samples %u", GST_TIME_ARGS (time), in_offset,
      GST_TIME_ARGS (ringbuffer->sink_segment.start), samples);

  data = GST_BUFFER_DATA (buf);

  stop = time + gst_util_uint64_scale_int (samples, GST_SECOND,
      rbuf->spec.rate);

  if (!gst_segment_clip (&ringbuffer->sink_segment, GST_FORMAT_TIME, time, stop,
          &ctime, &cstop))
    goto out_of_segment;

  /* see if some clipping happened */
  diff = ctime - time;
  if (diff > 0) {
    /* bring clipped time to samples */
    diff = gst_util_uint64_scale_int (diff, rbuf->spec.rate, GST_SECOND);
    GST_DEBUG_OBJECT (ringbuffer, "clipping start to %" GST_TIME_FORMAT " %"
        G_GUINT64_FORMAT " samples", GST_TIME_ARGS (ctime), diff);
    samples -= diff;
    data += diff * bps;
    time = ctime;
  }
  diff = stop - cstop;
  if (diff > 0) {
    /* bring clipped time to samples */
    diff = gst_util_uint64_scale_int (diff, rbuf->spec.rate, GST_SECOND);
    GST_DEBUG_OBJECT (ringbuffer, "clipping stop to %" GST_TIME_FORMAT " %"
        G_GUINT64_FORMAT " samples", GST_TIME_ARGS (cstop), diff);
    samples -= diff;
    stop = cstop;
  }

  /* bring buffer start and stop times to running time */
  render_start =
      gst_segment_to_running_time (&ringbuffer->sink_segment, GST_FORMAT_TIME,
      time);
  render_stop =
      gst_segment_to_running_time (&ringbuffer->sink_segment, GST_FORMAT_TIME,
      stop);

  GST_DEBUG_OBJECT (ringbuffer,
      "running: start %" GST_TIME_FORMAT " - stop %" GST_TIME_FORMAT,
      GST_TIME_ARGS (render_start), GST_TIME_ARGS (render_stop));

  /* and bring the time to the rate corrected offset in the buffer */
  render_start = gst_util_uint64_scale_int (render_start,
      rbuf->spec.rate, GST_SECOND);
  render_stop = gst_util_uint64_scale_int (render_stop,
      rbuf->spec.rate, GST_SECOND);

  /* positive playback rate, first sample is render_start, negative rate, first
   * sample is render_stop. When no rate conversion is active, render exactly
   * the amount of input samples to avoid aligning to rounding errors. */
  if (ringbuffer->sink_segment.rate >= 0.0) {
    sample_offset = render_start;
    if (ringbuffer->sink_segment.rate == 1.0)
      render_stop = sample_offset + samples;
  } else {
    sample_offset = render_stop;
    if (ringbuffer->sink_segment.rate == -1.0)
      render_start = sample_offset + samples;
  }

  /* always resync after a discont */
  if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT))) {
    GST_DEBUG_OBJECT (ringbuffer, "resync after discont");
    goto no_align;
  }

  /* resync when we don't know what to align the sample with */
  if (G_UNLIKELY (ringbuffer->next_sample == -1)) {
    GST_DEBUG_OBJECT (ringbuffer,
        "no align possible: no previous sample position known");
    goto no_align;
  }

  /* now try to align the sample to the previous one, first see how big the
   * difference is. */
  if (sample_offset >= ringbuffer->next_sample)
    diff = sample_offset - ringbuffer->next_sample;
  else
    diff = ringbuffer->next_sample - sample_offset;

  /* we tollerate half a second diff before we start resyncing. This
   * should be enough to compensate for various rounding errors in the timestamp
   * and sample offset position. We always resync if we got a discont anyway and
   * non-discont should be aligned by definition. */
  if (G_LIKELY (diff < rbuf->spec.rate / DIFF_TOLERANCE)) {
    /* calc align with previous sample */
    align = ringbuffer->next_sample - sample_offset;
    GST_DEBUG_OBJECT (ringbuffer,
        "align with prev sample, ABS (%" G_GINT64_FORMAT ") < %d", align,
        rbuf->spec.rate / DIFF_TOLERANCE);
  } else {
    /* bring sample diff to seconds for error message */
    diff = gst_util_uint64_scale_int (diff, GST_SECOND, rbuf->spec.rate);
    /* timestamps drifted apart from previous samples too much, we need to
     * resync. We log this as an element warning. */
    GST_ELEMENT_WARNING (ringbuffer, CORE, CLOCK,
        ("Compensating for audio synchronisation problems"),
        ("Unexpected discontinuity in audio timestamps of more "
            "than half a second (%" GST_TIME_FORMAT "), resyncing",
            GST_TIME_ARGS (diff)));
    align = 0;
  }
  ringbuffer->last_align = align;

  /* apply alignment */
  render_start += align;
  render_stop += align;

no_align:
  /* number of target samples is difference between start and stop */
  out_samples = render_stop - render_start;

  /* we render the first or last sample first, depending on the rate */
  if (ringbuffer->sink_segment.rate >= 0.0)
    sample_offset = render_start;
  else
    sample_offset = render_stop;

  GST_DEBUG_OBJECT (ringbuffer, "rendering at %" G_GUINT64_FORMAT " %d/%d",
      sample_offset, samples, out_samples);

  /* we need to accumulate over different runs for when we get interrupted */
  accum = 0;
  align_next = TRUE;
  do {
    written =
        gst_ring_buffer_commit_full (rbuf, &sample_offset, data, samples,
        out_samples, &accum);

    GST_DEBUG_OBJECT (ringbuffer, "wrote %u of %u", written, samples);
    /* if we wrote all, we're done */
    if (written == samples)
      break;

    GST_OBJECT_LOCK (ringbuffer);
    if (ringbuffer->flushing)
      goto flushing;
    GST_OBJECT_UNLOCK (ringbuffer);

    /* if we got interrupted, we cannot assume that the next sample should
     * be aligned to this one */
    align_next = FALSE;

    samples -= written;
    data += written * bps;
  } while (TRUE);

  if (align_next)
    ringbuffer->next_sample = sample_offset;
  else
    ringbuffer->next_sample = -1;

  GST_DEBUG_OBJECT (ringbuffer, "next sample expected at %" G_GUINT64_FORMAT,
      ringbuffer->next_sample);

  if (GST_CLOCK_TIME_IS_VALID (stop) && stop >= ringbuffer->sink_segment.stop) {
    GST_DEBUG_OBJECT (ringbuffer,
        "start playback because we are at the end of segment");
    gst_ring_buffer_start (rbuf);
  }

  return GST_FLOW_OK;

  /* SPECIAL cases */
out_of_segment:
  {
    GST_DEBUG_OBJECT (ringbuffer,
        "dropping sample out of segment time %" GST_TIME_FORMAT ", start %"
        GST_TIME_FORMAT, GST_TIME_ARGS (time),
        GST_TIME_ARGS (ringbuffer->sink_segment.start));
    return GST_FLOW_OK;
  }
  /* ERRORS */
wrong_state:
  {
    GST_DEBUG_OBJECT (ringbuffer, "ringbuffer not negotiated");
    GST_ELEMENT_ERROR (ringbuffer, STREAM, FORMAT, (NULL),
        ("ringbuffer not negotiated."));
    return GST_FLOW_NOT_NEGOTIATED;
  }
wrong_size:
  {
    GST_DEBUG_OBJECT (ringbuffer, "wrong size");
    GST_ELEMENT_ERROR (ringbuffer, STREAM, WRONG_TYPE,
        (NULL), ("ringbuffer received buffer of wrong size."));
    return GST_FLOW_ERROR;
  }
flushing:
  {
    GST_DEBUG_OBJECT (ringbuffer, "ringbuffer is flushing");
    GST_OBJECT_UNLOCK (ringbuffer);
    return GST_FLOW_WRONG_STATE;
  }
}
コード例 #3
0
static GstFlowReturn
gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length,
    GstBuffer ** outbuf)
{
  GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (bsrc);
  GstBuffer *buf;
  guchar *data;
  guint samples, total_samples;
  guint64 sample;
  gint bps;
  GstRingBuffer *ringbuffer;
  GstRingBufferSpec *spec;
  guint read;
  GstClockTime timestamp, duration;
  GstClock *clock;

  ringbuffer = src->ringbuffer;
  spec = &ringbuffer->spec;

  if (G_UNLIKELY (!gst_ring_buffer_is_acquired (ringbuffer)))
    goto wrong_state;

  bps = spec->bytes_per_sample;

  if ((length == 0 && bsrc->blocksize == 0) || length == -1)
    /* no length given, use the default segment size */
    length = spec->segsize;
  else
    /* make sure we round down to an integral number of samples */
    length -= length % bps;

  /* figure out the offset in the ringbuffer */
  if (G_UNLIKELY (offset != -1)) {
    sample = offset / bps;
    /* if a specific offset was given it must be the next sequential
     * offset we expect or we fail for now. */
    if (src->next_sample != -1 && sample != src->next_sample)
      goto wrong_offset;
  } else {
    /* calculate the sequentially next sample we need to read. This can jump and
     * create a DISCONT. */
    sample = gst_base_audio_src_get_offset (src);
  }

  GST_DEBUG_OBJECT (src, "reading from sample %" G_GUINT64_FORMAT, sample);

  /* get the number of samples to read */
  total_samples = samples = length / bps;

  /* FIXME, using a bufferpool would be nice here */
  buf = gst_buffer_new_and_alloc (length);
  data = GST_BUFFER_DATA (buf);

  do {
    read = gst_ring_buffer_read (ringbuffer, sample, data, samples);
    GST_DEBUG_OBJECT (src, "read %u of %u", read, samples);
    /* if we read all, we're done */
    if (read == samples)
      break;

    /* else something interrupted us and we wait for playing again. */
    GST_DEBUG_OBJECT (src, "wait playing");
    if (gst_base_src_wait_playing (bsrc) != GST_FLOW_OK)
      goto stopped;

    GST_DEBUG_OBJECT (src, "continue playing");

    /* read next samples */
    sample += read;
    samples -= read;
    data += read * bps;
  } while (TRUE);

  /* mark discontinuity if needed */
  if (G_UNLIKELY (sample != src->next_sample) && src->next_sample != -1) {
    GST_WARNING_OBJECT (src,
        "create DISCONT of %" G_GUINT64_FORMAT " samples at sample %"
        G_GUINT64_FORMAT, sample - src->next_sample, sample);
    GST_ELEMENT_WARNING (src, CORE, CLOCK,
        (_("Can't record audio fast enough")),
        ("dropped %" G_GUINT64_FORMAT " samples", sample - src->next_sample));
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
  }

  src->next_sample = sample + samples;

  /* get the normal timestamp to get the duration. */
  timestamp = gst_util_uint64_scale_int (sample, GST_SECOND, spec->rate);
  duration = gst_util_uint64_scale_int (src->next_sample, GST_SECOND,
      spec->rate) - timestamp;

  GST_OBJECT_LOCK (src);
  if (!(clock = GST_ELEMENT_CLOCK (src)))
    goto no_sync;

  if (clock != src->clock) {
    /* we are slaved, check how to handle this */
    switch (src->priv->slave_method) {
      case GST_BASE_AUDIO_SRC_SLAVE_RESAMPLE:
        /* not implemented, use skew algorithm. This algorithm should
         * work on the readout pointer and produces more or less samples based
         * on the clock drift */
      case GST_BASE_AUDIO_SRC_SLAVE_SKEW:
      {
        GstClockTime running_time;
        GstClockTime base_time;
        GstClockTime current_time;
        guint64 running_time_sample;
        gint running_time_segment;
        gint current_segment;
        gint segment_skew;
        gint sps;

        /* samples per segment */
        sps = ringbuffer->samples_per_seg;

        /* get the current time */
        current_time = gst_clock_get_time (clock);

        /* get the basetime */
        base_time = GST_ELEMENT_CAST (src)->base_time;

        /* get the running_time */
        running_time = current_time - base_time;

        /* the running_time converted to a sample (relative to the ringbuffer) */
        running_time_sample =
            gst_util_uint64_scale_int (running_time, spec->rate, GST_SECOND);

        /* the segmentnr corrensponding to running_time, round down */
        running_time_segment = running_time_sample / sps;

        /* the segment currently read from the ringbuffer */
        current_segment = sample / sps;

        /* the skew we have between running_time and the ringbuffertime */
        segment_skew = running_time_segment - current_segment;

        GST_DEBUG_OBJECT (bsrc, "\n running_time = %" GST_TIME_FORMAT
            "\n timestamp     = %" GST_TIME_FORMAT
            "\n running_time_segment = %d"
            "\n current_segment      = %d"
            "\n segment_skew         = %d",
            GST_TIME_ARGS (running_time),
            GST_TIME_ARGS (timestamp),
            running_time_segment, current_segment, segment_skew);

        /* Resync the ringbuffer if:
         * 1. We get one segment into the future.
         *    This is clearly a lie, because we can't
         *    possibly have a buffer with timestamp 1 at
         *    time 0. (unless it has time-travelled...)
         *
         * 2. We are more than the length of the ringbuffer behind.
         *    The length of the ringbuffer then gets to dictate
         *    the threshold for what is concidered "too late"
         *
         * 3. If this is our first buffer.
         *    We know that we should catch up to running_time
         *    the first time we are ran.
         */
        if ((segment_skew < 0) ||
            (segment_skew >= ringbuffer->spec.segtotal) ||
            (current_segment == 0)) {
          gint segments_written;
          gint first_segment;
          gint last_segment;
          gint new_last_segment;
          gint segment_diff;
          gint new_first_segment;
          guint64 new_sample;

          /* we are going to say that the last segment was captured at the current time
             (running_time), minus one segment of creation-latency in the ringbuffer.
             This can be thought of as: The segment arrived in the ringbuffer at time X, and
             that means it was created at time X - (one segment). */
          new_last_segment = running_time_segment - 1;

          /* for better readablity */
          first_segment = current_segment;

          /* get the amount of segments written from the device by now */
          segments_written = g_atomic_int_get (&ringbuffer->segdone);

          /* subtract the base to segments_written to get the number of the
             last written segment in the ringbuffer (one segment written = segment 0) */
          last_segment = segments_written - ringbuffer->segbase - 1;

          /* we see how many segments the ringbuffer was timeshifted */
          segment_diff = new_last_segment - last_segment;

          /* we move the first segment an equal amount */
          new_first_segment = first_segment + segment_diff;

          /* and we also move the segmentbase the same amount */
          ringbuffer->segbase -= segment_diff;

          /* we calculate the new sample value */
          new_sample = ((guint64) new_first_segment) * sps;

          /* and get the relative time to this -> our new timestamp */
          timestamp =
              gst_util_uint64_scale_int (new_sample, GST_SECOND, spec->rate);

          /* we update the next sample accordingly */
          src->next_sample = new_sample + samples;

          GST_DEBUG_OBJECT (bsrc,
              "Timeshifted the ringbuffer with %d segments: "
              "Updating the timestamp to %" GST_TIME_FORMAT ", "
              "and src->next_sample to %" G_GUINT64_FORMAT, segment_diff,
              GST_TIME_ARGS (timestamp), src->next_sample);
        }
        break;
      }
      case GST_BASE_AUDIO_SRC_SLAVE_RETIMESTAMP:
      {
        GstClockTime base_time, latency;

        /* We are slaved to another clock, take running time of the pipeline clock and
         * timestamp against it. Somebody else in the pipeline should figure out the
         * clock drift. We keep the duration we calculated above. */
        timestamp = gst_clock_get_time (clock);
        base_time = GST_ELEMENT_CAST (src)->base_time;

        if (timestamp > base_time)
          timestamp -= base_time;
        else
          timestamp = 0;

        /* subtract latency */
        latency =
            gst_util_uint64_scale_int (total_samples, GST_SECOND, spec->rate);
        if (timestamp > latency)
          timestamp -= latency;
        else
          timestamp = 0;
      }
      case GST_BASE_AUDIO_SRC_SLAVE_NONE:
        break;
    }
  } else {
    GstClockTime base_time;

    /* we are not slaved, subtract base_time */
    base_time = GST_ELEMENT_CAST (src)->base_time;

    if (timestamp > base_time)
      timestamp -= base_time;
    else
      timestamp = 0;
  }

no_sync:
  GST_OBJECT_UNLOCK (src);

  GST_BUFFER_TIMESTAMP (buf) = timestamp;
  GST_BUFFER_DURATION (buf) = duration;
  GST_BUFFER_OFFSET (buf) = sample;
  GST_BUFFER_OFFSET_END (buf) = sample + samples;

  *outbuf = buf;

  return GST_FLOW_OK;

  /* ERRORS */
wrong_state:
  {
    GST_DEBUG_OBJECT (src, "ringbuffer in wrong state");
    return GST_FLOW_WRONG_STATE;
  }
wrong_offset:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, SEEK,
        (NULL), ("resource can only be operated on sequentially but offset %"
            G_GUINT64_FORMAT " was given", offset));
    return GST_FLOW_ERROR;
  }
stopped:
  {
    gst_buffer_unref (buf);
    GST_DEBUG_OBJECT (src, "ringbuffer stopped");
    return GST_FLOW_WRONG_STATE;
  }
}