/* Called from I/O thread context */ static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; switch (code) { case PA_SINK_MESSAGE_GET_LATENCY: /* The sink is _put() before the sink input is, so let's * make sure we don't access it in that time. Also, the * sink input is first shut down, the sink second. */ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { *((pa_usec_t*) data) = 0; return 0; } *((pa_usec_t*) data) = /* Get the latency of the master sink */ pa_sink_get_latency_within_thread(u->sink_input->sink) + /* Add the latency internal to our sink input on top */ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); return 0; } return pa_sink_process_msg(o, code, data, offset, chunk); }
/* Called from I/O thread context */ static void cmtspeech_sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); if (!PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) return; pa_log_debug("Max request of %s updated to %zu bytes", i->sink->name, nbytes); }
/* Called from I/O thread context */ static void cmtspeech_sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); if (!PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) return; pa_log_debug("%s rewound %zu bytes", i->sink->name, nbytes); }
/* Called from main context */ static void sink_set_mute_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) return; pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); }
/* Called from main context */ static void sink_set_volume_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) return; pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE); }
/* Called from IO thread context */ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); /* If we are added for the first time, ask for a rewinding so that * we are heard right-away. */ if (PA_SINK_INPUT_IS_LINKED(state) && i->thread_info.state == PA_SINK_INPUT_INIT) pa_sink_input_request_rewind(i, 0, false, true, true); }
/* Called from I/O thread context */ static void sink_request_rewind(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) return; pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE); }
/* Called from main context */ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); if (!PA_SINK_IS_LINKED(state) || !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) return 0; pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); return 0; }
/* Called from I/O thread context */ static void sink_request_rewind_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) return; /* Just hand this one over to the master sink */ pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE); }
/* Called from IO thread context */ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { memblockq_stream *u; pa_sink_input_assert_ref(i); u = MEMBLOCKQ_STREAM(i->userdata); memblockq_stream_assert_ref(u); /* If we are added for the first time, ask for a rewinding so that * we are heard right-away. */ if (PA_SINK_INPUT_IS_LINKED(state) && i->thread_info.state == PA_SINK_INPUT_INIT) pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); }
/* Called from IO thread context */ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); /* If we are added for the first time, ask for a rewinding so that * we are heard right-away. */ if (PA_SINK_INPUT_IS_LINKED(state) && i->thread_info.state == PA_SINK_INPUT_INIT) { pa_log_debug("Requesting rewind due to state change."); pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); } }
/* Called from I/O thread context */ static void sink_update_requested_latency_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) return; /* Just hand this one over to the master sink */ pa_sink_input_set_requested_latency_within_thread( u->sink_input, pa_sink_get_requested_latency_within_thread(s)); }
/* Called from I/O thread context */ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; switch (code) { case PA_SINK_MESSAGE_GET_LATENCY: /* The sink is _put() before the sink input is, so let's * make sure we don't access it yet */ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { *((int64_t*) data) = 0; return 0; } *((int64_t*) data) = /* Get the latency of the master sink */ pa_sink_get_latency_within_thread(u->sink_input->sink, true) + /* Add the latency internal to our sink input on top */ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); return 0; case PA_SINK_MESSAGE_SET_STATE: { pa_sink_state_t new_state = (pa_sink_state_t) PA_PTR_TO_UINT(data); /* When set to running or idle for the first time, request a rewind * of the master sink to make sure we are heard immediately */ if ((new_state == PA_SINK_IDLE || new_state == PA_SINK_RUNNING) && u->sink->thread_info.state == PA_SINK_INIT) { pa_log_debug("Requesting rewind due to state change."); pa_sink_input_request_rewind(u->sink_input, 0, false, true, true); } break; } } return pa_sink_process_msg(o, code, data, offset, chunk); }
/* Generic sink state change logic. Used by raw_sink and voip_sink */ int voice_sink_set_state(pa_sink *s, pa_sink *other, pa_sink_state_t state) { struct userdata *u; pa_sink *om_sink; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); if (!other) { pa_log_debug("other sink not initialized or freed"); return 0; } pa_sink_assert_ref(other); om_sink = u->master_sink; if (u->hw_sink_input && PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->hw_sink_input))) { if (pa_sink_input_get_state(u->hw_sink_input) == PA_SINK_INPUT_CORKED) { if (PA_SINK_IS_OPENED(state) || PA_SINK_IS_OPENED(pa_sink_get_state(other)) || pa_atomic_load(&u->cmt_connection.dl_state) == CMT_DL_ACTIVE) { pa_sink_input_cork(u->hw_sink_input, FALSE); pa_log_debug("hw_sink_input uncorked"); } } else { if (state == PA_SINK_SUSPENDED && pa_sink_get_state(other) == PA_SINK_SUSPENDED && pa_atomic_load(&u->cmt_connection.dl_state) != CMT_DL_ACTIVE) { pa_sink_input_cork(u->hw_sink_input, TRUE); pa_log_debug("hw_sink_input corked"); } } } if (om_sink == NULL) { pa_log_info("No master sink, assuming primary mixer tuning.\n"); pa_atomic_store(&u->mixer_state, PROP_MIXER_TUNING_PRI); } else if (pa_atomic_load(&u->cmt_connection.dl_state) == CMT_DL_ACTIVE || (pa_sink_get_state(u->voip_sink) <= PA_SINK_SUSPENDED && voice_voip_sink_used_by(u))) { if (pa_atomic_load(&u->mixer_state) == PROP_MIXER_TUNING_PRI) { pa_proplist *p = pa_proplist_new(); pa_assert(p); pa_proplist_sets(p, PROP_MIXER_TUNING_MODE, PROP_MIXER_TUNING_ALT_S); pa_sink_update_proplist(om_sink, PA_UPDATE_REPLACE, p); pa_atomic_store(&u->mixer_state, PROP_MIXER_TUNING_ALT); pa_proplist_free(p); if (u->sidetone_enable) voice_enable_sidetone(u,1); } } else { if (pa_atomic_load(&u->mixer_state) == PROP_MIXER_TUNING_ALT) { pa_proplist *p = pa_proplist_new(); pa_assert(p); pa_proplist_sets(p, PROP_MIXER_TUNING_MODE, PROP_MIXER_TUNING_PRI_S); pa_sink_update_proplist(om_sink, PA_UPDATE_REPLACE, p); pa_atomic_store(&u->mixer_state, PROP_MIXER_TUNING_PRI); pa_proplist_free(p); voice_enable_sidetone(u,0); } } return 0; }