static void gst_pulsesrc_stream_latency_update_cb (pa_stream * s, void *userdata) { const pa_timing_info *info; pa_usec_t source_usec; info = pa_stream_get_timing_info (s); if (!info) { GST_LOG_OBJECT (GST_PULSESRC_CAST (userdata), "latency update (information unknown)"); return; } #ifdef HAVE_PULSE_0_9_11 source_usec = info->configured_source_usec; #else source_usec = 0; #endif GST_LOG_OBJECT (GST_PULSESRC_CAST (userdata), "latency_update, %" G_GUINT64_FORMAT ", %d:%" G_GINT64_FORMAT ", %d:%" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT, GST_TIMEVAL_TO_TIME (info->timestamp), info->write_index_corrupt, info->write_index, info->read_index_corrupt, info->read_index, info->source_usec, source_usec); }
void CAESinkPULSE::GetDelay(AEDelayStatus& status) { if (!m_IsAllocated) { status.SetDelay(0); return; } pa_threaded_mainloop_lock(m_MainLoop); const pa_timing_info* pti = pa_stream_get_timing_info(m_Stream); // only incorporate local sink delay + internal PA transport delay double sink_delay = (pti->configured_sink_usec / 1000000.0); double transport_delay = pti->transport_usec / 1000000.0; uint64_t diff = CurrentHostCounter() - m_lastPackageStamp; unsigned int bytes_played = (unsigned int) ((double) diff * (double) m_BytesPerSecond / (double) CurrentHostFrequency() + 0.5); int buffer_delay = m_filled_bytes - bytes_played; if (buffer_delay < 0) buffer_delay = 0; pa_threaded_mainloop_unlock(m_MainLoop); double delay = buffer_delay / (double) m_BytesPerSecond + sink_delay + transport_delay; status.SetDelay(delay); }
// Return the current latency in seconds static float get_delay(struct ao *ao) { /* This code basically does what pa_stream_get_latency() _should_ * do, but doesn't due to multiple known bugs in PulseAudio (at * PulseAudio version 2.1). In particular, the timing interpolation * mode (PA_STREAM_INTERPOLATE_TIMING) can return completely bogus * values, and the non-interpolating code has a bug causing too * large results at end of stream (so a stream never seems to finish). * This code can still return wrong values in some cases due to known * PulseAudio bugs that can not be worked around on the client side. * * We always query the server for latest timing info. This may take * too long to work well with remote audio servers, but at least * this should be enough to fix the normal local playback case. */ struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); if (!waitop(priv, pa_stream_update_timing_info(priv->stream, NULL, NULL))) { GENERIC_ERR_MSG(priv->context, "pa_stream_update_timing_info() failed"); return 0; } pa_threaded_mainloop_lock(priv->mainloop); const pa_timing_info *ti = pa_stream_get_timing_info(priv->stream); if (!ti) { pa_threaded_mainloop_unlock(priv->mainloop); GENERIC_ERR_MSG(priv->context, "pa_stream_get_timing_info() failed"); return 0; } const struct pa_sample_spec *ss = pa_stream_get_sample_spec(priv->stream); if (!ss) { pa_threaded_mainloop_unlock(priv->mainloop); GENERIC_ERR_MSG(priv->context, "pa_stream_get_sample_spec() failed"); return 0; } // data left in PulseAudio's main buffers (not written to sink yet) int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss); // since this info may be from a while ago, playback has progressed since latency -= ti->transport_usec; // data already moved from buffers to sink, but not played yet int64_t sink_latency = ti->sink_usec; if (!ti->playing) /* At the end of a stream, part of the data "left" in the sink may * be padding silence after the end; that should be subtracted to * get the amount of real audio from our stream. This adjustment * is missing from Pulseaudio's own get_latency calculations * (as of PulseAudio 2.1). */ sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss); if (sink_latency > 0) latency += sink_latency; if (latency < 0) latency = 0; pa_threaded_mainloop_unlock(priv->mainloop); return latency / 1e6; }
/* * update pulseaudio latency * args: * s - pointer to pa_stream * * asserts: * none * * returns:none */ static void get_latency(pa_stream *s) { pa_usec_t l; int negative; pa_stream_get_timing_info(s); if (pa_stream_get_latency(s, &l, &negative) != 0) { fprintf(stderr, "AUDIO: Pulseaudio pa_stream_get_latency() failed\n"); return; } //latency = l * (negative?-1:1); latency = l; /*can only be negative in monitoring streams*/ //printf("AUDIO: pulseaudio latency is %0.0f usec \r", (float)latency); }
static int pulse_playing(const pa_timing_info *the_timing_info) { ENTER(__FUNCTION__); int r = 0; const pa_timing_info *i; assert(the_timing_info); CHECK_CONNECTED(0); pa_threaded_mainloop_lock(mainloop); for (;;) { CHECK_DEAD_GOTO(fail, 1); if ((i = pa_stream_get_timing_info(stream))) { break; } if (pa_context_errno(context) != PA_ERR_NODATA) { SHOW("pa_stream_get_timing_info() failed: %s", pa_strerror(pa_context_errno(context))); goto fail; } pa_threaded_mainloop_wait(mainloop); } r = i->playing; memcpy((void*)the_timing_info, (void*)i, sizeof(pa_timing_info)); // display_timing_info(i); fail: pa_threaded_mainloop_unlock(mainloop); return r; }
} static void outputStreamOverflowCallback(pa_stream *stream, void *userdata) { Q_UNUSED(stream) Q_UNUSED(userdata) qWarning() << "Got a buffer overflow!"; } static void outputStreamLatencyCallback(pa_stream *stream, void *userdata) { Q_UNUSED(stream) Q_UNUSED(userdata) #ifdef DEBUG_PULSE const pa_timing_info *info = pa_stream_get_timing_info(stream); qDebug() << "Write index corrupt: " << info->write_index_corrupt; qDebug() << "Write index: " << info->write_index; qDebug() << "Read index corrupt: " << info->read_index_corrupt; qDebug() << "Read index: " << info->read_index; qDebug() << "Sink usec: " << info->sink_usec; qDebug() << "Configured sink usec: " << info->configured_sink_usec; #endif } static void outputStreamSuccessCallback(pa_stream *stream, int success, void *userdata) { Q_UNUSED(stream); Q_UNUSED(success); Q_UNUSED(userdata);
static int pulseaudio_audio_deliver(audio_decoder_t *ad, int samples, int64_t pts, int epoch) { decoder_t *d = (decoder_t *)ad; size_t bytes; if(ad->ad_spdif_muxer != NULL) { bytes = ad->ad_spdif_frame_size; } else { bytes = samples * d->framesize; } void *buf; assert(d->s != NULL); pa_threaded_mainloop_lock(mainloop); int writable = pa_stream_writable_size(d->s); if(writable == 0) { d->blocked = 1; pa_threaded_mainloop_unlock(mainloop); return 1; } pa_stream_begin_write(d->s, &buf, &bytes); assert(bytes > 0); if(ad->ad_spdif_muxer != NULL) { memcpy(buf, ad->ad_spdif_frame, ad->ad_spdif_frame_size); } else { int rsamples = bytes / d->framesize; uint8_t *data[8] = {0}; data[0] = (uint8_t *)buf; assert(rsamples <= samples); avresample_read(ad->ad_avr, data, rsamples); } if(pts != AV_NOPTS_VALUE) { media_pipe_t *mp = ad->ad_mp; hts_mutex_lock(&mp->mp_clock_mutex); const pa_timing_info *ti = pa_stream_get_timing_info(d->s); if(ti != NULL) { mp->mp_audio_clock_avtime = ti->timestamp.tv_sec * 1000000LL + ti->timestamp.tv_usec; int64_t busec = pa_bytes_to_usec(ti->write_index - ti->read_index, &d->ss); int64_t delay = ti->sink_usec + busec + ti->transport_usec; mp->mp_audio_clock = pts - delay; mp->mp_audio_clock_epoch = epoch; } hts_mutex_unlock(&mp->mp_clock_mutex); } pa_stream_write(d->s, buf, bytes, NULL, 0LL, PA_SEEK_RELATIVE); ad->ad_spdif_frame_size = 0; pa_threaded_mainloop_unlock(mainloop); return 0; }
/* This is called whenever the context status changes */ static void context_state_callback(pa_context *c, void *userdata) { fail_unless(c != NULL); switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: { pa_stream_flags_t flags = PA_STREAM_AUTO_TIMING_UPDATE; pa_buffer_attr attr; static const pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2 }; pa_zero(attr); attr.maxlength = (uint32_t) -1; attr.tlength = latency > 0 ? (uint32_t) pa_usec_to_bytes(latency, &ss) : (uint32_t) -1; attr.prebuf = (uint32_t) -1; attr.minreq = (uint32_t) -1; attr.fragsize = (uint32_t) -1; #ifdef INTERPOLATE flags |= PA_STREAM_INTERPOLATE_TIMING; #endif if (latency > 0) flags |= PA_STREAM_ADJUST_LATENCY; pa_log("Connection established"); stream = pa_stream_new(c, "interpol-test", &ss, NULL); fail_unless(stream != NULL); if (playback) { pa_assert_se(pa_stream_connect_playback(stream, NULL, &attr, flags, NULL, NULL) == 0); pa_stream_set_write_callback(stream, stream_write_cb, NULL); } else { pa_assert_se(pa_stream_connect_record(stream, NULL, &attr, flags) == 0); pa_stream_set_read_callback(stream, stream_read_cb, NULL); } pa_stream_set_latency_update_callback(stream, stream_latency_cb, NULL); break; } case PA_CONTEXT_TERMINATED: break; case PA_CONTEXT_FAILED: default: pa_log_error("Context error: %s", pa_strerror(pa_context_errno(c))); ck_abort(); } } START_TEST (interpol_test) { pa_threaded_mainloop* m = NULL; int k; struct timeval start, last_info = { 0, 0 }; pa_usec_t old_t = 0, old_rtc = 0; #ifdef CORK bool corked = false; #endif /* Set up a new main loop */ m = pa_threaded_mainloop_new(); fail_unless(m != NULL); mainloop_api = pa_threaded_mainloop_get_api(m); fail_unless(mainloop_api != NULL); context = pa_context_new(mainloop_api, bname); fail_unless(context != NULL); pa_context_set_state_callback(context, context_state_callback, NULL); fail_unless(pa_context_connect(context, NULL, 0, NULL) >= 0); pa_gettimeofday(&start); fail_unless(pa_threaded_mainloop_start(m) >= 0); /* #ifdef CORK */ for (k = 0; k < 20000; k++) /* #else */ /* for (k = 0; k < 2000; k++) */ /* #endif */ { bool success = false, changed = false; pa_usec_t t, rtc, d; struct timeval now, tv; bool playing = false; pa_threaded_mainloop_lock(m); if (stream) { const pa_timing_info *info; if (pa_stream_get_time(stream, &t) >= 0 && pa_stream_get_latency(stream, &d, NULL) >= 0) success = true; if ((info = pa_stream_get_timing_info(stream))) { if (memcmp(&last_info, &info->timestamp, sizeof(struct timeval))) { changed = true; last_info = info->timestamp; } if (info->playing) playing = true; } } pa_threaded_mainloop_unlock(m); pa_gettimeofday(&now); if (success) { #ifdef CORK bool cork_now; #endif rtc = pa_timeval_diff(&now, &start); pa_log_info("%i\t%llu\t%llu\t%llu\t%llu\t%lli\t%u\t%u\t%llu\t%llu\n", k, (unsigned long long) rtc, (unsigned long long) t, (unsigned long long) (rtc-old_rtc), (unsigned long long) (t-old_t), (signed long long) rtc - (signed long long) t, changed, playing, (unsigned long long) latency, (unsigned long long) d); fflush(stdout); old_t = t; old_rtc = rtc; #ifdef CORK cork_now = (rtc / (2*PA_USEC_PER_SEC)) % 2 == 1; if (corked != cork_now) { pa_threaded_mainloop_lock(m); pa_operation_unref(pa_stream_cork(stream, cork_now, NULL, NULL)); pa_threaded_mainloop_unlock(m); pa_log(cork_now ? "Corking" : "Uncorking"); corked = cork_now; } #endif } /* Spin loop, ugly but normal usleep() is just too badly grained */ tv = now; while (pa_timeval_diff(pa_gettimeofday(&now), &tv) < 1000) pa_thread_yield(); } if (m) pa_threaded_mainloop_stop(m); if (stream) { pa_stream_disconnect(stream); pa_stream_unref(stream); } if (context) { pa_context_disconnect(context); pa_context_unref(context); } if (m) pa_threaded_mainloop_free(m); } END_TEST int main(int argc, char *argv[]) { int failed = 0; Suite *s; TCase *tc; SRunner *sr; if (!getenv("MAKE_CHECK")) pa_log_set_level(PA_LOG_DEBUG); bname = argv[0]; playback = argc <= 1 || !pa_streq(argv[1], "-r"); latency = (argc >= 2 && !pa_streq(argv[1], "-r")) ? atoi(argv[1]) : (argc >= 3 ? atoi(argv[2]) : 0); s = suite_create("Interpol"); tc = tcase_create("interpol"); tcase_add_test(tc, interpol_test); tcase_set_timeout(tc, 5 * 60); suite_add_tcase(s, tc); sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); failed = srunner_ntests_failed(sr); srunner_free(sr); return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; }