[PATCH 2/5] core: dynamic change of sampling rate

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Implement dynamic change of sampling rate. To avoid glitches, changes
are only enabled when sinks and sources are suspended. Additional checks
may be needed to prevent system sounds and alerts from reconfiguring
the sample rate

Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart at intel.com>
---
 src/modules/alsa/alsa-sink.c   |   14 ++++++++++
 src/modules/alsa/alsa-source.c |   14 ++++++++++
 src/pulsecore/sink-input.c     |   27 +++++++++++++++++++
 src/pulsecore/sink.c           |   55 ++++++++++++++++++++++++++++++++++++++++
 src/pulsecore/sink.h           |    6 ++++
 src/pulsecore/source-output.c  |   24 +++++++++++++++++
 src/pulsecore/source.c         |   44 ++++++++++++++++++++++++++++++++
 src/pulsecore/source.h         |    5 +++
 8 files changed, 189 insertions(+), 0 deletions(-)

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index f861904..3399c83 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1380,6 +1380,19 @@ static void sink_update_requested_latency_cb(pa_sink *s) {
     }
 }
 
+static pa_bool_t sink_update_rate_cb(pa_sink *s, uint32_t rate)
+{
+    struct userdata *u = s->userdata;
+    pa_assert(u);
+
+    if (!PA_SINK_IS_OPENED(s->state)) {
+        pa_log_info("Updating rate for device %s, new rate is %d",u->device_name, rate);
+        u->sink->sample_spec.rate = rate;
+        return TRUE;
+    }
+    return FALSE;
+}
+
 static int process_rewind(struct userdata *u) {
     snd_pcm_sframes_t unused;
     size_t rewind_nbytes, unused_nbytes, limit_nbytes;
@@ -2045,6 +2058,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
         u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->set_state = sink_set_state_cb;
     u->sink->set_port = sink_set_port_cb;
+    u->sink->update_rate = sink_update_rate_cb;
     u->sink->userdata = u;
 
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index d214749..3358193 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1228,6 +1228,19 @@ static void source_update_requested_latency_cb(pa_source *s) {
     update_sw_params(u);
 }
 
+static pa_bool_t source_update_rate_cb(pa_source *s, uint32_t rate)
+{
+    struct userdata *u = s->userdata;
+    pa_assert(u);
+
+    if (!PA_SOURCE_IS_OPENED(s->state)) {
+        pa_log_info("Updating rate for device %s, new rate is %d", u->device_name, rate);
+        u->source->sample_spec.rate = rate;
+        return TRUE;
+    }
+    return FALSE;
+}
+
 static void thread_func(void *userdata) {
     struct userdata *u = userdata;
     unsigned short revents = 0;
@@ -1736,6 +1749,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
         u->source->update_requested_latency = source_update_requested_latency_cb;
     u->source->set_state = source_set_state_cb;
     u->source->set_port = source_set_port_cb;
+    u->source->update_rate = source_update_rate_cb;
     u->source->userdata = u;
 
     pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index 065fd2d..fa4cbba 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -270,6 +270,18 @@ int pa_sink_input_new(
     pa_assert(pa_sample_spec_valid(&data->sample_spec));
     pa_assert(pa_channel_map_valid(&data->channel_map));
 
+    if (!(data->flags & PA_SINK_INPUT_VARIABLE_RATE) &&
+        !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec)) {
+        /* try to change sink rate. This is done before the FIXATE hook since
+           module-suspend-on-idle can resume a sink */
+
+        pa_log_info("sink_input_new: Trying to change sample rate");
+        if (pa_sink_update_rate(data->sink, data->sample_spec.rate) == TRUE)
+            pa_log_info("sink_input_new: Rate changed");
+        else
+            pa_log_info("sink_input_new: Resampling enabled to %u kHz", data->sink->sample_spec.rate);
+    }
+
     /* Due to the fixing of the sample spec the volume might not match anymore */
     pa_cvolume_remap(&data->volume, &original_cm, &data->channel_map);
 
@@ -1274,6 +1286,21 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
     if (!pa_sink_input_may_move_to(i, dest))
         return -PA_ERR_NOTSUPPORTED;
 
+    if (!(i->flags & PA_SINK_INPUT_VARIABLE_RATE) &&
+        !pa_sample_spec_equal(&i->sample_spec, &dest->sample_spec)) {
+        /* try to change dest sink rate if possible without glitches.
+           module-suspend-on-idle resumes destination sink with
+           SINK_INPUT_MOVE_FINISH hook */
+
+        pa_log_info("sink_input_finish_move: Trying to change sample rate");
+        if (pa_sink_update_rate(dest, i->sample_spec.rate) == TRUE)
+            pa_log_info("sink_input_finish_move: Rate changed to %u kHz",
+                        dest->sample_spec.rate);
+        else
+            pa_log_info("sink_input_finish_move: Resampling enabled to %u kHz",
+                        dest->sample_spec.rate);
+    }
+
     if (i->thread_info.resampler &&
         pa_sample_spec_equal(pa_resampler_output_sample_spec(i->thread_info.resampler), &dest->sample_spec) &&
         pa_channel_map_equal(pa_resampler_output_channel_map(i->thread_info.resampler), &dest->channel_map))
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 3cadbff..e8ef0a7 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -177,6 +177,7 @@ static void reset_callbacks(pa_sink *s) {
     s->request_rewind = NULL;
     s->update_requested_latency = NULL;
     s->set_port = NULL;
+    s->update_rate = NULL;
 }
 
 /* Called from main context */
@@ -1148,6 +1149,60 @@ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) {
 }
 
 /* Called from main thread */
+pa_bool_t pa_sink_update_rate(pa_sink *s, uint32_t rate)
+{
+
+    if (PA_SINK_IS_OPENED(s->state)) {
+        pa_log_info("Cannot update rate, SINK_IS_OPENED, will keep using %u kHz",
+                    s->sample_spec.rate);
+        return FALSE; /* cannot reconfigure an IDLE or RUNNING sink without glitches */
+    }
+
+    if (s->monitor_source) {
+        if (PA_SOURCE_IS_OPENED(s->monitor_source->state) == TRUE) {
+            pa_log_info("Cannot update rate, monitor source is OPENED");
+            return FALSE;
+        }
+    }
+
+    if (s->update_rate) {
+
+        uint32_t desired_rate = rate;
+        uint32_t default_rate = s->core->default_sample_spec.rate;
+        uint32_t alternate_rate = s->core->alternate_sample_rate;
+        pa_bool_t use_alternate = FALSE;
+
+        if (PA_UNLIKELY (desired_rate < 8000 ||
+                         desired_rate > PA_RATE_MAX))
+            return FALSE;
+
+        if (default_rate%4000) {
+            /* default is a 11025 multiple */
+            if ((alternate_rate%4000==0) && (desired_rate%4000==0))
+                use_alternate=TRUE;
+        } else {
+            /* default is 4000 multiple */
+            if ((alternate_rate%11025==0) && (desired_rate%11025==0))
+                use_alternate=TRUE;
+        }
+
+        if (use_alternate)
+            desired_rate = alternate_rate;
+        else
+            desired_rate = default_rate;
+
+        if (s->update_rate(s, desired_rate) == TRUE) {
+            /* update monitor source as well */
+            if (s->monitor_source)
+                pa_source_update_rate(s->monitor_source, desired_rate);
+            pa_log_info("Changed sampling rate successfully ");
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+/* Called from main thread */
 pa_usec_t pa_sink_get_latency(pa_sink *s) {
     pa_usec_t usec = 0;
 
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index 4d569dd..f478af7 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -197,6 +197,10 @@ struct pa_sink {
      * thread. */
     int (*set_port)(pa_sink *s, pa_device_port *port); /* dito */
 
+      /* Called whenever the sampling frequency shall be changed. Called from main
+     * thread. */
+    pa_bool_t (*update_rate)(pa_sink *s, uint32_t rate);
+
     /* Contains copies of the above data so that the real-time worker
      * thread can work without access locking */
     struct {
@@ -359,6 +363,8 @@ unsigned pa_device_init_priority(pa_proplist *p);
 
 /**** May be called by everyone, from main context */
 
+pa_bool_t pa_sink_update_rate(pa_sink *s, uint32_t rate);
+
 /* The returned value is supposed to be in the time domain of the sound card! */
 pa_usec_t pa_sink_get_latency(pa_sink *s);
 pa_usec_t pa_sink_get_requested_latency(pa_sink *s);
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index 88731e7..8f9b84d 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -160,6 +160,15 @@ int pa_source_output_new(
     pa_assert(pa_sample_spec_valid(&data->sample_spec));
     pa_assert(pa_channel_map_valid(&data->channel_map));
 
+    if (!(data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) &&
+        !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec)){
+        /* try to change source rate. This is done before the FIXATE hook since
+           module-suspend-on-idle can resume a source */
+
+        pa_log_info("Trying to change sample rate");
+        pa_source_update_rate(data->source, data->sample_spec.rate);
+    }
+
     if (data->resample_method == PA_RESAMPLER_INVALID)
         data->resample_method = core->resample_method;
 
@@ -783,6 +792,21 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t
     if (!pa_source_output_may_move_to(o, dest))
         return -1;
 
+    if (!(o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) &&
+        !pa_sample_spec_equal(&o->sample_spec, &dest->sample_spec)){
+        /* try to change dest sink rate if possible without glitches.
+           module-suspend-on-idle resumes destination source with
+           SOURCE_OUTPUT_MOVE_FINISH hook */
+
+        pa_log_info("source_output_finish_move: Trying to change sample rate");
+        if (pa_source_update_rate(dest, o->sample_spec.rate) == TRUE)
+            pa_log_info("source_output_finish_move: Rate changed to %u kHz",
+                        dest->sample_spec.rate);
+        else
+            pa_log_info("source_output_finish_move: Resampling enabled to %u kHz",
+                        dest->sample_spec.rate);
+    }
+
     if (o->thread_info.resampler &&
         pa_sample_spec_equal(pa_resampler_input_sample_spec(o->thread_info.resampler), &dest->sample_spec) &&
         pa_channel_map_equal(pa_resampler_input_channel_map(o->thread_info.resampler), &dest->channel_map))
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 412a3db..9c06d92 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -130,6 +130,7 @@ static void reset_callbacks(pa_source *s) {
     s->set_mute = NULL;
     s->update_requested_latency = NULL;
     s->set_port = NULL;
+    s->update_rate = NULL;
 }
 
 /* Called from main context */
@@ -708,6 +709,49 @@ void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *
 }
 
 /* Called from main thread */
+pa_bool_t pa_source_update_rate(pa_source *s, uint32_t rate)
+{
+
+    if (PA_SOURCE_IS_OPENED(s->state)) {
+        pa_log_info("Cannot update rate, SOURCE_IS_OPENED, will keep using %u kHz",
+                    s->sample_spec.rate);
+        return FALSE; /* cannot reconfigure an IDLE or RUNNING source without glitches */
+    }
+
+    if (s->update_rate) {
+        uint32_t desired_rate = rate;
+        uint32_t default_rate = s->core->default_sample_spec.rate;
+        uint32_t alternate_rate = s->core->alternate_sample_rate;
+        pa_bool_t use_alternate = FALSE;
+
+        if (PA_UNLIKELY (desired_rate < 8000 ||
+                         desired_rate > PA_RATE_MAX))
+            return FALSE;
+
+        if (default_rate%4000) {
+            /* default is a 11025 multiple */
+            if ((alternate_rate%4000==0) && (desired_rate%4000==0))
+                use_alternate=TRUE;
+        } else {
+            /* default is 4000 multiple */
+            if ((alternate_rate%11025==0) && (desired_rate%11025==0))
+                use_alternate=TRUE;
+        }
+
+        if (use_alternate)
+            desired_rate = alternate_rate;
+        else
+            desired_rate = default_rate;
+
+        if (s->update_rate(s, rate) == TRUE) {
+            pa_log_info("Changed sampling rate successfully ");
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+/* Called from main thread */
 pa_usec_t pa_source_get_latency(pa_source *s) {
     pa_usec_t usec;
 
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index e3e56bc..4712cb9 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -133,6 +133,10 @@ struct pa_source {
      * thread. */
     int (*set_port)(pa_source *s, pa_device_port *port); /*dito */
 
+    /* Called whenever the sampling frequency shall be changed. Called from main
+     * thread. */
+    pa_bool_t (*update_rate)(pa_source *s, uint32_t rate);
+
     /* Contains copies of the above data so that the real-time worker
      * thread can work without access locking */
     struct {
@@ -273,6 +277,7 @@ pa_bool_t pa_source_get_mute(pa_source *source, pa_bool_t force_refresh);
 pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p);
 
 int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save);
+pa_bool_t pa_source_update_rate(pa_source *s, uint32_t rate);
 
 unsigned pa_source_linked_by(pa_source *s); /* Number of connected streams */
 unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that are not corked */
-- 
1.7.4




[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux