mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
alsa: sync followers from the driver
Keep track or the follower state and do the sync from the driver wakeup. This improves sync between devices because the sync no longer depends on when the node was woken up in the graph and scheduled. We would have been able to handle this difference by using the htimestamp but that doesn't seen to work reliably on some hardware. This is also essential for the interrupt based scheduling.
This commit is contained in:
parent
c51f6ccbd7
commit
ee1bb2362d
2 changed files with 80 additions and 15 deletions
|
|
@ -502,7 +502,8 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info)
|
||||||
int err;
|
int err;
|
||||||
const char *str;
|
const char *str;
|
||||||
|
|
||||||
spa_list_append(&states, &state->link);
|
spa_list_init(&state->followers);
|
||||||
|
spa_list_init(&state->rt.followers);
|
||||||
|
|
||||||
snd_config_update_free_global();
|
snd_config_update_free_global();
|
||||||
|
|
||||||
|
|
@ -550,6 +551,8 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info)
|
||||||
}
|
}
|
||||||
CHECK(snd_output_stdio_attach(&state->output, state->log_file, 0), "attach failed");
|
CHECK(snd_output_stdio_attach(&state->output, state->log_file, 0), "attach failed");
|
||||||
|
|
||||||
|
spa_list_append(&states, &state->link);
|
||||||
|
|
||||||
state->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
|
state->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
|
||||||
state->rate_limit.burst = 1;
|
state->rate_limit.burst = 1;
|
||||||
|
|
||||||
|
|
@ -2206,7 +2209,7 @@ static int setup_matching(struct state *state)
|
||||||
|
|
||||||
static void update_sources(struct state *state, bool active)
|
static void update_sources(struct state *state, bool active)
|
||||||
{
|
{
|
||||||
if (state->disable_tsched && state->source[0].data != NULL) {
|
if (state->disable_tsched && state->rt.sources_added) {
|
||||||
for (int i = 0; i < state->n_fds; i++) {
|
for (int i = 0; i < state->n_fds; i++) {
|
||||||
state->source[i].mask = active ? state->pfds[i].events : 0;
|
state->source[i].mask = active ? state->pfds[i].events : 0;
|
||||||
spa_loop_update_source(state->data_loop, &state->source[i]);
|
spa_loop_update_source(state->data_loop, &state->source[i]);
|
||||||
|
|
@ -2419,7 +2422,7 @@ again:
|
||||||
int spa_alsa_write(struct state *state)
|
int spa_alsa_write(struct state *state)
|
||||||
{
|
{
|
||||||
int res = 0;
|
int res = 0;
|
||||||
if (state->following) {
|
if (state->following && state->rt.driver == NULL) {
|
||||||
uint64_t current_time = state->position->clock.nsec;
|
uint64_t current_time = state->position->clock.nsec;
|
||||||
if ((res = alsa_write_sync(state, current_time)) < 0)
|
if ((res = alsa_write_sync(state, current_time)) < 0)
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -2627,7 +2630,7 @@ static int alsa_read_frames(struct state *state)
|
||||||
int spa_alsa_read(struct state *state)
|
int spa_alsa_read(struct state *state)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
if (state->following) {
|
if (state->following && state->rt.driver == NULL) {
|
||||||
uint64_t current_time = state->position->clock.nsec;
|
uint64_t current_time = state->position->clock.nsec;
|
||||||
if ((res = alsa_read_sync(state, current_time)) < 0)
|
if ((res = alsa_read_sync(state, current_time)) < 0)
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -2723,7 +2726,7 @@ static uint64_t get_time_ns(struct state *state)
|
||||||
|
|
||||||
static void alsa_wakeup_event(struct spa_source *source)
|
static void alsa_wakeup_event(struct spa_source *source)
|
||||||
{
|
{
|
||||||
struct state *state = source->data;
|
struct state *state = source->data, *follower;
|
||||||
uint64_t expire, current_time;
|
uint64_t expire, current_time;
|
||||||
int res, suppressed;
|
int res, suppressed;
|
||||||
|
|
||||||
|
|
@ -2776,8 +2779,18 @@ static void alsa_wakeup_event(struct spa_source *source)
|
||||||
if (SPA_UNLIKELY(res == -EAGAIN))
|
if (SPA_UNLIKELY(res == -EAGAIN))
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* then read all sources, the sinks will be written to when the
|
spa_list_for_each(follower, &state->rt.followers, rt.driver_link) {
|
||||||
* graph completes. */
|
if (follower == state)
|
||||||
|
continue;
|
||||||
|
if (follower->stream == SND_PCM_STREAM_CAPTURE)
|
||||||
|
alsa_read_sync(follower, current_time);
|
||||||
|
else
|
||||||
|
alsa_write_sync(follower, current_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* then read this source, the sinks will be written to when the
|
||||||
|
* graph completes. We can't read other follower sources yet because
|
||||||
|
* the resampler first needs to run. */
|
||||||
if (state->stream == SND_PCM_STREAM_CAPTURE)
|
if (state->stream == SND_PCM_STREAM_CAPTURE)
|
||||||
alsa_read_frames(state);
|
alsa_read_frames(state);
|
||||||
|
|
||||||
|
|
@ -2826,31 +2839,42 @@ static void reset_buffers(struct state *this)
|
||||||
static void remove_sources(struct state *state)
|
static void remove_sources(struct state *state)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
if (state->sources_added) {
|
if (state->rt.sources_added) {
|
||||||
for (i = 0; i < state->n_fds; i++)
|
for (i = 0; i < state->n_fds; i++)
|
||||||
spa_loop_remove_source(state->data_loop, &state->source[i]);
|
spa_loop_remove_source(state->data_loop, &state->source[i]);
|
||||||
state->sources_added = false;
|
state->rt.sources_added = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_sources(struct state *state)
|
static void add_sources(struct state *state)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
if (!state->sources_added) {
|
if (!state->rt.sources_added) {
|
||||||
for (i = 0; i < state->n_fds; i++)
|
for (i = 0; i < state->n_fds; i++)
|
||||||
spa_loop_add_source(state->data_loop, &state->source[i]);
|
spa_loop_add_source(state->data_loop, &state->source[i]);
|
||||||
state->sources_added = true;
|
state->rt.sources_added = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_state_sync(struct spa_loop *loop, bool async, uint32_t seq,
|
static int do_state_sync(struct spa_loop *loop, bool async, uint32_t seq,
|
||||||
const void *data, size_t size, void *user_data)
|
const void *data, size_t size, void *user_data)
|
||||||
{
|
{
|
||||||
struct state *state = user_data;
|
struct state *state = user_data;
|
||||||
|
struct rt_state *rt = &state->rt;
|
||||||
|
|
||||||
if (state->started) {
|
if (state->started) {
|
||||||
state->next_time = get_time_ns(state);
|
state->next_time = get_time_ns(state);
|
||||||
|
|
||||||
|
if (rt->driver != state->driver) {
|
||||||
|
spa_dll_init(&state->dll);
|
||||||
|
|
||||||
|
if (rt->driver != NULL)
|
||||||
|
spa_list_remove(&rt->driver_link);
|
||||||
|
if (state->driver != NULL)
|
||||||
|
spa_list_append(&state->driver->rt.followers, &rt->driver_link);
|
||||||
|
rt->driver = state->driver;
|
||||||
|
spa_log_debug(state->log, "state:%p -> driver:%p", state, state->driver);
|
||||||
|
}
|
||||||
if (state->following) {
|
if (state->following) {
|
||||||
remove_sources(state);
|
remove_sources(state);
|
||||||
set_timeout(state, 0);
|
set_timeout(state, 0);
|
||||||
|
|
@ -2860,6 +2884,10 @@ static int do_state_sync(struct spa_loop *loop, bool async, uint32_t seq,
|
||||||
set_timeout(state, state->next_time);
|
set_timeout(state, state->next_time);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (rt->driver) {
|
||||||
|
spa_list_remove(&rt->driver_link);
|
||||||
|
rt->driver = NULL;
|
||||||
|
}
|
||||||
if (!state->disable_tsched)
|
if (!state->disable_tsched)
|
||||||
set_timeout(state, 0);
|
set_timeout(state, 0);
|
||||||
remove_sources(state);
|
remove_sources(state);
|
||||||
|
|
@ -2980,16 +3008,38 @@ int spa_alsa_start(struct state *state)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct state *find_state(uint32_t id)
|
||||||
|
{
|
||||||
|
struct state *state;
|
||||||
|
spa_list_for_each(state, &states, link) {
|
||||||
|
if (state->clock != NULL && state->clock->id == id)
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int spa_alsa_reassign_follower(struct state *state)
|
int spa_alsa_reassign_follower(struct state *state)
|
||||||
{
|
{
|
||||||
bool following, freewheel;
|
bool following, freewheel;
|
||||||
struct spa_io_position *pos = state->position;
|
struct spa_io_position *pos = state->position;
|
||||||
struct spa_io_clock *clock = state->clock;
|
struct spa_io_clock *clock = state->clock;
|
||||||
|
struct state *driver;
|
||||||
|
|
||||||
if (clock != NULL)
|
if (clock != NULL)
|
||||||
spa_scnprintf(clock->name, sizeof(clock->name), "%s", state->clock_name);
|
spa_scnprintf(clock->name, sizeof(clock->name), "%s", state->clock_name);
|
||||||
|
|
||||||
following = pos && clock && pos->clock.id != clock->id;
|
following = pos && clock && pos->clock.id != clock->id;
|
||||||
|
|
||||||
|
driver = pos != NULL ? find_state(pos->clock.id) : NULL;
|
||||||
|
|
||||||
|
if (driver != state->driver) {
|
||||||
|
spa_log_debug(state->log, "%p: reassign driver %p->%p", state, state->driver, driver);
|
||||||
|
if (state->driver != NULL)
|
||||||
|
spa_list_remove(&state->driver_link);
|
||||||
|
if (driver)
|
||||||
|
spa_list_append(&driver->followers, &state->driver_link);
|
||||||
|
state->driver = driver;
|
||||||
|
}
|
||||||
if (following != state->following) {
|
if (following != state->following) {
|
||||||
spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following);
|
spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following);
|
||||||
state->following = following;
|
state->following = following;
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,15 @@ struct card {
|
||||||
uint32_t rate;
|
uint32_t rate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct rt_state {
|
||||||
|
struct spa_list followers;
|
||||||
|
struct state *driver;
|
||||||
|
struct spa_list driver_link;
|
||||||
|
|
||||||
|
unsigned int sources_added:1;
|
||||||
|
unsigned int following:1;
|
||||||
|
};
|
||||||
|
|
||||||
struct state {
|
struct state {
|
||||||
struct spa_handle handle;
|
struct spa_handle handle;
|
||||||
struct spa_node node;
|
struct spa_node node;
|
||||||
|
|
@ -130,9 +139,9 @@ struct state {
|
||||||
uint32_t allowed_rates[MAX_RATES];
|
uint32_t allowed_rates[MAX_RATES];
|
||||||
uint32_t n_allowed_rates;
|
uint32_t n_allowed_rates;
|
||||||
struct channel_map default_pos;
|
struct channel_map default_pos;
|
||||||
unsigned int disable_mmap;
|
unsigned int disable_mmap:1;
|
||||||
unsigned int disable_batch;
|
unsigned int disable_batch:1;
|
||||||
unsigned int disable_tsched;
|
unsigned int disable_tsched:1;
|
||||||
char clock_name[64];
|
char clock_name[64];
|
||||||
uint32_t quantum_limit;
|
uint32_t quantum_limit;
|
||||||
|
|
||||||
|
|
@ -228,6 +237,12 @@ struct state {
|
||||||
double last_rate;
|
double last_rate;
|
||||||
|
|
||||||
struct spa_list link;
|
struct spa_list link;
|
||||||
|
|
||||||
|
struct spa_list followers;
|
||||||
|
struct state *driver;
|
||||||
|
struct spa_list driver_link;
|
||||||
|
|
||||||
|
struct rt_state rt;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct spa_pod *spa_alsa_enum_propinfo(struct state *state,
|
struct spa_pod *spa_alsa_enum_propinfo(struct state *state,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue