mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-03-26 07:58:03 -04:00
filter-chain: support no input or output streams
When the graph has no inputs and the channels is set to 0, don't create a capture stream. Likewise, don't create a playback stream when there are no graph outputs and the output channels is 0. You can use this to make a sine source or a null sink.
This commit is contained in:
parent
7f2cce1021
commit
56a4ab5234
1 changed files with 163 additions and 129 deletions
|
|
@ -193,6 +193,10 @@ extern struct spa_handle_factory spa_filter_graph_factory;
|
||||||
* graph will then be duplicated as many times to match the number of input/output
|
* graph will then be duplicated as many times to match the number of input/output
|
||||||
* channels of the streams.
|
* channels of the streams.
|
||||||
*
|
*
|
||||||
|
* If the graph has no inputs and the capture channels is set as 0, only the
|
||||||
|
* playback stream will be created. Likewise, if there are no outputs and the
|
||||||
|
* playback channels is 0, there will be no capture stream created.
|
||||||
|
*
|
||||||
* ### Volumes
|
* ### Volumes
|
||||||
*
|
*
|
||||||
* Normally the volume of the sink/source is handled by the stream software volume.
|
* Normally the volume of the sink/source is handled by the stream software volume.
|
||||||
|
|
@ -1249,92 +1253,105 @@ static void capture_destroy(void *d)
|
||||||
impl->capture = NULL;
|
impl->capture = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void capture_process(void *d)
|
static void do_process(struct impl *impl)
|
||||||
{
|
{
|
||||||
struct impl *impl = d;
|
struct pw_buffer *in, *out;
|
||||||
int res;
|
uint32_t i, n_in = 0, n_out = 0, data_size = 0;
|
||||||
if ((res = pw_stream_trigger_process(impl->playback)) < 0) {
|
struct spa_data *bd;
|
||||||
pw_log_debug("playback trigger error: %s", spa_strerror(res));
|
const void *cin[128];
|
||||||
|
void *cout[128];
|
||||||
|
|
||||||
|
in = out = NULL;
|
||||||
|
if (impl->capture) {
|
||||||
while (true) {
|
while (true) {
|
||||||
struct pw_buffer *t;
|
struct pw_buffer *t;
|
||||||
if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL)
|
if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL)
|
||||||
break;
|
break;
|
||||||
/* playback part is not ready, consume, discard and recycle
|
if (in)
|
||||||
* the capture buffers */
|
pw_stream_queue_buffer(impl->capture, in);
|
||||||
pw_stream_queue_buffer(impl->capture, t);
|
in = t;
|
||||||
}
|
}
|
||||||
|
if (in == NULL) {
|
||||||
|
pw_log_debug("%p: out of capture buffers: %m", impl);
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < in->buffer->n_datas; i++) {
|
||||||
|
uint32_t offs, size;
|
||||||
|
|
||||||
|
bd = &in->buffer->datas[i];
|
||||||
|
|
||||||
|
offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
|
||||||
|
size = SPA_MIN(bd->chunk->size, bd->maxsize - offs);
|
||||||
|
|
||||||
|
cin[n_in++] = SPA_PTROFF(bd->data, offs, void);
|
||||||
|
|
||||||
|
data_size = i == 0 ? size : SPA_MIN(data_size, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (impl->playback) {
|
||||||
|
out = pw_stream_dequeue_buffer(impl->playback);
|
||||||
|
if (out == NULL) {
|
||||||
|
pw_log_debug("%p: out of playback buffers: %m", impl);
|
||||||
|
} else {
|
||||||
|
if (data_size == 0)
|
||||||
|
data_size = out->requested * sizeof(float);
|
||||||
|
|
||||||
|
for (i = 0; i < out->buffer->n_datas; i++) {
|
||||||
|
bd = &out->buffer->datas[i];
|
||||||
|
|
||||||
|
data_size = SPA_MIN(data_size, bd->maxsize);
|
||||||
|
|
||||||
|
cout[n_out++] = bd->data;
|
||||||
|
|
||||||
|
bd->chunk->offset = 0;
|
||||||
|
bd->chunk->size = data_size;
|
||||||
|
bd->chunk->stride = sizeof(float);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw_log_trace_fp("%p: size:%d requested:%"PRIu64, impl,
|
||||||
|
data_size, out->requested);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; n_in < impl->n_inputs; i++)
|
||||||
|
cin[n_in++] = NULL;
|
||||||
|
for (; n_out < impl->n_outputs; i++)
|
||||||
|
cout[n_out++] = NULL;
|
||||||
|
|
||||||
|
if (impl->graph_active)
|
||||||
|
spa_filter_graph_process(impl->graph, cin, cout, data_size / sizeof(float));
|
||||||
|
|
||||||
|
if (in != NULL)
|
||||||
|
pw_stream_queue_buffer(impl->capture, in);
|
||||||
|
if (out != NULL)
|
||||||
|
pw_stream_queue_buffer(impl->playback, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void capture_process(void *d)
|
||||||
|
{
|
||||||
|
struct impl *impl = d;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (impl->playback) {
|
||||||
|
if ((res = pw_stream_trigger_process(impl->playback)) < 0) {
|
||||||
|
pw_log_debug("playback trigger error: %s", spa_strerror(res));
|
||||||
|
while (impl->capture) {
|
||||||
|
struct pw_buffer *t;
|
||||||
|
if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL)
|
||||||
|
break;
|
||||||
|
/* playback part is not ready, consume, discard and recycle
|
||||||
|
* the capture buffers */
|
||||||
|
pw_stream_queue_buffer(impl->capture, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
do_process(impl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void playback_process(void *d)
|
static void playback_process(void *d)
|
||||||
{
|
{
|
||||||
struct impl *impl = d;
|
struct impl *impl = d;
|
||||||
struct pw_buffer *in, *out;
|
do_process(impl);
|
||||||
uint32_t i, data_size = 0;
|
|
||||||
int32_t stride = 0;
|
|
||||||
struct spa_data *bd;
|
|
||||||
const void *cin[128];
|
|
||||||
void *cout[128];
|
|
||||||
|
|
||||||
in = NULL;
|
|
||||||
while (true) {
|
|
||||||
struct pw_buffer *t;
|
|
||||||
if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL)
|
|
||||||
break;
|
|
||||||
if (in)
|
|
||||||
pw_stream_queue_buffer(impl->capture, in);
|
|
||||||
in = t;
|
|
||||||
}
|
|
||||||
if (in == NULL)
|
|
||||||
pw_log_debug("%p: out of capture buffers: %m", impl);
|
|
||||||
|
|
||||||
if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL)
|
|
||||||
pw_log_debug("%p: out of playback buffers: %m", impl);
|
|
||||||
|
|
||||||
if (in == NULL || out == NULL)
|
|
||||||
goto done;
|
|
||||||
|
|
||||||
for (i = 0; i < in->buffer->n_datas; i++) {
|
|
||||||
uint32_t offs, size;
|
|
||||||
|
|
||||||
bd = &in->buffer->datas[i];
|
|
||||||
|
|
||||||
offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
|
|
||||||
size = SPA_MIN(bd->chunk->size, bd->maxsize - offs);
|
|
||||||
|
|
||||||
cin[i] = SPA_PTROFF(bd->data, offs, void);
|
|
||||||
|
|
||||||
data_size = i == 0 ? size : SPA_MIN(data_size, size);
|
|
||||||
stride = SPA_MAX(stride, bd->chunk->stride);
|
|
||||||
}
|
|
||||||
for (; i < impl->n_inputs; i++)
|
|
||||||
cin[i] = NULL;
|
|
||||||
|
|
||||||
for (i = 0; i < out->buffer->n_datas; i++) {
|
|
||||||
bd = &out->buffer->datas[i];
|
|
||||||
|
|
||||||
data_size = SPA_MIN(data_size, bd->maxsize);
|
|
||||||
|
|
||||||
cout[i] = bd->data;
|
|
||||||
|
|
||||||
bd->chunk->offset = 0;
|
|
||||||
bd->chunk->size = data_size;
|
|
||||||
bd->chunk->stride = stride;
|
|
||||||
}
|
|
||||||
for (; i < impl->n_outputs; i++)
|
|
||||||
cout[i] = NULL;
|
|
||||||
|
|
||||||
pw_log_trace_fp("%p: stride:%d size:%d requested:%"PRIu64" (%"PRIu64")", impl,
|
|
||||||
stride, data_size, out->requested, out->requested * stride);
|
|
||||||
|
|
||||||
if (impl->graph_active)
|
|
||||||
spa_filter_graph_process(impl->graph, cin, cout, data_size / sizeof(float));
|
|
||||||
|
|
||||||
done:
|
|
||||||
if (in != NULL)
|
|
||||||
pw_stream_queue_buffer(impl->capture, in);
|
|
||||||
if (out != NULL)
|
|
||||||
pw_stream_queue_buffer(impl->playback, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int activate_graph(struct impl *impl)
|
static int activate_graph(struct impl *impl)
|
||||||
|
|
@ -1403,6 +1420,9 @@ static void update_latency(struct impl *impl, enum spa_direction direction, bool
|
||||||
struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ?
|
struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ?
|
||||||
impl->playback : impl->capture;
|
impl->playback : impl->capture;
|
||||||
|
|
||||||
|
if (s == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||||
latency = impl->latency[direction];
|
latency = impl->latency[direction];
|
||||||
spa_process_latency_info_add(&impl->process_latency, &latency);
|
spa_process_latency_info_add(&impl->process_latency, &latency);
|
||||||
|
|
@ -1459,10 +1479,13 @@ static void param_tag_changed(struct impl *impl, const struct spa_pod *param,
|
||||||
if (param == 0 || spa_tag_parse(param, &tag, &state) < 0)
|
if (param == 0 || spa_tag_parse(param, &tag, &state) < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (tag.direction == SPA_DIRECTION_INPUT)
|
if (tag.direction == SPA_DIRECTION_INPUT) {
|
||||||
pw_stream_update_params(impl->capture, params, 1);
|
if (impl->capture)
|
||||||
else
|
pw_stream_update_params(impl->capture, params, 1);
|
||||||
pw_stream_update_params(impl->playback, params, 1);
|
} else {
|
||||||
|
if (impl->playback)
|
||||||
|
pw_stream_update_params(impl->playback, params, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void capture_state_changed(void *data, enum pw_stream_state old,
|
static void capture_state_changed(void *data, enum pw_stream_state old,
|
||||||
|
|
@ -1539,8 +1562,7 @@ static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod *
|
||||||
return;
|
return;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
pw_stream_set_error(direction == SPA_DIRECTION_INPUT ? impl->capture : impl->playback,
|
pw_stream_set_error(stream, res, "can't start graph: %s", spa_strerror(res));
|
||||||
res, "can't start graph: %s", spa_strerror(res));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
||||||
|
|
@ -1599,7 +1621,7 @@ static void playback_state_changed(void *data, enum pw_stream_state old,
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
error:
|
error:
|
||||||
pw_stream_set_error(impl->capture, res, "can't start graph: %s",
|
pw_stream_set_error(impl->playback, res, "can't start graph: %s",
|
||||||
spa_strerror(res));
|
spa_strerror(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1628,43 +1650,39 @@ static const struct pw_stream_events out_stream_events = {
|
||||||
static int setup_streams(struct impl *impl)
|
static int setup_streams(struct impl *impl)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
uint32_t i, n_params, *offs;
|
uint32_t i, n_params, *offs, flags;
|
||||||
struct pw_array offsets;
|
struct pw_array offsets;
|
||||||
const struct spa_pod **params = NULL;
|
const struct spa_pod **params = NULL;
|
||||||
struct spa_pod_dynamic_builder b;
|
struct spa_pod_dynamic_builder b;
|
||||||
struct spa_filter_graph *graph = impl->graph;
|
struct spa_filter_graph *graph = impl->graph;
|
||||||
|
|
||||||
impl->capture = pw_stream_new(impl->core,
|
if (impl->capture_info.channels > 0) {
|
||||||
"filter capture", impl->capture_props);
|
impl->capture = pw_stream_new(impl->core,
|
||||||
impl->capture_props = NULL;
|
"filter capture", impl->capture_props);
|
||||||
if (impl->capture == NULL)
|
impl->capture_props = NULL;
|
||||||
return -errno;
|
if (impl->capture == NULL)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
pw_stream_add_listener(impl->capture,
|
pw_stream_add_listener(impl->capture,
|
||||||
&impl->capture_listener,
|
&impl->capture_listener,
|
||||||
&in_stream_events, impl);
|
&in_stream_events, impl);
|
||||||
|
}
|
||||||
|
|
||||||
impl->playback = pw_stream_new(impl->core,
|
if (impl->playback_info.channels > 0) {
|
||||||
"filter playback", impl->playback_props);
|
impl->playback = pw_stream_new(impl->core,
|
||||||
impl->playback_props = NULL;
|
"filter playback", impl->playback_props);
|
||||||
if (impl->playback == NULL)
|
impl->playback_props = NULL;
|
||||||
return -errno;
|
if (impl->playback == NULL)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
pw_stream_add_listener(impl->playback,
|
pw_stream_add_listener(impl->playback,
|
||||||
&impl->playback_listener,
|
&impl->playback_listener,
|
||||||
&out_stream_events, impl);
|
&out_stream_events, impl);
|
||||||
|
}
|
||||||
|
|
||||||
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
||||||
pw_array_init(&offsets, 512);
|
pw_array_init(&offsets, 512);
|
||||||
|
|
||||||
if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) == NULL) {
|
|
||||||
res = -errno;
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
*offs = b.b.state.offset;
|
|
||||||
spa_format_audio_raw_build(&b.b,
|
|
||||||
SPA_PARAM_EnumFormat, &impl->capture_info);
|
|
||||||
|
|
||||||
for (i = 0;; i++) {
|
for (i = 0;; i++) {
|
||||||
uint32_t save = b.b.state.offset;
|
uint32_t save = b.b.state.offset;
|
||||||
if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1)
|
if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1)
|
||||||
|
|
@ -1687,7 +1705,7 @@ static int setup_streams(struct impl *impl)
|
||||||
res = -ENOMEM;
|
res = -ENOMEM;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if ((params = calloc(n_params, sizeof(struct spa_pod*))) == NULL) {
|
if ((params = calloc(n_params+1, sizeof(struct spa_pod*))) == NULL) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -1696,32 +1714,44 @@ static int setup_streams(struct impl *impl)
|
||||||
for (i = 0; i < n_params; i++)
|
for (i = 0; i < n_params; i++)
|
||||||
params[i] = spa_pod_builder_deref(&b.b, offs[i]);
|
params[i] = spa_pod_builder_deref(&b.b, offs[i]);
|
||||||
|
|
||||||
res = pw_stream_connect(impl->capture,
|
if (impl->capture) {
|
||||||
PW_DIRECTION_INPUT,
|
params[n_params++] = spa_format_audio_raw_build(&b.b,
|
||||||
PW_ID_ANY,
|
SPA_PARAM_EnumFormat, &impl->capture_info);
|
||||||
PW_STREAM_FLAG_AUTOCONNECT |
|
flags = PW_STREAM_FLAG_AUTOCONNECT |
|
||||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||||
PW_STREAM_FLAG_RT_PROCESS |
|
PW_STREAM_FLAG_RT_PROCESS;
|
||||||
PW_STREAM_FLAG_ASYNC,
|
if (impl->playback)
|
||||||
params, n_params);
|
flags |= PW_STREAM_FLAG_ASYNC;
|
||||||
|
|
||||||
spa_pod_dynamic_builder_clean(&b);
|
res = pw_stream_connect(impl->capture,
|
||||||
if (res < 0)
|
PW_DIRECTION_INPUT,
|
||||||
goto done;
|
PW_ID_ANY,
|
||||||
|
flags,
|
||||||
|
params, n_params);
|
||||||
|
|
||||||
n_params = 0;
|
spa_pod_dynamic_builder_clean(&b);
|
||||||
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
if (res < 0)
|
||||||
params[n_params++] = spa_format_audio_raw_build(&b.b,
|
goto done;
|
||||||
SPA_PARAM_EnumFormat, &impl->playback_info);
|
|
||||||
|
|
||||||
res = pw_stream_connect(impl->playback,
|
n_params = 0;
|
||||||
PW_DIRECTION_OUTPUT,
|
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
||||||
PW_ID_ANY,
|
}
|
||||||
PW_STREAM_FLAG_AUTOCONNECT |
|
if (impl->playback) {
|
||||||
|
params[n_params++] = spa_format_audio_raw_build(&b.b,
|
||||||
|
SPA_PARAM_EnumFormat, &impl->playback_info);
|
||||||
|
|
||||||
|
flags = PW_STREAM_FLAG_AUTOCONNECT |
|
||||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||||
PW_STREAM_FLAG_RT_PROCESS |
|
PW_STREAM_FLAG_RT_PROCESS;
|
||||||
PW_STREAM_FLAG_TRIGGER,
|
if (impl->capture)
|
||||||
params, n_params);
|
flags |= PW_STREAM_FLAG_TRIGGER;
|
||||||
|
|
||||||
|
res = pw_stream_connect(impl->playback,
|
||||||
|
PW_DIRECTION_OUTPUT,
|
||||||
|
PW_ID_ANY,
|
||||||
|
flags,
|
||||||
|
params, n_params);
|
||||||
|
}
|
||||||
spa_pod_dynamic_builder_clean(&b);
|
spa_pod_dynamic_builder_clean(&b);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
|
@ -1801,7 +1831,11 @@ static void graph_props_changed(void *object, enum spa_direction direction)
|
||||||
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
|
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
|
||||||
spa_filter_graph_get_props(graph, &b.b, (struct spa_pod **)¶ms[0]);
|
spa_filter_graph_get_props(graph, &b.b, (struct spa_pod **)¶ms[0]);
|
||||||
|
|
||||||
pw_stream_update_params(impl->capture, params, 1);
|
if (impl->capture)
|
||||||
|
pw_stream_update_params(impl->capture, params, 1);
|
||||||
|
else if (impl->playback)
|
||||||
|
pw_stream_update_params(impl->playback, params, 1);
|
||||||
|
|
||||||
spa_pod_dynamic_builder_clean(&b);
|
spa_pod_dynamic_builder_clean(&b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue