diff options
| -rw-r--r-- | src/Makefile.am | 1 | ||||
| -rw-r--r-- | src/polyp/introspect.c | 55 | ||||
| -rw-r--r-- | src/polyp/introspect.h | 7 | ||||
| -rw-r--r-- | src/polypcore/cli-command.c | 54 | ||||
| -rw-r--r-- | src/polypcore/core-def.h | 30 | ||||
| -rw-r--r-- | src/polypcore/native-common.h | 2 | ||||
| -rw-r--r-- | src/polypcore/pdispatch.c | 1 | ||||
| -rw-r--r-- | src/polypcore/protocol-native.c | 36 | ||||
| -rw-r--r-- | src/polypcore/sink.h | 6 | ||||
| -rw-r--r-- | src/polypcore/source.c | 51 | ||||
| -rw-r--r-- | src/polypcore/source.h | 9 | 
11 files changed, 232 insertions, 20 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 410fd702..f36630e7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -425,6 +425,7 @@ polypcoreinclude_HEADERS = \  		polypcore/cli-text.h \  		polypcore/client.h \  		polypcore/core.h \ +		polypcore/core-def.h \  		polypcore/core-scache.h \  		polypcore/core-subscribe.h \  		polypcore/conf-parser.h \ diff --git a/src/polyp/introspect.c b/src/polyp/introspect.c index 043d2076..4fa23841 100644 --- a/src/polyp/introspect.c +++ b/src/polyp/introspect.c @@ -252,6 +252,7 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,                  pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||                  pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||                  pa_tagstruct_getu32(t, &i.owner_module) < 0 || +                pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||                  pa_tagstruct_getu32(t, &i.monitor_of_sink) < 0 ||                  pa_tagstruct_gets(t, &i.monitor_of_sink_name) < 0 ||                  pa_tagstruct_get_usec(t, &i.latency) < 0 || @@ -727,6 +728,60 @@ pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, cons      return o;  } +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { +    pa_operation *o; +    pa_tagstruct *t; +    uint32_t tag; + +    assert(c); +    assert(c->ref >= 1); +    assert(volume); + +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + +    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + +    t = pa_tagstruct_new(NULL, 0); +    pa_tagstruct_putu32(t, PA_COMMAND_SET_SOURCE_VOLUME); +    pa_tagstruct_putu32(t, tag = c->ctag++); +    pa_tagstruct_putu32(t, idx); +    pa_tagstruct_puts(t, NULL); +    pa_tagstruct_put_cvolume(t, volume); +    pa_pstream_send_tagstruct(c->pstream, t); +    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o)); + +    return o; +} + +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { +    pa_operation *o; +    pa_tagstruct *t; +    uint32_t tag; + +    assert(c); +    assert(c->ref >= 1); +    assert(name); +    assert(volume); + +    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); +    PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); +     +    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + +    t = pa_tagstruct_new(NULL, 0); +    pa_tagstruct_putu32(t, PA_COMMAND_SET_SOURCE_VOLUME); +    pa_tagstruct_putu32(t, tag = c->ctag++); +    pa_tagstruct_putu32(t, PA_INVALID_INDEX); +    pa_tagstruct_puts(t, name); +    pa_tagstruct_put_cvolume(t, volume); +    pa_pstream_send_tagstruct(c->pstream, t); +    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o)); + +    return o; +} +  /** Sample Cache **/  static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { diff --git a/src/polyp/introspect.h b/src/polyp/introspect.h index d4ff65fb..d7fad9c4 100644 --- a/src/polyp/introspect.h +++ b/src/polyp/introspect.h @@ -84,6 +84,7 @@ typedef struct pa_source_info {      pa_sample_spec sample_spec;         /**< Sample spec of this source */      pa_channel_map channel_map;         /**< Channel map \since 0.9 */      uint32_t owner_module;              /**< Owning module index, or PA_INVALID_INDEX */ +    pa_cvolume volume;                  /**< Volume of the source \since 0.8 */      uint32_t monitor_of_sink;           /**< If this is a monitor source the index of the owning sink, otherwise PA_INVALID_INDEX */      const char *monitor_of_sink_name;   /**< Name of the owning sink, or PA_INVALID_INDEX */      pa_usec_t latency;                  /**< Length of filled record buffer of this source. \since 0.5 */ @@ -213,6 +214,12 @@ pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name  /** Set the volume of a sink input stream */  pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); +/** Set the volume of a source device specified by its index \since 0.8 */ +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a source device specified by its name \since 0.8 */ +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); +  /** Memory block statistics */  typedef struct pa_stat_info {      uint32_t memblock_total;           /**< Currently allocated memory blocks */ diff --git a/src/polypcore/cli-command.c b/src/polypcore/cli-command.c index 6c9fc4b1..cd7993a8 100644 --- a/src/polypcore/cli-command.c +++ b/src/polypcore/cli-command.c @@ -77,6 +77,7 @@ static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int  static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail);  static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail);  static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); +static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail);  static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail);  static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail);  static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); @@ -114,6 +115,7 @@ static const struct command commands[] = {      { "unload-module",           pa_cli_command_unload,             "Unload a module (args: index)",                             2},      { "set-sink-volume",         pa_cli_command_sink_volume,        "Set the volume of a sink (args: index|name, volume)",             3},      { "set-sink-input-volume",   pa_cli_command_sink_input_volume,  "Set the volume of a sink input (args: index|name, volume)", 3}, +    { "set-source-volume",       pa_cli_command_source_volume,      "Set the volume of a source (args: index|name, volume)", 3},      { "set-default-sink",        pa_cli_command_sink_default,       "Set the default sink (args: index|name)", 2},      { "set-default-source",      pa_cli_command_source_default,     "Set the default source (args: index|name)", 2},      { "kill-client",             pa_cli_command_kill_client,        "Kill a client (args: index)", 2}, @@ -376,6 +378,37 @@ static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strb      return 0;  } +static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +    const char *n, *v; +    pa_source *source; +    uint32_t volume; +    pa_cvolume cvolume; + +    if (!(n = pa_tokenizer_get(t, 1))) { +        pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); +        return -1; +    } + +    if (!(v = pa_tokenizer_get(t, 2))) { +        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n"); +        return -1; +    } + +    if (pa_atou(v, &volume) < 0) { +        pa_strbuf_puts(buf, "Failed to parse volume.\n"); +        return -1; +    } + +    if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 1))) { +        pa_strbuf_puts(buf, "No source found by this name or index.\n"); +        return -1; +    } + +    pa_cvolume_set(&cvolume, source->sample_spec.channels, volume); +    pa_source_set_volume(source, PA_MIXER_HARDWARE, &cvolume); +    return 0; +} +  static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) {      const char *n;      assert(c && t); @@ -633,7 +666,8 @@ static int pa_cli_command_list_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf  static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) {      pa_module *m; -    pa_sink *s; +    pa_sink *sink; +    pa_source *source;      int nl;      const char *p;      uint32_t idx; @@ -667,8 +701,20 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G      nl = 0; -    for (s = pa_idxset_first(c->sinks, &idx); s; s = pa_idxset_next(c->sinks, &idx)) { -        if (s->owner && s->owner->auto_unload) +    for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) { +        if (sink->owner && sink->owner->auto_unload) +            continue; + +        if (!nl) { +            pa_strbuf_puts(buf, "\n"); +            nl = 1; +        } + +        pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, PA_MIXER_HARDWARE))); +    } + +    for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) { +        if (source->owner && source->owner->auto_unload)              continue;          if (!nl) { @@ -676,7 +722,7 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G              nl = 1;          } -        pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", s->name, pa_cvolume_avg(pa_sink_get_volume(s, PA_MIXER_HARDWARE))); +        pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_avg(pa_source_get_volume(source, PA_MIXER_HARDWARE)));      } diff --git a/src/polypcore/core-def.h b/src/polypcore/core-def.h new file mode 100644 index 00000000..89cc47f1 --- /dev/null +++ b/src/polypcore/core-def.h @@ -0,0 +1,30 @@ +#ifndef foocoredefhfoo +#define foocoredefhfoo + +/* $Id$ */ + +/*** +  This file is part of polypaudio. +  +  polypaudio is free software; you can redistribute it and/or modify +  it under the terms of the GNU Lesser General Public License as published +  by the Free Software Foundation; either version 2 of the License, +  or (at your option) any later version. +  +  polypaudio is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +  General Public License for more details. +  +  You should have received a copy of the GNU Lesser General Public License +  along with polypaudio; if not, write to the Free Software +  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +  USA. +***/ + +typedef enum pa_mixer { +    PA_MIXER_SOFTWARE, +    PA_MIXER_HARDWARE +} pa_mixer_t; + +#endif diff --git a/src/polypcore/native-common.h b/src/polypcore/native-common.h index 0d17b022..a5ca5c96 100644 --- a/src/polypcore/native-common.h +++ b/src/polypcore/native-common.h @@ -71,6 +71,8 @@ enum {      PA_COMMAND_SET_SINK_VOLUME,      PA_COMMAND_SET_SINK_INPUT_VOLUME, +    PA_COMMAND_SET_SOURCE_VOLUME, +          PA_COMMAND_CORK_PLAYBACK_STREAM,      PA_COMMAND_FLUSH_PLAYBACK_STREAM,      PA_COMMAND_TRIGGER_PLAYBACK_STREAM, diff --git a/src/polypcore/pdispatch.c b/src/polypcore/pdispatch.c index c082b8cc..2cc90bc2 100644 --- a/src/polypcore/pdispatch.c +++ b/src/polypcore/pdispatch.c @@ -82,6 +82,7 @@ static const char *command_names[PA_COMMAND_MAX] = {      [PA_COMMAND_SUBSCRIBE_EVENT] = "SUBSCRIBE_EVENT",      [PA_COMMAND_SET_SINK_VOLUME] = "SET_SINK_VOLUME",      [PA_COMMAND_SET_SINK_INPUT_VOLUME] = "SET_SINK_INPUT_VOLUME", +    [PA_COMMAND_SET_SOURCE_VOLUME] = "SET_SOURCE_VOLME",      [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = "TRIGGER_PLAYBACK_STREAM",      [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = "FLUSH_PLAYBACK_STREAM",      [PA_COMMAND_CORK_PLAYBACK_STREAM] = "CORK_PLAYBACK_STREAM", diff --git a/src/polypcore/protocol-native.c b/src/polypcore/protocol-native.c index e73ff761..cd1a685f 100644 --- a/src/polypcore/protocol-native.c +++ b/src/polypcore/protocol-native.c @@ -216,6 +216,7 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_SET_SINK_VOLUME] = command_set_volume,      [PA_COMMAND_SET_SINK_INPUT_VOLUME] = command_set_volume, +    [PA_COMMAND_SET_SOURCE_VOLUME] = command_set_volume,      [PA_COMMAND_CORK_PLAYBACK_STREAM] = command_cork_playback_stream,      [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = command_flush_playback_stream, @@ -1254,16 +1255,20 @@ static void sink_fill_tagstruct(pa_tagstruct *t, pa_sink *sink) {  static void source_fill_tagstruct(pa_tagstruct *t, pa_source *source) {      assert(t && source); -    pa_tagstruct_putu32(t, source->index); -    pa_tagstruct_puts(t, source->name); -    pa_tagstruct_puts(t, source->description); -    pa_tagstruct_put_sample_spec(t, &source->sample_spec); -    pa_tagstruct_put_channel_map(t, &source->channel_map); -    pa_tagstruct_putu32(t, source->owner ? source->owner->index : (uint32_t) -1); -    pa_tagstruct_putu32(t, source->monitor_of ? source->monitor_of->index : (uint32_t) -1); -    pa_tagstruct_puts(t, source->monitor_of ? source->monitor_of->name : NULL); -    pa_tagstruct_put_usec(t, pa_source_get_latency(source)); -    pa_tagstruct_puts(t, source->driver); +    pa_tagstruct_put( +        t, +        PA_TAG_U32, source->index, +        PA_TAG_STRING, source->name, +        PA_TAG_STRING, source->description, +        PA_TAG_SAMPLE_SPEC, &source->sample_spec, +        PA_TAG_CHANNEL_MAP, &source->channel_map, +        PA_TAG_U32, source->owner ? source->owner->index : PA_INVALID_INDEX, +        PA_TAG_CVOLUME, pa_source_get_volume(source, PA_MIXER_HARDWARE), +        PA_TAG_U32, source->monitor_of ? source->monitor_of->index : PA_INVALID_INDEX, +        PA_TAG_STRING, source->monitor_of ? source->monitor_of->name : NULL, +        PA_TAG_USEC, pa_source_get_latency(source), +        PA_TAG_STRING, source->driver, +        PA_TAG_INVALID);  }  static void client_fill_tagstruct(pa_tagstruct *t, pa_client *client) { @@ -1558,12 +1563,14 @@ static void command_set_volume(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command,      uint32_t idx;      pa_cvolume volume;      pa_sink *sink = NULL; +    pa_source *source = NULL;      pa_sink_input *si = NULL;      const char *name = NULL;      assert(c && t);      if (pa_tagstruct_getu32(t, &idx) < 0 ||          (command == PA_COMMAND_SET_SINK_VOLUME && pa_tagstruct_gets(t, &name) < 0) || +        (command == PA_COMMAND_SET_SOURCE_VOLUME && pa_tagstruct_gets(t, &name) < 0) ||          pa_tagstruct_get_cvolume(t, &volume) ||          !pa_tagstruct_eof(t)) {          protocol_error(c); @@ -1580,18 +1587,25 @@ static void command_set_volume(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command,              sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx);          else              sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK, 1); +    } else if (command == PA_COMMAND_SET_SOURCE_VOLUME) { +        if (idx != (uint32_t) -1) +            source = pa_idxset_get_by_index(c->protocol->core->sources, idx); +        else +            source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1);      }  else {          assert(command == PA_COMMAND_SET_SINK_INPUT_VOLUME);          si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);      } -    if (!si && !sink) { +    if (!si && !sink && !source) {          pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);          return;      }      if (sink)          pa_sink_set_volume(sink, PA_MIXER_HARDWARE, &volume); +    else if (source) +        pa_source_set_volume(source, PA_MIXER_HARDWARE, &volume);      else if (si)          pa_sink_input_set_volume(si, &volume); diff --git a/src/polypcore/sink.h b/src/polypcore/sink.h index fa120ebf..5f1a6cc8 100644 --- a/src/polypcore/sink.h +++ b/src/polypcore/sink.h @@ -29,6 +29,7 @@ typedef struct pa_sink pa_sink;  #include <polyp/sample.h>  #include <polyp/channelmap.h>  #include <polyp/volume.h> +#include <polypcore/core-def.h>  #include <polypcore/core.h>  #include <polypcore/idxset.h>  #include <polypcore/source.h> @@ -41,11 +42,6 @@ typedef enum pa_sink_state {      PA_SINK_DISCONNECTED  } pa_sink_state_t; -typedef enum pa_mixer { -    PA_MIXER_SOFTWARE, -    PA_MIXER_HARDWARE -} pa_mixer_t; -  struct pa_sink {      int ref;      uint32_t index; diff --git a/src/polypcore/source.c b/src/polypcore/source.c index b70cb129..47325a1b 100644 --- a/src/polypcore/source.c +++ b/src/polypcore/source.c @@ -77,8 +77,13 @@ pa_source* pa_source_new(      s->outputs = pa_idxset_new(NULL, NULL);      s->monitor_of = NULL; +    pa_cvolume_reset(&s->sw_volume, spec->channels); +    pa_cvolume_reset(&s->hw_volume, spec->channels); +      s->get_latency = NULL;      s->notify = NULL; +    s->set_hw_volume = NULL; +    s->get_hw_volume = NULL;      s->userdata = NULL;      r = pa_idxset_put(core->sources, s, &s->index); @@ -110,6 +115,8 @@ void pa_source_disconnect(pa_source *s) {      s->get_latency = NULL;      s->notify = NULL; +    s->get_hw_volume = NULL; +    s->set_hw_volume = NULL;      s->state = PA_SOURCE_DISCONNECTED;      pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); @@ -173,7 +180,14 @@ void pa_source_post(pa_source*s, const pa_memchunk *chunk) {      assert(chunk);      pa_source_ref(s); + +    if (!pa_cvolume_is_norm(&s->sw_volume)) { +        pa_memchunk_make_writable(chunk, s->core->memblock_stat, 0); +        pa_volume_memchunk(chunk, &s->sample_spec, &s->sw_volume); +    } +      pa_idxset_foreach(s->outputs, do_post, (void*) chunk); +      pa_source_unref(s);  } @@ -194,3 +208,40 @@ pa_usec_t pa_source_get_latency(pa_source *s) {      return s->get_latency(s);  } +void pa_source_set_volume(pa_source *s, pa_mixer_t m, const pa_cvolume *volume) { +    pa_cvolume *v; +     +    assert(s); +    assert(s->ref >= 1); +    assert(volume); + +    if (m == PA_MIXER_HARDWARE && s->set_hw_volume)  +        v = &s->hw_volume; +    else +        v = &s->sw_volume; + +    if (pa_cvolume_equal(v, volume)) +        return; +         +    *v = *volume; + +    if (v == &s->hw_volume) +        if (s->set_hw_volume(s) < 0) +            s->sw_volume =  *volume; + +    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +const pa_cvolume *pa_source_get_volume(pa_source *s, pa_mixer_t m) { +    assert(s); +    assert(s->ref >= 1); + +    if (m == PA_MIXER_HARDWARE && s->set_hw_volume) { + +        if (s->get_hw_volume) +            s->get_hw_volume(s); +         +        return &s->hw_volume; +    } else +        return &s->sw_volume; +} diff --git a/src/polypcore/source.h b/src/polypcore/source.h index a267e73c..3c5bb6c4 100644 --- a/src/polypcore/source.h +++ b/src/polypcore/source.h @@ -28,6 +28,8 @@ typedef struct pa_source pa_source;  #include <polyp/sample.h>  #include <polyp/channelmap.h> +#include <polyp/volume.h> +#include <polypcore/core-def.h>  #include <polypcore/core.h>  #include <polypcore/idxset.h>  #include <polypcore/memblock.h> @@ -56,9 +58,13 @@ struct pa_source {      pa_idxset *outputs;      pa_sink *monitor_of; + +    pa_cvolume hw_volume, sw_volume;      void (*notify)(pa_source*source);      pa_usec_t (*get_latency)(pa_source *s); +    int (*set_hw_volume)(pa_source *s); +    int (*get_hw_volume)(pa_source *s);      void *userdata;  }; @@ -84,4 +90,7 @@ void pa_source_set_owner(pa_source *s, pa_module *m);  pa_usec_t pa_source_get_latency(pa_source *s); +void pa_source_set_volume(pa_source *source, pa_mixer_t m, const pa_cvolume *volume); +const pa_cvolume *pa_source_get_volume(pa_source *source, pa_mixer_t m); +  #endif  | 
