SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
SINK_INPUT_MESSAGE_REWIND,
SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT,
- SINK_INPUT_MESSAGE_SINK_CHANGED,
SINK_INPUT_MESSAGE_SOURCE_CHANGED,
SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY
};
if (u->max_source_latency >= MIN_DEVICE_LATENCY)
u->min_source_latency = PA_MAX(u->min_source_latency, MIN_DEVICE_LATENCY);
else
- u->min_source_latency = u->max_source_latency;
+ u->min_source_latency = u->max_source_latency;
}
if (sink) {
if (u->max_sink_latency >= MIN_DEVICE_LATENCY)
u->min_sink_latency = PA_MAX(u->min_sink_latency, MIN_DEVICE_LATENCY);
else
- u->min_sink_latency = u->max_sink_latency;
+ u->min_sink_latency = u->max_sink_latency;
}
}
* Get current effective latency of the source. If the source is in use with
* smaller latency than the configured latency, it will continue running with
* the smaller value when the source output is switched to the source. */
-static void get_effective_source_latency(struct userdata *u, pa_source *source, pa_sink *sink) {
+static void update_effective_source_latency(struct userdata *u, pa_source *source, pa_sink *sink) {
pa_usec_t effective_source_latency;
effective_source_latency = u->configured_source_latency;
/* Called from main thread.
* Set source output latency to one third of the overall latency if possible.
* The choice of one third is rather arbitrary somewhere between the minimum
- * possible latency (which would cause a lot of CPU load) and half the configured
- * latency (which would lead to an empty memblockq if the sink is configured
- * likewise). */
+ * possible latency which would cause a lot of CPU load and half the configured
+ * latency which would quickly lead to underruns */
static void set_source_output_latency(struct userdata *u, pa_source *source) {
pa_usec_t latency, requested_latency;
/* Set latency and calculate latency limits */
update_latency_boundaries(u, dest, NULL);
set_source_output_latency(u, dest);
- get_effective_source_latency(u, dest, u->sink_input->sink);
+ update_effective_source_latency(u, dest, u->sink_input->sink);
- if (pa_source_get_state(dest) == PA_SOURCE_SUSPENDED) {
- if (dest->suspend_cause != PA_SUSPEND_IDLE)
- pa_sink_input_cork(u->sink_input, true);
- } else
+ if (pa_source_get_state(dest) == PA_SOURCE_SUSPENDED)
+ pa_sink_input_cork(u->sink_input, true);
+ else
pa_sink_input_cork(u->sink_input, false);
update_adjust_timer(u);
pa_assert_ctl_context();
pa_assert_se(u = o->userdata);
- /* If the source output has been suspended, we need to handle this like
- * a source change when the source output is resumed */
+ /* If the source has been suspended, we need to handle this like
+ * a source change when the source is resumed */
if (suspended) {
if (u->sink_input->sink)
pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_SOURCE_CHANGED, NULL, 0, NULL);
} else
/* Get effective source latency on unsuspend */
- get_effective_source_latency(u, u->source_output->source, u->sink_input->sink);
+ update_effective_source_latency(u, u->source_output->source, u->sink_input->sink);
pa_sink_input_cork(u->sink_input, suspended);
/* While pop has not been called, latency adjustments in SINK_INPUT_MESSAGE_POST are
* enabled. Disable them on second pop and enable the final adjustment during the
- * next push. We are waiting for the second pop, because the first pop is called
- * before the sink is actually started. */
+ * next push. The adjustment must be done on the next push, because there is no way
+ * to retrieve the source latency here. We are waiting for the second pop, because
+ * the first pop may be called before the sink is actually started. */
if (!u->output_thread_info.pop_called && u->output_thread_info.first_pop_done) {
u->output_thread_info.pop_adjust = true;
u->output_thread_info.pop_called = true;
chunk->length = PA_MIN(chunk->length, nbytes);
pa_memblockq_drop(u->memblockq, chunk->length);
- /* If push has not been called yet, assume that the source will deliver one full latency
- * when it starts pushing. Adjust the memblockq accordingly and ensure that there is
+ /* Adjust the memblockq to ensure that there is
* enough data in the queue to avoid underruns. */
if (!u->output_thread_info.push_called)
- memblockq_adjust(u, u->output_thread_info.effective_source_latency, true);
+ memblockq_adjust(u, 0, true);
return 0;
}
pa_memblockq_push_align(u->memblockq, chunk);
/* If push has not been called yet, latency adjustments in sink_input_pop_cb()
- * are enabled. Disable them on first push and correct the memblockq. Do the
- * same if the pop_cb() requested the adjustment */
- if (!u->output_thread_info.push_called || u->output_thread_info.pop_adjust) {
+ * are enabled. Disable them on first push and correct the memblockq. If pop
+ * has not been called yet, wait until the pop_cb() requests the adjustment */
+ if (u->output_thread_info.pop_called && (!u->output_thread_info.push_called || u->output_thread_info.pop_adjust)) {
pa_usec_t time_delta;
+ /* This is the source latency at the time push was called */
time_delta = PA_PTR_TO_UINT(data);
- time_delta += pa_rtclock_now() - offset;
+ /* Add the time between push and post */
+ time_delta += pa_rtclock_now() - (pa_usec_t) offset;
+ /* Add the sink latency */
time_delta += pa_sink_get_latency_within_thread(u->sink_input->sink);
- /* If the source has overrun, assume that the maximum it should have pushed is
- * one full source latency. It may still be possible that the next push also
- * contains too much data, then the resulting latency will be wrong. */
+ /* The source latency report includes the audio in the chunk,
+ * but since we already pushed the chunk to the memblockq, we need
+ * to subtract the chunk size from the source latency so that it
+ * won't be counted towards both the memblockq latency and the
+ * source latency.
+ *
+ * Sometimes the alsa source reports way too low latency (might
+ * be a bug in the alsa source code). This seems to happen when
+ * there's an overrun. As an attempt to detect overruns, we
+ * check if the chunk size is larger than the configured source
+ * latency. If so, we assume that the source should have pushed
+ * a chunk whose size equals the configured latency, so we
+ * modify time_delta only by that amount, which makes
+ * memblockq_adjust() drop more data than it would otherwise.
+ * This seems to work quite well, but it's possible that the
+ * next push also contains too much data, and in that case the
+ * resulting latency will be wrong. */
if (pa_bytes_to_usec(chunk->length, &u->sink_input->sample_spec) > u->output_thread_info.effective_source_latency)
time_delta = PA_CLIP_SUB(time_delta, u->output_thread_info.effective_source_latency);
else
time_delta = PA_CLIP_SUB(time_delta, pa_bytes_to_usec(chunk->length, &u->sink_input->sample_spec));
+ /* FIXME: We allow pushing silence here to fix up the latency. This
+ * might lead to a gap in the stream */
memblockq_adjust(u, time_delta, true);
u->output_thread_info.pop_adjust = false;
/* If pop has not been called yet, make sure the latency does not grow too much.
* Don't push any silence here, because we already have new data in the queue */
if (!u->output_thread_info.pop_called)
- memblockq_adjust(u, pa_sink_get_latency_within_thread(u->sink_input->sink), false);
+ memblockq_adjust(u, 0, false);
/* Is this the end of an underrun? Then let's start things
* right-away */
return 0;
- case SINK_INPUT_MESSAGE_SINK_CHANGED:
-
- u->output_thread_info.pop_called = false;
- u->output_thread_info.first_pop_done = false;
-
- return 0;
-
case SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY:
u->output_thread_info.effective_source_latency = (pa_usec_t)offset;
/* Called from main thread.
* Set sink input latency to one third of the overall latency if possible.
* The choice of one third is rather arbitrary somewhere between the minimum
- * possible latency (which would cause a lot of CPU load) and half the configured
- * latency (which would lead to an empty memblockq if the source is configured
- * likewise). */
+ * possible latency which would cause a lot of CPU load and half the configured
+ * latency which would quickly lead to underruns. */
static void set_sink_input_latency(struct userdata *u, pa_sink *sink) {
pa_usec_t latency, requested_latency;
/* Set latency and calculate latency limits */
update_latency_boundaries(u, NULL, dest);
set_sink_input_latency(u, dest);
- get_effective_source_latency(u, u->source_output->source, dest);
+ update_effective_source_latency(u, u->source_output->source, dest);
- if (pa_sink_get_state(dest) == PA_SINK_SUSPENDED) {
- if (dest->suspend_cause != PA_SUSPEND_IDLE)
- pa_source_output_cork(u->source_output, true);
- } else
+ if (pa_sink_get_state(dest) == PA_SINK_SUSPENDED)
+ pa_source_output_cork(u->source_output, true);
+ else
pa_source_output_cork(u->source_output, false);
update_adjust_timer(u);
- /* Send a message to the output thread that the sink has changed */
- pa_asyncmsgq_send(dest->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_SINK_CHANGED, NULL, 0, NULL);
+ u->output_thread_info.pop_called = false;
+ u->output_thread_info.first_pop_done = false;
}
/* Called from main thread */
pa_assert_ctl_context();
pa_assert_se(u = i->userdata);
- /* If the sink input has been suspended, we need to handle this like
- * a sink change when the sink input is resumed. Because the sink input
+ /* If the sink has been suspended, we need to handle this like
+ * a sink change when the sink is resumed. Because the sink
* is suspended, we can set the variables directly. */
if (suspended) {
u->output_thread_info.pop_called = false;
u->output_thread_info.first_pop_done = false;
+
} else
/* Set effective source latency on unsuspend */
- get_effective_source_latency(u, u->source_output->source, u->sink_input->sink);
+ update_effective_source_latency(u, u->source_output->source, u->sink_input->sink);
pa_source_output_cork(u->source_output, suspended);
u->sink_input->suspend = sink_input_suspend_cb;
u->sink_input->userdata = u;
- update_latency_boundaries(u, NULL, u->sink_input->sink);
- set_sink_input_latency(u, u->sink_input->sink);
-
pa_source_output_new_data_init(&source_output_data);
source_output_data.driver = __FILE__;
source_output_data.module = m;
u->source_output->userdata = u;
update_latency_boundaries(u, u->source_output->source, u->sink_input->sink);
+ set_sink_input_latency(u, u->sink_input->sink);
set_source_output_latency(u, u->source_output->source);
pa_sink_input_get_silence(u->sink_input, &silence);
pa_proplist_sets(u->sink_input->proplist, PA_PROP_MEDIA_ICON_NAME, n);
/* The output thread is not yet running, set effective_source_latency directly */
- get_effective_source_latency(u, u->source_output->source, NULL);
+ update_effective_source_latency(u, u->source_output->source, NULL);
pa_sink_input_put(u->sink_input);
pa_source_output_put(u->source_output);